Files
jason.woltje 068d868a99 lead: commit rocko's untracked evidence (4471 files: queue-124 queue-129 queue-171 queue-178 queue-280 queue-31 queue-84 queue-85 queue-94)
Unclaimed files left in the canonical checkout. Inventory: canonical-cleanup-inventory.md (commit-as-evidence groups). detect-secrets scan clean on 2026-11-02: hits are sha256 pins, git object ids, test sentinels already in tracked tests and unit-file lines.
2026-11-02 15:05:15 -06:00

11 KiB
Raw Permalink Blame History

Row 178: Console proxy proof

Issue #1676 / P19 385. Owner Rocko; reviewer Dewey; gate owner Sage. Brief: docs/plans/2026-10-19_console-proxy-proof.md, blob 1da7ac8753. Claim rev 2125. Initial base c7421cabd (plans only above 7f519242e).

Boundaries and design

Only code and fixture tests. No slot token, Traefik middleware, live unit flags, user config, live bus or Console action. No real proof file is created. No credentials, push or source commit. Header and path alone enter unit flags and argv; the value lives only in a buffer read once before listen.

Serve's proofOptions is the shared startup validator. It opens O_NOFOLLOW and O_NONBLOCK, fstats the opened descriptor, requires a regular file owned by the process uid and exact permission bits 0400/0600, then reads that descriptor. A single optional final LF is removed; every remaining byte must be base64url and there must be at least 32. All file errors become one fixed refusal that names neither path nor contents. Headers use the existing header-name grammar, and Host, Origin, auth and tester names cannot collide with proof.

The public-Host request guard precedes auth-header reads, compares exactly one header via equal-length buffers and timingSafeEqual, and returns the same fixed 401 for missing, incorrect and repeated proof. Wrong-Host and tester ingress refuse the proof header with 403. Existing auth, Origin, Fetch Metadata and human write checks remain in force. The tester receives the header name only.

Service render checks metadata and canonical location, not contents. Proof flags precede --send and --tester-port, with the send-status regex extended to accept the optional proof pair; the tester status parsing is unchanged. stack-release already carries arbitrary Console flags through its pointer and target resolution, so its product change is an explicit preservation comment; new lifecycle tests pin that existing behavior for the proof pair, including tester-port replacement. No new activation flag or target probe.

Verification plan and review mapping

  • proof.test.mjs: public read/send success, read once, config/log non-disclosure; missing/wrong/equal-length/short/repeated proofs, before-auth ordering and zero reads; wrong Hosts and tester rejection; header grammar and collisions.
  • proof-cli.test.mjs: partial flags and preview requirement; missing/directory/ symlink/0644/other uid/short/interior newline/extra final LF/bad-byte refusals; generic errors; /proc argv and startup on/off; real main/tester wiring.
  • service.test.mjs: both render flags, metadata/location refusal and no content validation; existing stand-in systemctl, no live unit action.
  • stack-release.test.mjs: exact pointer and unit flags after re-pin, rollback and tester-port replacement. All units and copies are fixtures.

All suites and mutants wait for Sage's explicit slot after row 180. Before review, rebase onto any landed row 180/181 and rerun every touched test singly. Sage owns the full CLI/WebUI and shell landing gate. No timing benchmark is a gate: the constant-time primitive and equal-length check are reviewed directly.

Sandbox and residual boundary

Pending fixture-only probe and source evidence. The proof is a partial fix: same-uid T3 seats can read anything the Console uid can read. This row does not claim to prevent that. Service uid separation remains Jason's decision on #385. Activation is a separate Mos plan-check window, middleware first, then Console flags/restart and smoke; rollback drops flags before middleware. This packet never authorizes those steps.

Prepared checks (not yet run): render-no-open.py watches a temp fixture with Linux inotify IN_OPEN/IN_ACCESS, so reading and discarding bytes cannot pass the render check. Its mutant adds a discarded cat read. sandbox-probe.mjs uses the existing bwrap bind builder with both harness mount lists and a fixture marker outside all mounts, checks the host-config path only inside that sandbox, and also checks the run-task container's exact two mount sources in compose.yaml. It reads no live config. The legacy container check is source evidence only, not a claimed Docker runtime run; an operator override that mounts an ancestor of the proof could expose it. Same-uid callers remain outside this partial fix.

Seventeen exact-once mutant sites are prepared. Tests were written before the implementation, but execution is deferred under sage-1177; no red/green result is claimed before the slot. Only node --check, bash -n, git diff --check and mutation-site text preflight have run during preparation.

Rebase after row 180 (sage-1179)

Before the first run, rebuilt on a757f8fe6, including row 180 landing 1905e385c. One manual conflict: packages/cli/tests/stack-release.test.mjs, both rows appended a tail. Kept row 180's new timestamp, status and plan tests intact and closed that test block, then appended the two independent T178 proof lifecycle tests. The other shared files merged cleanly:

  • scripts/stack-release.sh: row 180's activation-id and plan changes remain; row 178 adds only the proof-pair preservation comment at console_for_target.
  • scripts/webui-service.sh: row 180's tester status parsing remains; row 178 adds proof flags/metadata checking without changing that parser.
  • packages/webui/tests/service.test.mjs: row 180's status cases remain before the two appended proof render tests.
  • docs/TOOLS.md: row 180's operational notes remain; proof notes are appended.

