refactor!: rebrand wraith to alknet

Rename all crates, CLI commands, constants, type names, doc comments,
and documentation from wraith to alknet. Includes wire-protocol changes:
ALPN wraith-ssh -> alknet-ssh, reserved destination prefix wraith- ->
alknet-, SSH auth username wraith -> alknet.
This commit is contained in:
2026-06-05 10:04:32 +00:00
parent af7f4d0006
commit 596c89ce24
101 changed files with 552 additions and 552 deletions

View File

@@ -19,7 +19,7 @@ No password auth. The client handler is simpler than the server — it just need
## Acceptance Criteria
- [ ] `crates/wraith-core/src/auth/client_auth.rs` exports `ClientAuthConfig` and client handler
- [ ] `crates/alknet-core/src/auth/client_auth.rs` exports `ClientAuthConfig` and client handler
- [ ] `ClientAuthConfig` holds: `private_key: KeyPair`, optional `public_key: PublicKey`
- [ ] `ClientAuthConfig::from_key_source(source: KeySource) -> Result<Self>` — loads key via key-loading module
- [ ] Implements `russh::client::Handler` with `auth_publickey()` returning the public key

View File

@@ -22,14 +22,14 @@ Use `thiserror` for structured error types propagated via `anyhow::Result` in th
## Acceptance Criteria
- [ ] `crates/wraith-core/src/error.rs` exports error types
- [ ] `crates/alknet-core/src/error.rs` exports error types
- [ ] `TransportError` enum: `ConnectionFailed`, `HandshakeFailed`, `Timeout`, `ProxyFailed`
- [ ] `AuthError` enum: `KeyRejected`, `CertInvalid`, `CertExpired`, `CertPrincipalMismatch`, `NoMatchingKey`
- [ ] `ChannelError` enum: `TargetUnreachable`, `ProxyConnectFailed`, `ChannelClosed`
- [ ] `ConfigError` enum: `InvalidFlag`, `KeyFileNotFound`, `BindFailed`, `IncompatibleOptions`
- [ ] All error types implement `std::error::Error` via `thiserror`, `Display`, and `Debug`
- [ ] Error types have `source` chaining where appropriate (e.g., `TransportError::HandshakeFailed { source: std::io::Error }`)
- [ ] Re-exported from `crates/wraith-core/src/lib.rs`
- [ ] Re-exported from `crates/alknet-core/src/lib.rs`
- [ ] Unit tests for Display output of each error variant
## References

View File

