Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 9 additions & 12 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,29 +4,26 @@ All notable changes to this project will be documented in this file.

The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).

## [0.6.0] - 2026-04-09
## [0.7.0] - 2026-04-12

### Breaking

- **`@authmesh/agent` merged into `@authmesh/cli`** — single `amesh` binary replaces `amesh` + `amesh-agent`. Install `@authmesh/cli` only. The `@authmesh/agent` npm package is deprecated.
- **Remote shell and agent daemon removed** — `amesh agent start/stop`, `amesh shell`, `amesh grant`, and `amesh reset` commands removed. The `@authmesh/agent` npm package is deprecated. amesh now focuses exclusively on device-bound M2M authentication.

### Security

- **Reduced attack surface** — removing the agent daemon (PTY spawning, shell cipher, frame protocol) eliminates the highest-risk component
- **Relay simplified** — agent registration, challenge-response, and shell routing handlers removed

## [0.6.0] - 2026-04-09

### Added

- **`amesh agent start`** / **`amesh agent stop`** — daemon management with PID file and graceful shutdown via SIGTERM
- **`amesh listen --shell`** — auto-grants shell permission to the new controller after pairing completes
- **`amesh reset`** — clears stale session state (stops running agent, removes PID file) without affecting identity or pairings
- **SAS confirmation protocol** — controller now waits for target to verify the 6-digit code before adding to allow list, preventing one-sided trust when the target rejects or disconnects

### Security

- **Relay per-session data cap** — 5 MB maximum forwarded per session, prevents bulk data streaming abuse
- **Relay shell rate limit** tightened to 2 sessions/min per IP (was 5)

### Changed

- **Unified binary** — all commands (`init`, `listen`, `invite`, `shell`, `agent start/stop`, `reset`, etc.) in one `amesh` binary
- **`install-agent` script** now redirects to the main install script
- **Packaging** — single binary in .deb, Homebrew, and release tarballs

## [0.5.3] - 2026-04-08

Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,7 @@ app.use(amesh.verify());
| Package | Description |
|---------|-------------|
| [`@authmesh/sdk`](./packages/sdk) | Signing fetch client + Express verification middleware |
| [`@authmesh/cli`](./packages/cli) | CLI + agent: `init`, `listen`, `invite`, `list`, `revoke`, `provision`, `grant`, `shell`, `agent start/stop`, `reset` |
| [`@authmesh/cli`](./packages/cli) | CLI: `init`, `listen`, `invite`, `list`, `revoke`, `provision` |
| [`@authmesh/core`](./packages/core) | Crypto primitives: sign, verify, canonical string, nonce, HMAC, HKDF, ECDH |
| [`@authmesh/keystore`](./packages/keystore) | Key storage drivers: Secure Enclave, macOS Keychain, TPM 2.0, encrypted file |
| [`@authmesh/relay`](./packages/relay) | WebSocket relay for device pairing handshakes |
Expand Down
34 changes: 8 additions & 26 deletions docs/architecture-decisions.md
Original file line number Diff line number Diff line change
Expand Up @@ -165,39 +165,21 @@ The controller CLI displays this code; the target CLI prompts the operator to en

---

## ADR-011: Remote shell in the CLI with explicit shell permission
## ADR-011: Remote shell removed (v0.7.0)

> **Status: Partially superseded (2026-04-05).** The single-package design was reversed: the agent daemon now ships in a separate `@authmesh/cli` package exposing an `amesh` binary, while `@authmesh/cli` (`amesh`) keeps the controller-side commands (`init`, `list`, `invite`, `shell`, etc.) without the daemon.
>
> **Why the split:** the daemon uses `Bun.spawn({ terminal })` for PTY support, a Bun-only API. Bundling it with `@authmesh/cli` forced the entire controller install to depend on Bun even for users who only wanted `amesh init` + `amesh.fetch()`. Splitting lets `@authmesh/cli` ship a Node-compatible CLI and `@authmesh/cli` ship a Bun-dependent (or prebuilt-binary-only) daemon. The per-architecture prebuilt binaries are produced by the same release pipeline, so end users `brew install ameshdev/tap/amesh` to get both.
>
> **The security argument below is unchanged:** `amesh grant --shell` is still the real boundary, not the package boundary. The split is purely a runtime-dependency concern.
> **Status: Superseded (2026-04-12).** The remote shell feature (agent daemon, shell client, grant/reset commands) was removed entirely in v0.7.0. amesh now focuses exclusively on device-bound M2M authentication.

