Skip to content

feat(serve): humanish serve — run-library surface with capability-link exposure - #291

Merged
danielgwilson merged 4 commits into
mainfrom
codex/serve-command
Aug 2, 2026
Merged

feat(serve): humanish serve — run-library surface with capability-link exposure#291
danielgwilson merged 4 commits into
mainfrom
codex/serve-command

Conversation

@danielgwilson

Copy link
Copy Markdown
Owner

What

Adds humanish serve — a read-only run-library HTTP surface, so the Observer can be watched from another device (e.g. a phone) through an operator-run tunnel, and so runs/replays can be browsed in one place. Complements the existing watch (attached run) and observe (one finished run) commands; the whole thing binds 127.0.0.1 and only reaches beyond loopback through an explicit --expose.

Three composable modes:

command mode auth content
humanish serve loopback none (loopback trust class + Host allowlist + security headers) all runs
humanish serve --expose --tunnel ngrok --tunnel-domain <domain> capability-link tap-once secret URL → session cookie, required on every request all runs (add --safe to restrict)
humanish serve --safe --expose --auth none --tunnel ngrok share-safe-open none share_ready runs only, fail-closed

No password prompt: exposure uses a capability link (256-bit token → HttpOnly, host-only, SameSite=Lax session cookie). The token is per-process and digest-only in memory; Ctrl-C revokes it and every session; restart rotates it.

Design provenance

Designed via a three-lens workshop (minimal-delta / product-vision / security-doctrine) judged by a doctrine critic and a pragmatism judge, then synthesized. Deliberately not shipped (fatal-flawed in judging): no --host non-loopback bind (would send the secret in cleartext over a LAN), no --secret pin flag (defeats restart-rotation revocation), no mutually-exclusive --expose enum (auth and --safe compose). Exposure always requires --expose plus a declared origin (--tunnel or --public-url); the Host allowlist stays strict in every mode.

Safety properties (each tested)

  • No-loopback-bypass: auth applies regardless of source address — the tunnel agent connects from 127.0.0.1, so a "loopback is trusted" shortcut would disable auth in exactly the deployment it protects.
  • Runtime E2B stream URLs are never served: structural (separate process; the runtime WeakMap is never persisted) + defensive (explicit empty runtimeStreamUrls at the serveRunPath call site) + a no-injection test.
  • No existence oracle: filtered/nonexistent runs return byte-identical 404s; unauthorized requests return a uniform 401.
  • Fail-closed exposure: --auth none requires --safe; a requested tunnel that can't start tears the server down (exit 2) rather than silently serving loopback-only.
  • --safe rides verifyRun shareSafety at the publish/verify boundary, with a bounded (30s) admission TTL so an out-of-band artifact edit can't ride a stale share_ready verdict.

Verification

  • pnpm release:check green: 1125 tests pass (+67 new across auth, server integration, tunnel, CLI), public-surface scan clean (467 files, 2 binary assets), skill check + pack dry-run good.
  • Command-level proof against a real process: capability mint → cookie → 200; no cookie / wrong token → 401; bad Host → 421; reserved /_humanish/api → 501; POST → 405; SIGTERM → clean teardown, port closed. (No live tunnel or provider spend in the test suite; the ngrok JSONL fixture is a real captured wire shape with the domain genericized to observer.example.dev.)
  • An adversarial review pass (correctness / security / public-safety, each finding independently verified) returned zero ship blockers; the confirmed non-blockers it surfaced (admission-cache TTL, cookie Secure derived from the declared origin scheme rather than a spoofable header, honest exposure counts, admit made required, auto-open kept out of process argv under --expose, session-store sweep, keep-alive shutdown, scanner patterns for host paths + ngrok tokens, and two doc/mechanism corrections) are all folded into the fix(serve): commit.

For maintainer sign-off

share_ready was designed as the bar for feedback payloads, and the --safe --auth none open mode extends it to arbitrary-audience browsing. verify does not yet detect free-form PII/PHI (issue #108), so open mode is only as safe as that gate. The capability-link mode (which does not widen the bar — the link holder is you) is the recommended default for phone viewing; the open mode is opt-in and documented as such in docs/architecture/serve.md. Flagging for your explicit call on whether the open mode should ship enabled or be held.

Two commits: serve core + auth, then CLI/tunnel/library/docs; plus the review-fix and 0.17.0 release commits.

🤖 Generated with Claude Code

danielgwilson and others added 4 commits August 2, 2026 00:52
…afety gating

Adds the serve core: a loopback-only run-library HTTP surface with three
composable modes — loopback (trust class of today's watch server plus Host
allowlist and security headers), capability-link (cookie-minting secret URL,
digest-only token storage, per-tap viewer sessions, no loopback-source
bypass), and share-safe-open (share_ready-verified runs only, fail-closed
admission with stat-identity caching).

Auth is origin-scoped by mechanism: the observer client mixes page-relative
and root-relative fetches, so a cookie survives both where a path-prefix
token cannot. Uniform 401 bodies carry no run-existence oracle; filtered
runs 404 byte-identically to nonexistent ones. Runtime E2B stream URLs are
structurally absent (separate process) and defensively pinned by an explicit
empty array at the serveRunPath call site, with a no-injection test.

observer.ts changes are export-only; the watch path is byte-identical.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Registers 'humanish serve' with a fail-closed flag conflict matrix
(exposure requires --expose plus a declared public origin; --auth none
requires --safe; tunnel failures tear the server down rather than silently
serving loopback-only under declared exposure intent). The optional ngrok
tunnel spawns the operator's own external binary — no npm dependency — and
is killed by exact child pid on close.

The library index is a mobile-first, self-contained page (system fonts,
zero external requests) polling the filtered history feed. Docs cover the
threat model, mode-to-boundary mapping, stream-URL doctrine, the
share_ready doctrinal note, and the reserved v2 control-plane seam.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…e scheme, exposure counts

- Admission cache gains a bounded TTL (default 30s) so an out-of-band edit to
  a scanned artifact that leaves run.json untouched is re-verified rather than
  served on a stale share_ready verdict (defense-in-depth on the open mode).
- Cookie Secure now follows the declared public origin scheme, not the
  spoofable X-Forwarded-Proto header; the tunnel's https origin is declared
  post-start via addPublicOrigin.
- Exposure warning counts derive from the uncapped run list, not the 80-item
  index, so 'grants access to all N runs' is truthful for large libraries.
- admit is required on the handler options (no silent fail-open path).
- Auto-open suppressed under --expose (keeps the capability token out of an
  opener process argv); explicit --open still honored.
- Session store sweeps expired records on mint; server force-closes keep-alive
  connections on shutdown; formatServeHuman gains the conventional trailing
  newline.
- public-surface scan gains Linux host-home-path and ngrok-authtoken patterns.
- Docs corrected: /_humanish/api answers 501 behind the auth gate (401 first
  for session-less exposed requests); safe admission is bounded-fresh, not live.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Minor bump for the new `humanish serve` command (loopback run library,
capability-link exposure, share_ready-gated open mode, optional ngrok tunnel).
Bumps package + doc version pins and records serve in the program-truth table.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@danielgwilson
danielgwilson merged commit 6143875 into main Aug 2, 2026
3 checks passed
@danielgwilson
danielgwilson deleted the codex/serve-command branch August 2, 2026 01:35
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