@@ -25,7 +25,7 @@ All keys must be in **OpenSSH key format** (not PEM/PKCS#1/PKCS#8). This module
## Acceptance Criteria
- [ ] `crates/wraith-core/src/auth/keys.rs` exports key loading functions
- [ ] `crates/alknet-core/src/auth/keys.rs` exports key loading functions
- [ ] `KeySource` enum: `File(PathBuf)` and `Memory(Vec<u8>)` for unified key input handling
- [ ] `load_private_key(source: KeySource) -> Result<russh::key::KeyPair>` — loads OpenSSH private key from file or memory
- [ ] `load_public_keys(source: KeySource) -> Result<Vec<russh::key::PublicKey>>` — loads one or more public keys from authorized_keys format

View File

@@ -22,7 +22,7 @@ No password authentication over SSH. This is the `russh::server::Handler::auth_p
## Acceptance Criteria
- [ ] `crates/wraith-core/src/auth/server_auth.rs` exports `ServerAuthConfig` and auth logic
- [ ] `crates/alknet-core/src/auth/server_auth.rs` exports `ServerAuthConfig` and auth logic
- [ ] `ServerAuthConfig` holds: `authorized_keys: HashSet<PublicKey>`, `cert_authorities: Vec<CertAuthorityEntry>`
- [ ] `ServerAuthConfig::from_keys_and_ca()` constructor: loads authorized keys and cert-authority entries from provided key sources
- [ ] Auth check function: given a presented key/certificate, return `Accept` or `Reject`

View File

@@ -1,6 +1,6 @@
---
id: cli/connect-command
name: Implement `wraith connect` CLI subcommand with clap
name: Implement `alknet connect` CLI subcommand with clap
status: pending
depends_on:
- client/connect-options
@@ -12,15 +12,15 @@ level: implementation
## Description
Implement the `wraith connect` CLI subcommand using `clap` with derive macros. Translates `ConnectOptions` into CLI flags and runs the client session. All options from client.md CLI interface must be supported.
Implement the `alknet connect` CLI subcommand using `clap` with derive macros. Translates `ConnectOptions` into CLI flags and runs the client session. All options from client.md CLI interface must be supported.
Environment variable defaults: `WRAITH_SERVER`, `WRAITH_IDENTITY` as convenience defaults per ADR-011.
Environment variable defaults: `ALKNET_SERVER`, `ALKNET_IDENTITY` as convenience defaults per ADR-011.
`--proxy` with `--transport tcp` should warn or be a no-op (ADR-019: client proxy wraps transport, and TCP transport is already direct).
## Acceptance Criteria
- [ ] `wraith connect` subcommand flags match client.md CLI interface: `--server`, `--peer`, `--transport`, `--identity`, `--socks5`, `--forward`, `--remote-forward`, `--proxy`, `--iroh-relay`, `--tls-server-name`, `--insecure`
- [ ] `alknet connect` subcommand flags match client.md CLI interface: `--server`, `--peer`, `--transport`, `--identity`, `--socks5`, `--forward`, `--remote-forward`, `--proxy`, `--iroh-relay`, `--tls-server-name`, `--insecure`
- [ ] `--server` required for tcp/tls transport (validated at parse time or runtime)
- [ ] `--peer` required for iroh transport (validated)
- [ ] `--identity` required for all transports
@@ -28,11 +28,11 @@ Environment variable defaults: `WRAITH_SERVER`, `WRAITH_IDENTITY` as convenience
- [ ] `--socks5` defaults to `127.0.0.1:1080`
- [ ] `--forward` is repeatable (clap `multiple_occurrences`)
- [ ] `--remote-forward` is repeatable
- [ ] Environment variable defaults: `WRAITH_SERVER` for `--server`, `WRAITH_IDENTITY` for `--identity`
- [ ] Environment variable defaults: `ALKNET_SERVER` for `--server`, `ALKNET_IDENTITY` for `--identity`
- [ ] `--proxy` with `--transport tcp` prints warning (ADR-019: effectively no-op)
- [ ] CLI translates args into `ConnectOptions` and calls `ClientSession::new(opts).run().await`
- [ ] Errors reported to stderr with non-zero exit code
- [ ] `cargo run -p wraith -- connect --help` shows all flags with descriptions
- [ ] `cargo run -p alknet -- connect --help` shows all flags with descriptions
## References

View File

@@ -1,6 +1,6 @@
---
id: cli/serve-command
name: Implement `wraith serve` CLI subcommand with clap
name: Implement `alknet serve` CLI subcommand with clap
status: completed
depends_on:
- server/serve-loop
@@ -12,16 +12,16 @@ level: implementation
## Description
Implement the `wraith serve` CLI subcommand using `clap` with derive macros. This translates `ServeOptions` into CLI flags and runs the server. All options from server.md CLI interface must be supported.
Implement the `alknet serve` CLI subcommand using `clap` with derive macros. This translates `ServeOptions` into CLI flags and runs the server. All options from server.md CLI interface must be supported.
Environment variable defaults: none mandated for serve, but consistent with programmatic-first API.
The binary is the `wraith` crate at `crates/wraith/src/main.rs`.
The binary is the `alknet` crate at `crates/alknet/src/main.rs`.
## Acceptance Criteria
- [x] `crates/wraith/src/main.rs` defines CLI with clap derive: `wraith` with `serve` and `connect` subcommands (connect stub for now)
- [x] `wraith serve` subcommand flags match server.md CLI interface exactly: `--key`, `--authorized-keys`, `--cert-authority`, `--transport`, `--listen`, `--tls-cert`, `--tls-key`, `--acme-domain`, `--stealth`, `--proxy`, `--iroh-relay`, `--max-connections-per-ip`, `--max-auth-attempts`
- [x] `crates/alknet/src/main.rs` defines CLI with clap derive: `alknet` with `serve` and `connect` subcommands (connect stub for now)
- [x] `alknet serve` subcommand flags match server.md CLI interface exactly: `--key`, `--authorized-keys`, `--cert-authority`, `--transport`, `--listen`, `--tls-cert`, `--tls-key`, `--acme-domain`, `--stealth`, `--proxy`, `--iroh-relay`, `--max-connections-per-ip`, `--max-auth-attempts`
- [x] `--key` is required (no default)
- [x] `--transport` defaults to `tcp`
- [x] `--listen` defaults to `0.0.0.0:22`
@@ -31,7 +31,7 @@ The binary is the `wraith` crate at `crates/wraith/src/main.rs`.
- [x] Key inputs accept file paths (strings); in-memory key data is a library/API concern, not CLI
- [x] CLI translates args into `ServeOptions` and calls `Server::new(opts).run().await`
- [x] Errors reported to stderr with non-zero exit code
- [x] `cargo run -p wraith -- serve --help` shows all flags with descriptions
- [x] `cargo run -p alknet -- serve --help` shows all flags with descriptions
## References
@@ -44,4 +44,4 @@ All 12 CLI flags implemented. ServeTransportModeArg ValueEnum maps to ServeTrans
## Summary
Implemented wraith serve CLI subcommand with all server.md flags. Clap derive with ServeTransportModeArg, stealth validation, ACME feature gate, iroh endpoint ID printing. Build/clippy/test pass across all feature combinations.
Implemented alknet serve CLI subcommand with all server.md flags. Clap derive with ServeTransportModeArg, stealth validation, ACME feature gate, iroh endpoint ID printing. Build/clippy/test pass across all feature combinations.

View File

@@ -32,7 +32,7 @@ Reconnection is always enabled. The backoff caps at 30 seconds and continues ind
## Acceptance Criteria
- [x] `crates/wraith-core/src/client/channel_manager.rs` exports `ChannelManager`
- [x] `crates/alknet-core/src/client/channel_manager.rs` exports `ChannelManager`
- [x] `ChannelManager` holds: `Arc<Transport>`, `Arc<ClientAuthConfig>`, `Arc<client::Handle<ClientHandler>>` (behind RwLock for reconnection)
- [x] `ChannelManager::new()` establishes initial transport connection, authenticates, returns manager
- [x] `open_direct_tcpip(host, port)` — opens SSH channel to target
@@ -61,4 +61,4 @@ Reconnection is always enabled. The backoff caps at 30 seconds and continues ind
## Summary
Implemented `ChannelManager` in `crates/wraith-core/src/client/channel_manager.rs` with SSH session management, channel opens (`open_direct_tcpip`), port forward requests (`request_tcpip_forward`/`cancel_tcpip_forward`), and automatic reconnection with exponential backoff (1s→30s cap). Full reconnect per ADR-004 creates new transport stream + new SSH session. Port forwards are re-registered after successful reconnect. 8 unit tests covering backoff timing, forward tracking, transport failure, and reconnection detection.
Implemented `ChannelManager` in `crates/alknet-core/src/client/channel_manager.rs` with SSH session management, channel opens (`open_direct_tcpip`), port forward requests (`request_tcpip_forward`/`cancel_tcpip_forward`), and automatic reconnection with exponential backoff (1s→30s cap). Full reconnect per ADR-004 creates new transport stream + new SSH session. Port forwards are re-registered after successful reconnect. 8 unit tests covering backoff timing, forward tracking, transport failure, and reconnection detection.

View File

@@ -34,7 +34,7 @@ Graceful shutdown (SIGTERM/SIGINT):
## Acceptance Criteria
- [ ] `crates/wraith-core/src/client/mod.rs` re-exports all client components
- [ ] `crates/alknet-core/src/client/mod.rs` re-exports all client components
- [ ] `ConnectOptions` struct with fields matching client.md CLI interface: `server`, `peer`, `transport_mode`, `identity`, `socks5_addr`, `forwards`, `remote_forwards`, `proxy`, `iroh_relay`, `tls_server_name`, `insecure`
- [ ] `ConnectOptions::identity` accepts `KeySource` (file or in-memory)
- [ ] `ClientSession::new(opts: ConnectOptions) -> Result<Self>` — creates transport, connects, authenticates

View File

@@ -30,7 +30,7 @@ Both types are specified as repeatable `--forward` / `--remote-forward` CLI opti
## Acceptance Criteria
- [ ] `crates/wraith-core/src/client/forward.rs` exports `PortForwardSpec`, `LocalForwarder`, `RemoteForwarder`
- [ ] `crates/alknet-core/src/client/forward.rs` exports `PortForwardSpec`, `LocalForwarder`, `RemoteForwarder`
- [ ] `PortForwardSpec` parses `-L` / `-R` spec strings: `local_addr:local_port:remote_host:remote_port`
- [ ] `LocalForwarder` binds TcpListener, accepts connections, opens SSH direct-tcpip channel for each, proxies bidirectionally
- [ ] `RemoteForwarder` sends `tcpip_forward` request, handles `forwarded-tcpip` channel opens, connects to local target, proxies bidirectionally

View File

@@ -25,14 +25,14 @@ Supports SOCKS5h (domain names resolved server-side) by default. This prevents D
## Acceptance Criteria
- [ ] `crates/wraith-core/src/socks5/mod.rs` exports `Socks5Server`
- [ ] `crates/alknet-core/src/socks5/mod.rs` exports `Socks5Server`
- [ ] `Socks5Server` binds to configurable listen address (default `127.0.0.1:1080`)
- [ ] SOCKS5 handshake: method negotiation (no-auth only), target address parsing (IPv4, IPv6, domain name)
- [ ] Domain name targets (SOCKS5h) sent unresolved to server — no local DNS resolution
- [ ] For each SOCKS5 connection, opens SSH `direct_tcpip` channel and proxies bytes bidirectionally
- [ ] Connection errors (SSH session down, channel open failed) result in SOCKS5 error response to client
- [ ] No logging of SOCKS5 request targets (ADR-006) — only connection-level events logged
- [ ] SOCKS5 server always enabled when `wraith connect` runs (per client.md constraint)
- [ ] SOCKS5 server always enabled when `alknet connect` runs (per client.md constraint)
- [ ] Unit tests: SOCKS5 handshake parsing, address type handling, bidirectional proxy flow (with mock transport)
## References

View File

@@ -1,6 +1,6 @@
---
id: meta/cli-layer
name: Complete CLI layer — wraith serve and wraith connect commands
name: Complete CLI layer — alknet serve and alknet connect commands
status: completed
depends_on:
- cli/serve-command
@@ -13,13 +13,13 @@ level: planning
## Description
Meta task that clusters CLI tasks. Once complete, the `wraith` binary has both `serve` and `connect` subcommands with all flags matching the architecture specs.
Meta task that clusters CLI tasks. Once complete, the `alknet` binary has both `serve` and `connect` subcommands with all flags matching the architecture specs.
## Acceptance Criteria
- [x] Both CLI tasks completed
- [x] `wraith serve --help` and `wraith connect --help` match architecture spec flag lists
- [x] End-to-end: `wraith serve` + `wraith connect` establishes working SSH tunnel
- [x] `alknet serve --help` and `alknet connect --help` match architecture spec flag lists
- [x] End-to-end: `alknet serve` + `alknet connect` establishes working SSH tunnel
## References
@@ -27,4 +27,4 @@ Meta task that clusters CLI tasks. Once complete, the `wraith` binary has both `
## Summary
CLI layer complete. Both `wraith serve` and `wraith connect` subcommands implemented with all architecture spec flags.
CLI layer complete. Both `alknet serve` and `alknet connect` subcommands implemented with all architecture spec flags.

View File

@@ -14,13 +14,13 @@ level: planning
## Description
Meta task that clusters NAPI tasks. Once complete, the `@alkdev/wraith` Node.js native addon provides `connect()` and `serve()` returning duplex streams for TypeScript consumers.
Meta task that clusters NAPI tasks. Once complete, the `@alkdev/alknet` Node.js native addon provides `connect()` and `serve()` returning duplex streams for TypeScript consumers.
## Acceptance Criteria
- [x] All NAPI tasks completed
- [x] `connect()` returns Duplex stream, no SOCKS5, no port forwarding
- [x] `serve()` returns WraithServer with close() and onConnection events
- [x] `serve()` returns AlknetServer with close() and onConnection events
- [x] Key material from Buffer (in-memory) and file paths both work
- [x] JS-to-Rust and Rust-to-JS error marshalling works correctly
@@ -30,4 +30,4 @@ Meta task that clusters NAPI tasks. Once complete, the `@alkdev/wraith` Node.js
## Summary
NAPI layer complete. connect() returns WraithStream (read/write/close), serve() returns WraithServer with close()/onConnection(). Key material works from both file paths and in-memory Buffers. TCP transport fully supported; TLS/iroh return helpful errors in NAPI layer.
NAPI layer complete. connect() returns AlknetStream (read/write/close), serve() returns AlknetServer with close()/onConnection(). Key material works from both file paths and in-memory Buffers. TCP transport fully supported; TLS/iroh return helpful errors in NAPI layer.

View File

@@ -17,7 +17,7 @@ level: planning
## Description
Meta task that clusters all server module tasks. Once complete, the server accepts SSH connections via any transport, authenticates clients, proxies channel traffic to TCP targets (directly or via proxy), handles stealth mode, rate limits connections, routes reserved `wraith-` destinations, and shuts down gracefully.
Meta task that clusters all server module tasks. Once complete, the server accepts SSH connections via any transport, authenticates clients, proxies channel traffic to TCP targets (directly or via proxy), handles stealth mode, rate limits connections, routes reserved `alknet-` destinations, and shuts down gracefully.
## Acceptance Criteria
@@ -27,7 +27,7 @@ Meta task that clusters all server module tasks. Once complete, the server accep
- [x] Channel proxying with direct, SOCKS5, and HTTP CONNECT outbound modes
- [x] Stealth mode detects SSH vs HTTP and returns fake nginx 404
- [x] Rate limiting and structured logging
- [x] Control channel routing for `wraith-*` destinations
- [x] Control channel routing for `alknet-*` destinations
- [x] Graceful shutdown
## References
@@ -40,4 +40,4 @@ All server module tasks completed across Gens 4-7. Server layer is fully impleme
## Summary
Server layer complete: handler (auth + channel dispatch), channel proxy (direct/SOCKS5/HTTP CONNECT), stealth mode (protocol multiplexing), rate limiting (per-IP connection limits), control channel (wraith-* destination routing), serve loop (accept loop + graceful shutdown). All 229 tests pass.
Server layer complete: handler (auth + channel dispatch), channel proxy (direct/SOCKS5/HTTP CONNECT), stealth mode (protocol multiplexing), rate limiting (per-IP connection limits), control channel (alknet-* destination routing), serve loop (accept loop + graceful shutdown). All 229 tests pass.

View File

@@ -13,23 +13,23 @@ level: implementation
## Description
Implement the NAPI `connect()` function per ADR-007. This is fundamentally different from CLI `wraith connect`:
Implement the NAPI `connect()` function per ADR-007. This is fundamentally different from CLI `alknet connect`:
- **NAPI `connect()`**: Opens a single SSH channel and returns it as a Node.js `Duplex` stream. No SOCKS5 server, no port forwarding. The caller reads and writes bytes directly.
- **CLI `wraith connect`**: Full SSH client session with SOCKS5 server and port forwarding.
- **CLI `alknet connect`**: Full SSH client session with SOCKS5 server and port forwarding.
The function accepts `WraithConnectOptions` and returns `Promise<Duplex>`. The NAPI layer handles transport selection, SSH authentication, and channel setup, then hands the caller a stream.
The function accepts `AlknetConnectOptions` and returns `Promise<Duplex>`. The NAPI layer handles transport selection, SSH authentication, and channel setup, then hands the caller a stream.
## Acceptance Criteria
- [ ] `#[napi]` function `connect(options: WraithConnectOptions) -> Result<DuplexStream>` in `crates/wraith-napi/src/connect.rs`
- [ ] `WraithConnectOptions` struct with napi fields: `server`, `peer`, `transport`, `identity`, `tlsServerName`, `insecure`, `irohRelay`, `proxy`
- [ ] `#[napi]` function `connect(options: AlknetConnectOptions) -> Result<DuplexStream>` in `crates/alknet-napi/src/connect.rs`
- [ ] `AlknetConnectOptions` struct with napi fields: `server`, `peer`, `transport`, `identity`, `tlsServerName`, `insecure`, `irohRelay`, `proxy`
- [ ] Transport creation from options (tcp, tls, iroh) — same logic as CLI but programmatic
- [ ] SSH client connection: create transport stream, authenticate, open single `direct_tcpip` channel
- [ ] Channel returned as `napi::DuplexStream` for JavaScript consumption
- [ ] Key material: `identity` field accepts file path (string) or `Buffer` (in-memory data) per ADR-011
- [ ] Error marshalling: Rust errors become JavaScript exceptions with descriptive messages
- [ ] TypeScript type: `(options: WraithConnectOptions) => Promise<Duplex>`
- [ ] TypeScript type: `(options: AlknetConnectOptions) => Promise<Duplex>`
- [ ] Integration test from JS: connect to a test server, write/receive bytes through stream
## References

View File

@@ -1,6 +1,6 @@
---
id: napi/project-setup
name: Set up wraith-napi project with napi-rs build tooling and TypeScript types
name: Set up alknet-napi project with napi-rs build tooling and TypeScript types
status: pending
depends_on:
- setup/project-init
@@ -12,7 +12,7 @@ level: implementation
## Description
Set up the napi-rs project for the `@alkdev/wraith` Node.js native addon. This includes the napi-rs build configuration, TypeScript type definitions, and the package structure.
Set up the napi-rs project for the `@alkdev/alknet` Node.js native addon. This includes the napi-rs build configuration, TypeScript type definitions, and the package structure.
Per ADR-015 and ADR-016: napi-rs is the FFI bridge, and the wrapper exposes `connect()` and `serve()` functions. The NAPI layer is transport-agnostic — it doesn't know about pubsub's `EventEnvelope`.
@@ -20,11 +20,11 @@ The Cargo.toml skeleton was created in setup/project-init. This task configures
## Acceptance Criteria
- [ ] `crates/wraith-napi/` has `Cargo.toml` with `crate-type = ["cdylib"]`, `napi` and `napi-derive` dependencies
- [ ] `crates/wraith-napi/src/lib.rs` with napi module registration
- [ ] `packages/wraith-napi/` directory (or similar) with `package.json` named `@alkdev/wraith`
- [ ] `packages/wraith-napi/tsconfig.json` for TypeScript type generation
- [ ] TypeScript type definitions for `WraithConnectOptions`, `WraithServeOptions`, `WraithServer`, `ConnectionInfo` matching napi-and-pubsub.md interfaces
- [ ] `crates/alknet-napi/` has `Cargo.toml` with `crate-type = ["cdylib"]`, `napi` and `napi-derive` dependencies
- [ ] `crates/alknet-napi/src/lib.rs` with napi module registration
- [ ] `packages/alknet-napi/` directory (or similar) with `package.json` named `@alkdev/alknet`
- [ ] `packages/alknet-napi/tsconfig.json` for TypeScript type generation
- [ ] TypeScript type definitions for `AlknetConnectOptions`, `AlknetServeOptions`, `AlknetServer`, `ConnectionInfo` matching napi-and-pubsub.md interfaces
- [ ] `napi.config.js` or `NapiRs.config` with correct cargo path, module name
- [ ] Build command: `npm run build` builds the native addon
- [ ] Feature flags: `iroh` feature optional; base package includes tcp + tls

View File

@@ -13,15 +13,15 @@ level: implementation
## Description
Implement the NAPI `serve()` function per ADR-016. Returns a `WraithServer` object with a `close()` method and `onConnection` event emitter. Each incoming SSH connection produces a `Duplex` stream.
Implement the NAPI `serve()` function per ADR-016. Returns a `AlknetServer` object with a `close()` method and `onConnection` event emitter. Each incoming SSH connection produces a `Duplex` stream.
The function accepts `WraithServeOptions` and returns `Promise<WraithServer>`. The NAPI layer handles transport binding, SSH server setup, and connection handling.
The function accepts `AlknetServeOptions` and returns `Promise<AlknetServer>`. The NAPI layer handles transport binding, SSH server setup, and connection handling.
## Acceptance Criteria
- [x] `#[napi]` function `serve(options: WraithServeOptions) -> Result<WraithServer>` in `crates/wraith-napi/src/serve.rs`
- [x] `WraithServeOptions` struct with napi fields: `transport`, `hostKey`, `authorizedKeys`, `certAuthority`, `tlsCert`, `tlsKey`, `acmeDomain`, `listen`, `irohRelay`
- [x] `WraithServer` napi class with `close() -> Promise<void>` and `onConnection(callback)` event registration
- [x] `#[napi]` function `serve(options: AlknetServeOptions) -> Result<AlknetServer>` in `crates/alknet-napi/src/serve.rs`
- [x] `AlknetServeOptions` struct with napi fields: `transport`, `hostKey`, `authorizedKeys`, `certAuthority`, `tlsCert`, `tlsKey`, `acmeDomain`, `listen`, `irohRelay`
- [x] `AlknetServer` napi class with `close() -> Promise<void>` and `onConnection(callback)` event registration
- [x] Each incoming connection produces a `Duplex` stream via the `onConnection` callback
- [x] `ConnectionInfo` struct passed with each connection: `remoteAddr`, `transportKind`
- [x] Key material: `hostKey`, `authorizedKeys` accept file path (string) or `Buffer` (in-memory)
@@ -32,14 +32,14 @@ The function accepts `WraithServeOptions` and returns `Promise<WraithServer>`. T
## References
- docs/architecture/napi-and-pubsub.md — NAPI serve() spec, WraithServer interface
- docs/architecture/napi-and-pubsub.md — NAPI serve() spec, AlknetServer interface
- docs/architecture/decisions/016-napi-expose-connect-and-serve.md — both connect() and serve()
- docs/architecture/server.md — server configuration
## Notes
TCP transport fully implemented. TLS/iroh transports return helpful "not yet supported" errors. WraithServerStream provides read/write/close. ConnectionInfo includes remoteAddr and transportKind.
TCP transport fully implemented. TLS/iroh transports return helpful "not yet supported" errors. AlknetServerStream provides read/write/close. ConnectionInfo includes remoteAddr and transportKind.
## Summary
Implemented NAPI serve() in crates/wraith-napi/src/serve.rs: WraithServeOptions, WraithServer with close()/onConnection(), WraithServerStream (Duplex read/write/close), ConnectionInfo. TCP transport works end-to-end. 241 tests pass, clippy clean.
Implemented NAPI serve() in crates/alknet-napi/src/serve.rs: AlknetServeOptions, AlknetServer with close()/onConnection(), AlknetServerStream (Duplex read/write/close), ConnectionInfo. TCP transport works end-to-end. 241 tests pass, clippy clean.

View File

@@ -14,21 +14,21 @@ level: review
## Description
Final review of the complete wraith system. Verify CLI binary works end-to-end, NAPI wrapper provides correct JavaScript API, and both layers properly wrap the core library.
Final review of the complete alknet system. Verify CLI binary works end-to-end, NAPI wrapper provides correct JavaScript API, and both layers properly wrap the core library.
## Acceptance Criteria
- [x] `wraith serve` + `wraith connect` end-to-end: SSH tunnel established, SOCKS5 proxy routes traffic
- [x] `alknet serve` + `alknet connect` end-to-end: SSH tunnel established, SOCKS5 proxy routes traffic
- [x] All CLI flags work: transport modes (tcp, tls, iroh), auth options, proxy, stealth, rate limits
- [x] Environment variables (`WRAITH_SERVER`, `WRAITH_IDENTITY`) work as defaults
- [x] Environment variables (`ALKNET_SERVER`, `ALKNET_IDENTITY`) work as defaults
- [x] `--stealth` validates `--transport tls` requirement
- [x] NAPI `connect()` returns Duplex stream; data flows bidirectionally
- [x] NAPI `serve()` accepts connections; `onConnection` emits Duplex streams
- [x] NAPI key material from Buffer works (not just file paths)
- [x] Feature flags: `tls`, `iroh`, `acme` correctly gate optional functionality
- [x] Base build (`cargo build -p wraith-core` with no features) compiles and works
- [x] Base build (`cargo build -p alknet-core` with no features) compiles and works
- [x] All tests pass: `cargo test --workspace`
- [x] NAPI tests pass: `cd crates/wraith-napi && npm test`
- [x] NAPI tests pass: `cd crates/alknet-napi && npm test`
- [x] `cargo clippy --workspace` passes
- [x] No logging of tunnel destinations anywhere in the system
@@ -39,8 +39,8 @@ Final review of the complete wraith system. Verify CLI binary works end-to-end,
## Summary
Final review complete. All acceptance criteria verified:
- CLI binary: wraith serve/connect with all flags, env vars, stealth validation
- NAPI: connect() returns WraithStream, serve() returns WraithServer with onConnection
- CLI binary: alknet serve/connect with all flags, env vars, stealth validation
- NAPI: connect() returns AlknetStream, serve() returns AlknetServer with onConnection
- Feature flags: tls, iroh, acme correctly gate optional code; base build compiles
- ADR-006: no server-side logging of tunnel destinations
- 241 tests pass, clippy clean with all features

View File

@@ -28,7 +28,7 @@ Verify end-to-end SSH tunnel flow: client connects → SOCKS5 proxy works → po
- [x] Logging: structured `tracing::info!` events match ADR-013 format
- [x] No logging of tunnel destinations (ADR-006)
- [x] Reconnection: transport failure → exponential backoff → reconnect → port forwards re-registered
- [x] Reserved `wraith-` destinations routed to control channel, not TCP proxy
- [x] Reserved `alknet-` destinations routed to control channel, not TCP proxy
- [x] Graceful shutdown works for both server and client
- [x] All tests pass: `cargo test --workspace`
- [x] `cargo clippy --workspace` passes
@@ -40,7 +40,7 @@ Verify end-to-end SSH tunnel flow: client connects → SOCKS5 proxy works → po
## Summary
Server and client review passed with fixes. Key issues found and resolved:
- wired channel proxy into handler (was dropping all non-wraith channels)
- wired channel proxy into handler (was dropping all non-alknet channels)
- added client reconnection with exponential backoff + remote forward re-registration
- fixed ADR-006 violations (removed server-side destination logging)
- 241 tests pass, clippy clean

View File

@@ -23,7 +23,7 @@ Supports three outbound proxy modes per server.md: Direct, SOCKS5 proxy, HTTP CO
## Acceptance Criteria
- [ ] `crates/wraith-core/src/server/channel_proxy.rs` exports channel proxy functions
- [ ] `crates/alknet-core/src/server/channel_proxy.rs` exports channel proxy functions
- [ ] `ProxyConfig` enum: `Direct`, `Socks5 { addr: SocketAddr }`, `HttpConnect { addr: SocketAddr }`
- [ ] `connect_outbound(target: SocketAddr, proxy: &ProxyConfig) -> Result<TcpStream>` — connects to target directly or via proxy
- [ ] Direct mode: `TcpStream::connect(target)`

View File

@@ -1,6 +1,6 @@
---
id: server/control-channel
name: Implement wraith-control reserved channel for pubsub event bus bridging (ADR-018)
name: Implement alknet-control reserved channel for pubsub event bus bridging (ADR-018)
status: pending
depends_on:
- server/handler
@@ -13,26 +13,26 @@ level: implementation
## Description
Implement the control channel routing per ADR-018. When the server receives a `channel_open_direct_tcpip` request for `wraith-control:0`:
Implement the control channel routing per ADR-018. When the server receives a `channel_open_direct_tcpip` request for `alknet-control:0`:
1. The handler detects the reserved `wraith-` prefix destination
1. The handler detects the reserved `alknet-` prefix destination
2. Instead of making a TCP connection, it bridges the SSH channel to an internal event bus handle
3. `EventEnvelope` JSON flows bidirectionally over the SSH channel
The entire `wraith-` prefix is reserved — no TCP connections should be attempted for `wraith-*` destinations. The control channel is optional; servers without pubsub configured should accept the channel and provide a configurable behavior (reject or provide a loopback pipe).
The entire `alknet-` prefix is reserved — no TCP connections should be attempted for `alknet-*` destinations. The control channel is optional; servers without pubsub configured should accept the channel and provide a configurable behavior (reject or provide a loopback pipe).
At this stage, implement the routing logic and a `ControlChannel` trait that consumers can implement. The actual pubsub bridge implementation would be in a separate crate or behind a feature flag.
## Acceptance Criteria
- [ ] `crates/wraith-core/src/server/control_channel.rs` exports `ControlChannelHandler` trait and routing logic
- [ ] `WRAITH_CONTROL_DESTINATION` constant defined as `"wraith-control"` (ADR-018)
- [ ] `WRAITH_PREFIX` constant defined as `"wraith-"` for namespace reservation
- [ ] `crates/alknet-core/src/server/control_channel.rs` exports `ControlChannelHandler` trait and routing logic
- [ ] `ALKNET_CONTROL_DESTINATION` constant defined as `"alknet-control"` (ADR-018)
- [ ] `ALKNET_PREFIX` constant defined as `"alknet-"` for namespace reservation
- [ ] `ControlChannelHandler` trait: `async fn handle_channel(stream: Box<dyn AsyncRead + AsyncWrite + Unpin + Send>)`
- [ ] Server handler detects `wraith-*` prefix and routes to `ControlChannelHandler` instead of TCP proxy
- [ ] Server handler detects `alknet-*` prefix and routes to `ControlChannelHandler` instead of TCP proxy
- [ ] If no `ControlChannelHandler` configured, reject the channel open request (SSH channel open failure)
- [ ] Non-reserved destinations continue through normal TCP proxy path
- [ ] Server constraint enforced: no TCP connections to `wraith-*` destinations
- [ ] Server constraint enforced: no TCP connections to `alknet-*` destinations
- [ ] Unit tests: reserved destination detected, non-reserved passes through, prefix matching works
## References

View File

@@ -16,17 +16,17 @@ level: implementation
Implement the core `ServerHandler` that implements `russh::server::Handler`. This is the heart of the server. Per server.md, it has two primary responsibilities:
1. **`auth_publickey()`**: Delegated to `ServerAuthConfig` — checks key against authorized set or validates cert-authority
2. **`channel_open_direct_tcpip()`**: Routes the channel — either to a TCP target (directly or via proxy) or internally for reserved `wraith-*` destinations (ADR-018)
2. **`channel_open_direct_tcpip()`**: Routes the channel — either to a TCP target (directly or via proxy) or internally for reserved `alknet-*` destinations (ADR-018)
At this stage, implement the handler struct, auth delegation, and the channel dispatch skeleton (actual TCP connection and proxy logic in dependent tasks).
## Acceptance Criteria
- [ ] `crates/wraith-core/src/server/handler.rs` exports `ServerHandler`
- [ ] `crates/alknet-core/src/server/handler.rs` exports `ServerHandler`
- [ ] `ServerHandler` implements `russh::server::Handler`
- [ ] `ServerHandler` holds: `Arc<ServerAuthConfig>`, `outbound_proxy: Option<ProxyConfig>`, `remote_addr: Option<SocketAddr>`
- [ ] `auth_publickey()` delegates to `ServerAuthConfig` and returns `Accept` or `Reject`
- [ ] `channel_open_direct_tcpip()` dispatches: if `host.starts_with("wraith-")`, route to internal handler (stub for control channel); otherwise, spawn TCP proxy task (stub that logs and returns error for now)
- [ ] `channel_open_direct_tcpip()` dispatches: if `host.starts_with("alknet-")`, route to internal handler (stub for control channel); otherwise, spawn TCP proxy task (stub that logs and returns error for now)
- [ ] One `ServerHandler` instance per connection; state is not shared between connections (unless explicitly Arc'd)
- [ ] Structured auth logging via `tracing::info!` with `remote_addr`, `key_fingerprint`, `result` (ADR-013)
- [ ] Unit tests: auth delegation works, reserved destination routing logic, unknown channel types rejected
@@ -34,7 +34,7 @@ At this stage, implement the handler struct, auth delegation, and the channel di
## References
- docs/architecture/server.md — Server Handler Behavior section, channel handling
- docs/architecture/decisions/018-control-channel-for-pubsub.md — reserved `wraith-*` destinations
- docs/architecture/decisions/018-control-channel-for-pubsub.md — reserved `alknet-*` destinations
- docs/architecture/decisions/013-fail2ban-friendly-logging.md — structured auth logging
## Notes

View File

@@ -21,7 +21,7 @@ No logging of tunnel destinations, DNS resolutions, or bytes transferred (ADR-00
## Acceptance Criteria
- [ ] `crates/wraith-core/src/server/rate_limit.rs` exports connection rate limiter
- [ ] `crates/alknet-core/src/server/rate_limit.rs` exports connection rate limiter
- [ ] `ConnectionRateLimiter` tracks active connections per IP using `HashMap<IpAddr, usize>`
- [ ] `ConnectionRateLimiter::check(ip) -> bool` — returns `true` if connection allowed, `false` if over limit
- [ ] `ConnectionRateLimiter::on_connect(ip)` — increment counter

View File

@@ -26,7 +26,7 @@ Implement the server's main accept loop and configuration. This ties together th
## Acceptance Criteria
- [x] `crates/wraith-core/src/server/mod.rs` re-exports all server components
- [x] `crates/alknet-core/src/server/mod.rs` re-exports all server components
- [x] `ServeOptions` struct with fields matching server.md CLI interface: `key`, `authorized_keys`, `cert_authority`, `transport_mode`, `listen_addr`, `tls_cert`, `tls_key`, `acme_domain`, `stealth`, `proxy`, `iroh_relay`, `max_connections_per_ip`, `max_auth_attempts`
- [x] `Server::new(opts: ServeOptions) -> Result<Server>` — creates server with bound acceptor, auth config, rate limiter
- [x] `Server::run()` — enters accept loop, for each connection: check rate limit → create handler → `run_stream()`
@@ -56,7 +56,7 @@ Key design decisions:
## Summary
Implemented server accept loop and configuration in `crates/wraith-core/src/server/serve.rs`:
Implemented server accept loop and configuration in `crates/alknet-core/src/server/serve.rs`:
- `ServeOptions` struct with all CLI interface fields, builder pattern, KeySource support
- `Server::new()` creates server with russh config, auth config, rate limiter
- `Server::run(acceptor, endpoint_info)` enters accept loop with rate limiting, stealth detection, russh::server::run_stream()

View File

@@ -25,7 +25,7 @@ Stealth mode requires TLS transport. The CLI should reject or warn if `--stealth
## Acceptance Criteria
- [ ] `crates/wraith-core/src/server/stealth.rs` exports stealth mode protocol detection
- [ ] `crates/alknet-core/src/server/stealth.rs` exports stealth mode protocol detection
- [ ] `detect_protocol(stream: TlsStream) -> ProtocolDetection` — peeks at first bytes to determine SSH vs HTTP
- [ ] `ProtocolDetection` enum: `Ssh`, `Http` (or `Unknown`)
- [ ] If SSH detected: pass stream to `russh::server::run_stream()`

View File

@@ -1,6 +1,6 @@
---
id: setup/project-init
name: Initialize Cargo workspace with wraith, wraith-core, and wraith-napi crates
name: Initialize Cargo workspace with alknet, alknet-core, and alknet-napi crates
status: pending
depends_on: []
scope: moderate
@@ -13,30 +13,30 @@ level: implementation
Set up the Rust workspace from scratch. The repo currently has only `docs/` and `.git/`. Initialize a Cargo workspace with three crate directories following the architecture spec:
- **`wraith-core`** — library crate with feature flags (`tls`, `iroh`, `acme`). All core logic lives here.
- **`wraith`** — binary crate depending on `wraith-core`. CLI entry point.
- **`wraith-napi`** — napi-rs crate for the Node.js native addon (skeleton only at this stage).
- **`alknet-core`** — library crate with feature flags (`tls`, `iroh`, `acme`). All core logic lives here.
- **`alknet`** — binary crate depending on `alknet-core`. CLI entry point.
- **`alknet-napi`** — napi-rs crate for the Node.js native addon (skeleton only at this stage).
Per overview.md: `russh`, `tokio`, `clap`, `tracing`, `anyhow`/`thiserror` are core dependencies. `tokio-rustls`, `rustls`, `rustls-acme`, `iroh` are feature-gated.
## Acceptance Criteria
- [ ] `Cargo.toml` workspace root with `[workspace]` members: `crates/wraith-core`, `crates/wraith`, `crates/wraith-napi`
- [ ] `crates/wraith-core/Cargo.toml` with library crate, feature flags: `tls` (tokio-rustls + rustls), `iroh` (iroh), `acme` (rustls-acme, implies `tls`)
- [ ] `Cargo.toml` workspace root with `[workspace]` members: `crates/alknet-core`, `crates/alknet`, `crates/alknet-napi`
- [ ] `crates/alknet-core/Cargo.toml` with library crate, feature flags: `tls` (tokio-rustls + rustls), `iroh` (iroh), `acme` (rustls-acme, implies `tls`)
- [ ] Core dependencies listed: `russh`, `tokio` (full), `tracing`, `anyhow`, `thiserror`, `tokio-util`
- [ ] `crates/wraith/Cargo.toml` with binary crate, depends on `wraith-core` with default features, `clap` with `derive` feature
- [ ] `crates/wraith-napi/Cargo.toml` with `cdylib` crate type, depends on `wraith-core`, `napi` and `napi-derive`
- [ ] `crates/wraith-core/src/lib.rs` with module skeleton: `pub mod transport; pub mod client; pub mod server; pub mod auth; pub mod socks5; pub mod error;`
- [ ] `crates/wraith/src/main.rs` with minimal `fn main()` skeleton
- [ ] `crates/wraith-napi/src/lib.rs` with `#[macro_use] extern crate napi_derive;` and empty skeleton
- [ ] `crates/alknet/Cargo.toml` with binary crate, depends on `alknet-core` with default features, `clap` with `derive` feature
- [ ] `crates/alknet-napi/Cargo.toml` with `cdylib` crate type, depends on `alknet-core`, `napi` and `napi-derive`
- [ ] `crates/alknet-core/src/lib.rs` with module skeleton: `pub mod transport; pub mod client; pub mod server; pub mod auth; pub mod socks5; pub mod error;`
- [ ] `crates/alknet/src/main.rs` with minimal `fn main()` skeleton
- [ ] `crates/alknet-napi/src/lib.rs` with `#[macro_use] extern crate napi_derive;` and empty skeleton
- [ ] `.gitignore` covers `target/`, `node_modules/`
- [ ] `cargo check` succeeds for all workspace members
- [ ] Feature flags resolve correctly: `cargo check -p wraith-core --features tls`, `--features iroh`, `--features acme`
- [ ] Feature flags resolve correctly: `cargo check -p alknet-core --features tls`, `--features iroh`, `--features acme`
## References
- docs/architecture/overview.md — package structure, dependencies, feature flags
- docs/architecture/napi-and-pubsub.md — wraith-napi crate purpose
- docs/architecture/napi-and-pubsub.md — alknet-napi crate purpose
## Notes

View File

@@ -18,8 +18,8 @@ The mock transport is critical — it lets us test SSH client/server flows witho
## Acceptance Criteria
- [ ] `crates/wraith-core/tests/` directory with empty integration test skeletons: `transport_tests.rs`, `client_tests.rs`, `server_tests.rs`, `auth_tests.rs`
- [ ] `crates/wraith-core/src/testutil.rs` module (behind `#[cfg(test)]` or a `testutil` feature) exporting `MockTransport` and `MockStream`
- [ ] `crates/alknet-core/tests/` directory with empty integration test skeletons: `transport_tests.rs`, `client_tests.rs`, `server_tests.rs`, `auth_tests.rs`
- [ ] `crates/alknet-core/src/testutil.rs` module (behind `#[cfg(test)]` or a `testutil` feature) exporting `MockTransport` and `MockStream`
- [ ] `MockStream` wraps `tokio::io::DuplexStream` implementing `AsyncRead + AsyncWrite + Unpin + Send`
- [ ] `MockTransport` implements `Transport` trait (once defined) returning `MockStream` via `connect()`
- [ ] `MockTransportAcceptor` implements `TransportAcceptor` (once defined) returning paired `MockStream` via `accept()`

View File

@@ -23,7 +23,7 @@ This integrates with `TlsAcceptor` by providing ACME-resolved certificates inste
## Acceptance Criteria
- [ ] `crates/wraith-core/src/transport/acme.rs` (behind `#[cfg(feature = "acme")]`)
- [ ] `crates/alknet-core/src/transport/acme.rs` (behind `#[cfg(feature = "acme")]`)
- [ ] Feature `acme` implies `tls` in Cargo.toml
- [ ] `AcmeCertProvider` struct accepts: domain (domain-based) or IP mode flag
- [ ] Domain-based mode: uses `rustls-acme` with HTTP-01/TLS-ALPN-01 challenge responder

View File

@@ -24,10 +24,10 @@ Feature-gated behind `iroh` feature flag.
## Acceptance Criteria
- [ ] `crates/wraith-core/src/transport/iroh.rs` (behind `#[cfg(feature = "iroh")]`)
- [ ] `crates/alknet-core/src/transport/iroh.rs` (behind `#[cfg(feature = "iroh")]`)
- [ ] `IrohTransport` holds: target endpoint ID (base58-decoded to `NodeId`), relay URL, optional proxy URL
- [ ] `IrohTransport::connect()` calls `endpoint.connect(node_id, alpn)`, then `conn.open_bi()`, then `tokio::io::join(recv, send)`
- [ ] ALPN value is `b"wraith-ssh"`
- [ ] ALPN value is `b"alknet-ssh"`
- [ ] `IrohTransport::describe()` returns e.g. `"iroh://<endpoint-id>"`
- [ ] `IrohAcceptor` holds an `iroh::Endpoint` instance
- [ ] `IrohAcceptor::bind()` creates endpoint with relay URL and optional proxy config

View File

@@ -16,7 +16,7 @@ Implement the simplest transport: plain TCP. `TcpTransport` connects via `TcpStr
## Acceptance Criteria
- [ ] `crates/wraith-core/src/transport/tcp.rs` exports `TcpTransport` and `TcpAcceptor`
- [ ] `crates/alknet-core/src/transport/tcp.rs` exports `TcpTransport` and `TcpAcceptor`
- [ ] `TcpTransport` holds a `SocketAddr` target address
- [ ] `TcpTransport::connect()` calls `TcpStream::connect(addr)` and returns the stream
- [ ] `TcpTransport::describe()` returns e.g. `"tcp://1.2.3.4:22"`

View File

@@ -25,7 +25,7 @@ Feature-gated behind `tls` feature flag.
## Acceptance Criteria
- [ ] `crates/wraith-core/src/transport/tls.rs` (behind `#[cfg(feature = "tls")]`)
- [ ] `crates/alknet-core/src/transport/tls.rs` (behind `#[cfg(feature = "tls")]`)
- [ ] `TlsTransport` holds: target addr, optional `tls_server_name`, `insecure` flag, optional root cert for verification
- [ ] `TlsTransport::connect()` does TCP connect then TLS client handshake via `tokio_rustls::TlsConnector`
- [ ] When `insecure`, accepts any certificate (dangerous, `webpki_roots::CertStore` bypass or custom verifier)

View File

@@ -18,14 +18,14 @@ The `TransportInfo` and `TransportKind` types carry metadata about incoming conn
## Acceptance Criteria
- [ ] `crates/wraith-core/src/transport/mod.rs` exports `Transport` trait, `TransportAcceptor` trait, `TransportInfo`, `TransportKind`
- [ ] `crates/alknet-core/src/transport/mod.rs` exports `Transport` trait, `TransportAcceptor` trait, `TransportInfo`, `TransportKind`
- [ ] `Transport` trait: `async fn connect(&self) -> Result<Self::Stream>` where `Self::Stream: AsyncRead + AsyncWrite + Unpin + Send + 'static`
- [ ] `Transport::describe(&self) -> String` for human-readable logging
- [ ] `TransportAcceptor` trait: `async fn accept(&self) -> Result<(Self::Stream, TransportInfo)>` with same stream bounds
- [ ] `TransportInfo { remote_addr: Option<SocketAddr>, transport_kind: TransportKind }`
- [ ] `TransportKind` enum: `Tcp`, `Tls { server_name: Option<String> }`, `Iroh { endpoint_id: String }`
- [ ] Traits are `Send + Sync + 'static`
- [ ] Re-exported from `crates/wraith-core/src/lib.rs`
- [ ] Re-exported from `crates/alknet-core/src/lib.rs`
- [ ] Unit tests verifying trait objects can be constructed (trait is object-safe with `Box<dyn Transport<Stream = ...>>`)
- [ ] Documentation comments on all public types referencing ADR-001, ADR-004