**Original decision (superseded):** The remote shell feature is part of `@authmesh/cli` — one package, one binary. `amesh shell` connects to a remote target. `amesh agent start` runs the daemon. Shell access requires explicit `amesh grant --shell` after pairing.
**Why it was removed:**

**Why:**
1. **Mission dilution.** The remote shell was tangential to the core value proposition (replacing static API keys with device-bound identities). It confused the product story.

1. **One install:** Developers install one thing (`@authmesh/cli`) and get everything — identity management, pairing, API auth, shell client, and agent daemon.
2. **Attack surface.** The agent daemon spawned bash processes and was the highest-risk component. The v0.5.0 security audit found 4 critical/high issues in shell code alone. Removing it eliminates an entire class of risk.

2. **Explicit consent:** Pairing for API authentication (`amesh invite`) does not grant shell access. A `permissions.shell` flag in the allow list defaults to `false`. The target admin must explicitly run `amesh grant <device-id> --shell`. This is the security boundary, not the package boundary.
3. **Maintenance weight.** The shell feature touched ~25 files across every package (CLI, relay, core, keystore, docs, landing page). Each release required building, testing, and distributing agent binaries.

3. **The daemon is opt-in by invocation:** `amesh agent start` must be explicitly run. It doesn't auto-start, doesn't install as a service, and refuses to run as root without `--allow-root`.
4. **Bun runtime dependency.** The agent required `Bun.spawn({ terminal })` for PTY support, creating friction that the core SDK didn't have.

**Security design choices:**

- **Incrementing nonce counters** (not random) for shell encryption — eliminates birthday-bound collision risk over long sessions
- **Device-ID-bound HKDF** (`amesh-shell-v1` salt + both device IDs) — cryptographic separation from pairing sessions
- **No session resumption** — dropped connection = full new ECDH handshake
- **Authenticated agent registration** — relay stores public key, controllers must match it (prevents squatting)
- **Uniform relay responses** — no `agent_not_found` message (prevents device enumeration)
- **Root guard** — agent refuses `root` without `--allow-root`
- **Per-controller session limits** — prevents DoS by authorized-but-misbehaving peers

**Rejected alternatives (at the time of the original decision):**
- ~~Separate `@authmesh/cli` package — adds install confusion without meaningful security benefit; the permission gate (`amesh grant --shell`) is the real security boundary, not the package boundary~~ *This was later reversed — see the Status note above. The runtime-dependency concern (Bun for PTY) outweighed the install-confusion concern once prebuilt binaries were shipped via the release pipeline.*
- Auto-granting shell on pairing — violates principle of least privilege
- Reusing pairing handshake's random-nonce encryption — birthday-bound risk over long sessions
- Session resumption — complexity and nonce-reuse risk outweigh the latency benefit
If remote shell demand materializes, it could return as a separate companion package with its own release cycle.

---

Expand Down
36 changes: 1 addition & 35 deletions docs/guide.md
Original file line number Diff line number Diff line change
Expand Up @@ -256,9 +256,8 @@ The handshake establishes trust between two machines. Run it once per device pai

On the **target** machine (the server being secured):
```bash
amesh listen --shell
amesh listen
# ✔ "Dev Laptop" added as controller.
# Shell access: granted
```

On the **controller** machine (your laptop), using the 6-digit code displayed by the target:
Expand Down Expand Up @@ -314,39 +313,6 @@ Health check: `curl http://localhost:3001/health`

---

## 9. Remote Shell

Once paired with `--shell`, you can open a remote terminal to the target.

On the **target** (server):
```bash
amesh agent start
# [amesh agent] Registered with relay (identity verified).
# [amesh agent] Authorized controllers with shell access: 1
```

On the **controller** (your laptop):
```bash
amesh shell prod-api
# Connected. Shell session started.

amesh shell prod-api -c "uptime" # single command mode
```

Stop the agent:
```bash
amesh agent stop
# Agent (PID 12345) stopped.
```

If you have persistent connection issues:
```bash
amesh reset # clears stale sessions, stops running agent
amesh agent start # reconnect
```

---

## What the Authorization Header Looks Like