Base red: proof.test.mjs against the rebased original serve.mjs failed all four new tests because proofOptions did not exist, then the candidate was restored. The focused candidate runs follow; no red result is claimed for other files. The first shell command printed CLEAR before red/green began at 03:55:31Z.

Initial suite cleanup stall

The first WebUI suite stalled with proof-cli.test.mjs as its only remaining child. No serve child remained. After terminating that test process group, Node reported one cancelled file, zero assertion failures, and a pending Promise with the event loop resolved. The new test's cleanup checked only child.exitCode, which stays null after signal termination; it could await an already-emitted close event. Both new cleanup guards now also check signalCode. The corrected file passed 3/3 singly before the full suite rerun. The original log is retained as webui-suite-cleanup-stall.log; its counts are not a pass. No product change was made. The busy/sweep scripts also recognize node-* comm names (this Node build reports node-MainThread), as well as node/chromium.

Send-status regression found before packet freeze

After the first full package runs and all 17 original mutants passed, code review found that proof flags between --allow-user and --send made the service status regex say send off. A new service fixture test reproduced exactly that wrong line (service-status-red.log). The regex now accepts the optional proof pair; no tester-status logic changes. The service file passed 31/31 afterward. The final lifecycle and package runs are repeated after this local product fix. M18 removes only the new regex group and must fail the new status assertion. The final batch therefore has the original 17 plus this regression mutant.

The sandbox probe passed for the two harness mount lists, with fixture host config, the host config path and private HOME config all absent inside bwrap. It also verified compose.yaml's exact two mount sources: dataRoot and read-only PI_AUTH_FILE. This establishes no host-config mount in that source definition; no Docker runtime run or live-config-dependent claim is made. Overrides that mount an ancestor of proof can defeat that boundary. Same-uid readers remain outside this partial fix.

Final verification

Final base a757f8fe6. Slot span 2026-10-20T03:55:31Z–04:17:04Z, including the documented cleanup cancellation and local status regression repair. Final WebUI: 269 tests, 268 passed, one pinned esbuild rebuild skipped (0.28.1 installed, 0.28.2 required), zero failed/cancelled. Final CLI: 408/408, zero failed/cancelled/skipped. awk package sum: 677 tests, 676 passed, one skip. Focused file sum: 149/149. render-no-open and sandbox fixture commands passed. No Docker or shell suites were run; Sage owns the shell landing gate. All runners exited; process-sweep.json is empty. Slot clear sent at 04:17:04Z. No recurrence of the named infrastructure flakes was observed.

No source or packet commits. Source byte reconstruction and preserved transport pins are recorded by verification.json and pins.json. reader-and-pin-search.json and rg-path-search.json/rg-64hex-pins.txt record path, basename and hash searches across tests and script readers.

Mutant table

Mutant What it removes or changes Killing test / pattern Result
M01-auth-before-proof proof refusal precedes auth packages/webui/tests/proof.test.mjs / refuses missing wrong short killed
M02-wrong-equal-length same-length incorrect proof refuses packages/webui/tests/proof.test.mjs / refuses missing wrong short killed
M03-missing-proof absent proof refuses packages/webui/tests/proof.test.mjs / refuses missing wrong short killed
M04-loopback-proof proof on nonpublic Host refuses packages/webui/tests/proof.test.mjs / header is refused on other Hosts killed
M05-mode-0644 0644 file refused packages/webui/tests/proof-cli.test.mjs / startup refuses killed
M06-log-refusal refusal log never includes proof packages/webui/tests/proof.test.mjs / refuses missing wrong short killed
M07-no-follow fixture symlink refused packages/webui/tests/proof-cli.test.mjs / startup refuses killed
M08-owner another uid refused packages/webui/tests/proof-cli.test.mjs / startup refuses killed
M09-line-bytes short/newline/bad-byte file refused packages/webui/tests/proof-cli.test.mjs / startup refuses killed
M10-duplicate-proof duplicate header refused packages/webui/tests/proof.test.mjs / refuses missing wrong short killed
M11-tester-proof tester proof header refused packages/webui/tests/proof.test.mjs / header is refused on other Hosts killed
M12-main-wiring actual CLI main listener checks proof packages/webui/tests/proof-cli.test.mjs / CLI applies proof killed
M13-tester-wiring actual CLI tester rejects proof header packages/webui/tests/proof-cli.test.mjs / CLI applies proof killed
M14-service-location render refuses repository/copy path packages/webui/tests/service.test.mjs / proxy proof render refuses killed
M15-service-propagation render carries both flags packages/webui/tests/service.test.mjs / proxy proof render carries killed
M16-release-preservation pointer/unit preserve pair through re-pin and rollback packages/cli/tests/stack-release.test.mjs / T178 proxy proof header killed
M17-render-no-open render never opens or reads the fixture render-no-open.py / inotify probe killed
M18-proof-send-status proof flags retain send-on status packages/webui/tests/service.test.mjs / proxy proof status killed