Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Phase log 56: 2026-10-09 - Phase 39: pre-1.0 security review and pentest checklist

Version unchanged (0.38.0): this phase changed no code. It is a read-only review of the surfaces Phases 34 to 38 added, plus the checklist the maintainer must run against a real deployment before tagging 1.0.0 (this environment cannot: no second host, no Windows, no real window). Nothing committed; no commit script (no files changed except this handoff, which is gitignored).

1. What was reviewed (code and tests, 2026-10-09)

SurfaceResult
Node routes (envv-server/src/nodes.rs)Off unless --nodes (404 test). Every node route authenticates a per-request Ed25519 signature over method, path, timestamp and body hash; strictly increasing persisted timestamp; revoked node 401; auth failures counted per IP and rate limited (test with 10+ bad tokens). Owner routes use require_owner; a node's signed headers are not a session (test).
Command execution on a nodeapply/validate/reload come only from the node's own config file; a pushed target is looked up in that config by id (Agent::act), so the hub cannot name a command, a path or a version directory (valid_id rejects .. and /). Commands run with a timeout and scrubbed error text.
Pull uploadsOnly for a target the owner requested (409 otherwise), base64 decoded, 1 MiB cap, held in memory for a bounded time and dropped on revoke.
ApprovalHub withholds the bytes; signed token binds node, target, sha256, expiry, nonce; node verifies independently and spends the nonce on a write; hub key pinned TOFU. 16 mutations in phase log 54, all caught.
History APIOne owner-only route; dispatcher ops take no file paths (list/show/diff/snapshot/prune/verify/stats/policy/blast/where). Content only masked unless reveal.
Dependenciesnpm audit --omit=dev --audit-level=high: 0. cargo deny check: advisories, bans, licenses, sources ok.
Parsers added in 38YAML writer quotes anything not matching a conservative bare-scalar pattern, so a hostile value cannot add a key or a document (tests with reserved words, numbers, newlines, : , #).

No defect found. Residual risks, accepted and written down:

  1. A compromised hub can mint approvals (ADR-0143). Mitigation is the node's own require_approval plus the pinned key; a device-held signing key is not built.
  2. Auth-failure rate limiting is per source IP. Behind a reverse proxy every node shares an address, so one bad node can lock out the others for the window. Run nodes directly or give the proxy a per-client address; a per-node-id limit is a possible follow-up.
  3. Long-poll beats hold a connection up to 25 s per node; only enrolled nodes can do it, but nothing caps beats per node.
  4. The three new project types are unverified in CI until the exporters.yml jobs run green once.
  5. The VS Code extension has never run in a real editor.

2. Checklist for the maintainer (authorised, own systems only)

Run against a real hub, two nodes (one with require_approval), a workstation.

  • Hub with --nodes off: every /api/nodes/* returns 404; with it on, unsigned requests 401.
  • Replay a captured beat (same timestamp) and one with an old timestamp: 401.
  • Reuse an enrollment token; try 20 wrong tokens: second use refused, then 429 with Retry-After.
  • Revoke a node mid-poll: its next request 401, its approvals withdrawn.
  • Edit the node config's target path/commands on the host: only that file is the source of commands.
  • On a require_approval target: push without approval, with another hub's key, with other bytes, replayed: nothing is written, hub audit shows the reason.
  • Change the vault while a request is pending: a new request opens, the old yes does not apply.
  • Read /api/history as a sub-user and with a node's headers: refused.
  • Inspect nodes.json, materialisations.jsonl, approvals-seen.json for 0600 and for absence of any secret value (envv scan --exposed on the state directory).
  • Tamper with a history row in the SQLite file; envv history verify reports the break.
  • Stack outputs: run the exporters.yml workflow once and confirm all three jobs, including the bad-input controls, go green; then consider graduating the types.
  • Windows: build, envv node run as a service, file permission note (no 0600 equivalent).
  • Real window: the A4/A9/A10 smoke tests listed in phase log 37, and the TLS TOFU flow.
  • Repository settings: Dependabot alerts, code scanning, private vulnerability reporting, Pages, CATALOGUE_SIGNING_KEY secret.

3. Next

Sub-phases 28.1, 29.1, 30.1, 30.2, 31.3, 34.1, 38.1, bundle acceptance, and the 1.0.0 viability review (phase log 57).