Skip to content

Introduce two-stage local control auth, stage 6a: accept-and-log (rebuild W3 PR-G)#144

Merged
MaxGhenis merged 1 commit into
mainfrom
w3-prg-auth
Jul 19, 2026
Merged

Introduce two-stage local control auth, stage 6a: accept-and-log (rebuild W3 PR-G)#144
MaxGhenis merged 1 commit into
mainfrom
w3-prg-auth

Conversation

@MaxGhenis

Copy link
Copy Markdown
Owner

Rebuild W3 PR-G — auth stage 6a (accept-and-log, never reject)

Final Wave-3 lane. The installation capability (control.token, cookie, bearer) now exists end-to-end, with zero enforcement: missing/invalid auth WARNs once per remote and passes through. 401s are stage 6b, one packaged-app release later, after the Swift wrapper attaches the token — so the live v2-primary install keeps working untouched.

Review

Opus adversarial review: FIX-FIRST → all 6 findings fixed with discriminators. The two that mattered:

  • Availability trap (proven): a partial write during the one-time mint (disk full) left an empty control.token that bricked every subsequent server start — the exact denial the 6a contract forbids, via file robustness instead of the auth path. Now: atomic temp+rename mint, and empty/undecodable existing files regenerate with a WARN instead of failing startup.
  • Two-data-dirs trap (third sighting this rebuild): the CLI read the token from its XDG default while the macOS daemon mints under ~/Library/Application Support/OpenMessage → nothing attached → openmessage send would 401 the day 6b lands. Now: the CLI resolves the token path from the daemon's own /api/status auth.data_dir (server-truth, like PR-E's mode probe); the 0600 file remains the capability — only the path travels over HTTP.

Plus: de-vacuized the once-per-remote WARN test (same host twice → exactly one WARN), SSE write deadlines so half-open sockets can't hold subscriber slots, warned-map bounded at 1024, bootstrap loopback-gated (defense in depth; wrong guesses never consumed the code even before).

Verification

  • Independent full GOWORK=off go test -race ./... outside the sandbox: 32/32 packages, exit 0
  • Full Playwright suite over the auth-wrapped middleware: 91 passed / 9 skipped / 0 failed — the middleware is invisible to the UI, exactly as 6a demands
  • Never-reject contract pinned by tests asserting unauthenticated requests SUCCEED

🤖 Generated with Claude Code

…uild W3 PR-G)

First half of the W3-6 auth plan: the installation capability exists and is
attached everywhere, but nothing is enforced — missing or invalid auth logs a
rate-limited WARN and passes through. Enforcement (401, stage 6b) ships one
packaged-app release later, after the Swift wrapper attaches the token, so no
unauthenticated native call or MCP client breaks today.

- internal/web/auth.go: 256-bit control token minted-or-loaded at
  <dataDir>/control.token (0600). Minting writes a temp file and renames
  atomically, and an empty or undecodable existing file is regenerated with a
  WARN instead of failing startup — a partial write (e.g. disk full during
  the one-time mint) previously left a file that bricked every subsequent
  start, denying all callers through the side door the accept-and-log
  contract exists to prevent. Constant-time compare; token never logged.
- Requests authenticate via Authorization: Bearer or the om_control cookie
  (HttpOnly, SameSite=Strict, Path=/). A single-use loopback bootstrap URL
  (printed once to stderr by serve --web) sets the cookie and redirects; the
  real token never appears in a URL, wrong guesses never consume the code,
  reuse gets 410, and the endpoint requires a loopback Host before
  evaluating anything.
- Middleware inside ProtectLocalControl classifies bearer/cookie/missing/
  invalid, WARNs once per remote host (port-stripped; map capped at 1024
  with clear-and-warn overflow), and always passes through. /api/status
  gains auth: {token_present, mode: "accept-and-log", data_dir}.
- CLI sends resolve the token from the DAEMON's data dir via the status
  probe rather than the CLI default — the macOS app runs the server against
  ~/Library/Application Support/OpenMessage while a bare terminal defaults
  to ~/.local/share/openmessage, so local-default resolution would attach
  nothing and break openmessage send the day 6b enforces. The 0600 file
  remains the capability; only the path travels over the status endpoint.
- Riding 6a per spec: Cross-Origin-Resource-Policy: same-origin on /api/*
  and /mcp/* (UI media loads are same-origin relative URLs; bootstrap
  excluded), Sec-Fetch-Site anomalies logged never rejected, SSE subscribers
  capped at 32 (frontend falls back to polling with backoff on 503) with a
  write deadline on every SSE event so half-open sockets cannot hold a slot
  for the TCP retransmit timeout.
- Swift header attach is deliberately a separate PR (spec: R6-adjacent).
@MaxGhenis
MaxGhenis merged commit b8e40f4 into main Jul 19, 2026
5 checks passed
@MaxGhenis
MaxGhenis deleted the w3-prg-auth branch July 19, 2026 13:07
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