```http
Expand Down
10 changes: 5 additions & 5 deletions docs/protocol-spec.md
Original file line number Diff line number Diff line change
Expand Up @@ -822,25 +822,25 @@ Sort query parameters alphabetically before including in canonical string `M`. T

### Security audit — April 2026

A full external-pen-tester-style audit was performed on 2026-04-05 covering the crypto primitives, keystore, SDK middleware, relay, and agent/cli shell flows. All critical and high-severity findings have been fixed; the mediums and informational items have also landed in the same branch. Summary:
A full external-pen-tester-style audit was performed on 2026-04-05 covering the crypto primitives, keystore, SDK middleware, relay, and agent/cli shell flows. All critical and high-severity findings have been fixed. **Note:** The remote shell feature was removed in v0.7.0 — findings marked with *(shell — removed)* are no longer applicable. Summary:

| Severity | Finding | Fix |
|---|---|---|
| Critical | **C1** — Shell handshake MITM via unbound `selfSig` | Transcript-bound signature: `selfSig` now covers `"amesh-shell-v1"` domain prefix + peer identity fields + `sha256(signerEph \|\| verifierEph)`. A MITM relay forwarding an encrypted identity envelope across two ECDH legs no longer produces a signature that verifies on the receiving leg. See Remote Shell Spec §7.1. |
| Critical | **C1** — Shell handshake MITM via unbound `selfSig` *(shell — removed)* | Was fixed with transcript-bound signatures. Feature removed in v0.7.0. |
| Critical | **C2** — Encrypted-file passphrase stored next to the key | Passphrase moved to a dedicated file (`~/.amesh/.passphrase`, mode 0o400) with legacy auto-migration. Preferred source is `AUTH_MESH_PASSPHRASE` env var so secrets can stay off disk. Operators can relocate via `AMESH_PASSPHRASE_FILE`. |
| High | **H1** — Rate limiter used LB peer IP | Relay extracts client IP from left-most `X-Forwarded-For` entry when `AMESH_TRUST_PROXY=1`, with bounded-format validation. |
| High | **H2** — Passphrase colocation (see C2) | — |
| High | **H3** — ShellCipher DoS via counter desync on injected frame | `recvCounter` now only advances after successful Poly1305 verification. |
| High | **H3** — ShellCipher DoS via counter desync *(shell — removed)* | Feature removed in v0.7.0. |
| High | **H4** — Bootstrap token `single_use` not enforced | Relay keeps a 25h consumed-jti set; duplicate `bootstrap_init` is rejected with `token_already_used`. Payload `scope`/`single_use`/`alg`/`iat` are now enforced at decode time. |
| Medium | **M1** — Relay connection counter double-decrement | Rejected sockets marked via `ws.data.rejected`; `close()` handles the single decrement. |
| Medium | **M2** — SessionStore unbounded | 50,000-session cap with distinct `relay_capacity` error code. |
| Medium | **M3** — Bootstrap watcher race | `jti_already_watched` rejection + dedicated rate limiter + jti length cap. |
| Medium | **M4** — Agent listener leak + orphan bash on reconnect | `createMessageReader().dispose()` + outer-scope `activeSession` torn down on relay disconnect. |
| Medium | **M4** — Agent listener leak *(shell — removed)* | Feature removed in v0.7.0. |
| Medium | **M5** — Middleware re-serialized parsed bodies | Middleware now hashes raw bytes only (`rawBody` → Buffer → string → stream). Parsed-object bodies without `rawBody` return `500 body_parser_ordering_error`. |
| Medium | **M6** — Bootstrap token `iat`/`alg`/`scope`/`single_use` unchecked | `validateBootstrapToken` enforces all four invariants with distinct error codes. |
| Medium | **M7** — TPM driver returned wrong formats | `tpm2_sign --format=plain` with TPMT_SIGNATURE fallback parser; `pemToRaw` now properly decodes P-256 SubjectPublicKeyInfo into a 33-byte compressed point. |
| Low | **L2** — Auth header parser laxity | Reject duplicate keys, unknown keys, oversized headers, per-field length caps. |
| Low | **L3** — AgentStore pubkey compare not constant-time | Replaced with `constantTimeStringEqual`. |
| Low | **L3** — AgentStore pubkey compare *(shell — removed)* | Feature removed in v0.7.0. |
| Low | **L4** — macOS DER parser had no bounds checks | Bounds-checked every field, rejected long-form lengths, enforced r/s ≤ 32 bytes. |
| Low | **L5** — Allow-list canonical JSON was insertion-order dependent | Deterministic `stableStringify` with recursive key sorting; legacy canonical accepted on read with auto re-seal. |
| Low | **L6** — Bootstrap ack message had no delimiter | Ack message now `"amesh-bootstrap-ack-v1\n" + pubB64 + "\n" + jti`. |
Expand Down
Loading
Loading