coolshell-hostd
v0.0.2
Published
CoolShell host daemon — P2P remote AI-agent terminal sessions with a real device-authorization gate. Built directly on hyperdht (MIT); no GPL/AGPL holesail code.
Maintainers
Readme
coolshell-hostd
The CoolShell host daemon — runs on any VPS and exposes persistent AI-agent terminal
sessions over an end-to-end-encrypted P2P connection, gated by a real device-authorization
layer. Built directly on hyperdht (MIT) — no
GPL/AGPL holesail code is used or shipped (holesail is reference-only).
Status: working prototype core, runnable and tested on Node (29/29 tests, stable across repeated runs), hardened against an adversarial security review. The Bare static-binary packaging and the macOS client are separate workstreams.
Why this exists
holesail's connect string is a pure bearer token: the server derives seed = sha256(key),
calls createServer with no firewall and zero client-identity check, so anyone holding the
URL gets a shell. CoolShell's entire security premise is the net-new Layer-2 gate: only an
enrolled device public key may connect, enforced before any data or PTY bytes flow.
Security model (three layers)
- Transport (HyperDHT Noise). Every connection is an E2E-encrypted Noise stream (ed25519 + libsodium secretstream). Peers are identified by static public key. There is no plaintext mode. We always use per-identity secure keypairs.
- Authorization (
authz.js).createServer({ firewall })rejects any inbound connection whoseremotePublicKeyis not an enrolled, non-revoked, non-expired device — before the connection is handed to the app. Leases (ttlMs) make access fail closed: it expires on its own even if a revocation message is never delivered. - Capabilities (
capability.js). Privileged actions (approvals, authorizing another device) require a typed, signed statement bound to{host, live handshakeHash, monotonic counter, validity window, exact action+nonce}— closing cross-host, cross-session, and same-session replay.
Authorization is continuous, not just at connect time (post-review hardening): a periodic
sweep + a devices.json file watch tear down any live connection whose device is revoked or
whose lease expired — so revoke works against the running daemon and an out-of-process CLI
revoke, not only at restart. Sessions are owned by their creating device (no cross-device
hijack). Concurrent-session caps + output backpressure bound resource use. PTY children get a
scrubbed env (no inheritance of hostd secrets). Revocation is sticky (enroll won't
silently un-revoke).
Modules
| File | Responsibility |
|------|----------------|
| src/identity.js | Generate-once / read-on-rerun stable host keypair (0600). The key must never rotate or every paired device is orphaned. |
| src/authz.js | Device allowlist + the HyperDHT firewall gate + leases + revocation + audit log. |
| src/capability.js | Typed, channel-bound, signed capabilities (sign/verify, replay/expiry/host-binding). |
| src/protocol.js | Length-prefixed JSON frame codec over the Noise stream. |
| src/sessions.js | PTY session manager (node-pty) with bounded scrollback + detach/reattach. |
| src/transport.js | The Host: wires DHT → firewall → protocol → sessions, plus approvals. |
| src/client.js | Reference P2P client (test driver + blueprint for the macOS CoolKit). |
| bin/hostd.js | CLI: start \| enroll \| revoke \| list \| info (single-line JSON output). |
Usage
npm install # postinstall fixes node-pty's spawn-helper exec bit
node bin/hostd.js info # prints the stable host public key (creates it once)
node bin/hostd.js enroll <64-hex-device-pubkey> "my-mac"
node bin/hostd.js start # announces on the DHT; prints {event:"listening", publicKey}A client connects by the host public key with an enrolled device keypair; an unenrolled or revoked key is dropped at the gate.
Tests
npm test # node --testtest/identity, test/capability, test/protocol are offline unit tests.
test/e2e.test.js needs network (reaches the public HyperDHT bootstrap) and proves the gate
end to end: enrolled device attaches a real PTY and sees output; unenrolled + revoked are
dropped before any data; signed approvals are accepted, refusals denied.
Not yet (tracked in the product plan)
- Bare static-binary packaging (
bare-make) forlinux-x64/arm64, glibc + musl. - SSH first-contact installer + persistence ladder (systemd-user + linger).
- Secret broker (separate-uid / localhost token) so the provider API key never enters the
agent's
/proc/<pid>/environ. - Relay fallback for symmetric-NAT / CGNAT.
- Independent security review of the gate + capability scheme before any "secure" claim.
