Skip to content

feat: remote shell — @authmesh/shell with agent daemon + shell client - #11

Merged
YairEtzion merged 4 commits into
mainfrom
feat/remote-shell
Apr 3, 2026
Merged

feat: remote shell — @authmesh/shell with agent daemon + shell client#11
YairEtzion merged 4 commits into
mainfrom
feat/remote-shell

Conversation

@YairEtzion

Copy link
Copy Markdown
Contributor

Summary

New @authmesh/shell package — SSH-like remote access using amesh device identity. Separate install, separate binaries (amesh-agent, amesh-shell). All 3 critical + 4 high security review findings addressed in the implementation.

New package: @authmesh/shell

  • amesh-agent start — daemon on target, persistent relay connection, PTY via Bun.spawn({ terminal: }), encrypted I/O, session management
  • amesh-shell <device> — client on controller, interactive + single command (-c) mode, raw terminal, resize handling
  • ShellCipher — ChaCha20-Poly1305 with incrementing nonces (controller starts 0x00, target starts 0x80), not random nonces
  • Shell handshake — ECDH + identity exchange, device-ID-bound HKDF (amesh-shell-v1 salt), no OTC/SAS needed
  • Frame protocol — binary framing: data (0x01), resize (0x02), exit (0x03), ping (0x04), pong (0x05), command (0x06)

Security review findings addressed

Finding Fix
C1: Agent registration unauthenticated Agent sends publicKey on registration; relay matches before routing
C2: No shell permission gate permissions.shell field in AllowListDevice, requires amesh grant --shell
C3: Relay presence oracle Uniform responses — no agent_not_found message; rate limited
H1: No idle timeout 30-minute default, configurable via --idle-timeout
H3: Session key not bound to device IDs deriveShellSessionKey(shared, targetId, controllerId) with shell-specific HKDF
H4: Nonce counter on reconnect No session resumption — always full new handshake
M1: Agent as root Refuses root without --allow-root
M2: Per-controller session limit Default 1 per controller, 5 total

Modified existing packages

  • @authmesh/corederiveShellSessionKey() in ecdh.ts + 3 new tests
  • @authmesh/keystoreDevicePermissions type, updatePermissions() on AllowList
  • @authmesh/cliamesh grant <device-id> --shell/--no-shell command
  • @authmesh/relayAgentStore, agent/shell/ping message handlers, shell rate limiting

Docs + site

  • Remote Shell use case page + guide page on landing site
  • ADR-011 (remote shell as separate package with explicit permission)
  • Spec (docs/remote-shell-spec.md) + security review (docs/remote-shell-security-review.md)
  • Sitemap updated

Test plan

  • bun run build — all packages compile (core, keystore, cli, sdk, relay, shell, landpage)
  • Shell package: 15 tests pass (ShellCipher: 8, frame protocol: 7)
  • Core package: 84 tests pass (+3 for deriveShellSessionKey)
  • All existing tests pass (keystore: 36, relay: 10, sdk: 20)
  • Lint: clean across all packages
  • Landpage builds with new pages prerendered
  • Manual: e2e agent + shell session on two machines
  • Independent security review before "secure as SSH" claim (hard prerequisite feat: landing page redesign #3)

…l client

New @authmesh/shell package (separate install, separate binaries):
- amesh-agent: daemon on target, persistent relay connection, PTY via Bun.spawn()
- amesh-shell: client on controller, interactive + single command (-c) mode
- ShellCipher: ChaCha20-Poly1305 with incrementing nonces (not random)
- Shell handshake: ECDH + identity exchange, no OTC/SAS, device-ID-bound HKDF
- Frame protocol: data, resize, exit, ping/pong, command (binary framing)

Security foundations (all critical review findings addressed):
- permissions.shell field in AllowListDevice (C2 fix — explicit opt-in)
- amesh grant --shell / --no-shell command in CLI
- deriveShellSessionKey() with device-ID binding (H3 fix)
- Agent registration with publicKey on relay (C1 fix — anti-squatting)
- Uniform relay responses for shell requests (C3 fix — no enumeration)
- Shell rate limiting, heartbeat, agent store with stale cleanup
- Root guard: agent refuses root without --allow-root (M1 fix)
- Per-controller session limits (M2 fix)
- No session resumption (H4 fix)
- Idle timeout with configurable duration (H1 fix)

Docs + site:
- Remote Shell use case page + guide page on landing site
- ADR-011: remote shell as separate package with explicit permission
- Remote Shell Guide in docs index
- navigation.ts updated (auto-propagates to nav, footer, indexes)
- Sitemap updated
- Shell spec and security review documents

Tests: 15 new (ShellCipher: 8, frame protocol: 7) + 3 deriveShellSessionKey tests
H1: Validate selfSig timestamp freshness (60s window) in shell handshake
H2: Zero ephemeral private key, shared secret, and temp key after derivation
H3: Increment session counter before async handshake (prevent race condition)
L3: Zero session key copy after constructing ShellCipher
M1: Add payload length checks to parseResize/parseExit
M2: Add nonce counter overflow guard (2^64 limit)
M6: Shell rate limiter only counts failures, not successful connections

Also: fix Dockerfile.relay for new workspace layout, Dockerfile uses bun install
@YairEtzion
YairEtzion merged commit 4006882 into main Apr 3, 2026
1 check passed
@YairEtzion
YairEtzion deleted the feat/remote-shell branch April 3, 2026 15:23
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant