Group U specifies the Console's loopback bind (FR-U02, docs/design/srs.md:739-741) and its per-run token (FR-U04, srs.md:745-748), but three further controls that src/ui/server.ts implements and that CI exercises have no requirement at any authority level:
Host-header allowlist. src/ui/server.ts:630-634 refuses any request whose Host is not exactly 127.0.0.1:<boundPort> or localhost:<boundPort>, with a 403 evaluated before the token check. This is the DNS-rebinding guard — the control that stops a page in the Operator's own browser from driving the port through a hostname the attacker resolves to 127.0.0.1. Loopback binding (FR-U02) does not cover this case.
Cache-Control: no-store on every response. respond() (src/ui/server.ts:233), respondMethodNotAllowed() (:242-247), and the SSE stream (:460-463). Relevant because the startup URL carries the token (src/ui/index.ts:112).
- Constant-time token comparison.
tokenEquals (src/ui/server.ts:192-200) uses timingSafeEqual and treats a length mismatch as an ordinary miss rather than an early return. FR-U04 requires the token, not that comparing it leaks no timing.
grep -ni 'host header\|host-header\|rebind\|no-store\|allowlist\|constant.time\|timing.safe' docs/design/srs.md docs/adr/0012-optional-local-ui-server.md returns zero hits, so none of the three is traceable from either the SRS or the governing ADR.
Found while implementing #31 (PR #69), which documents all three in docs/design/security.md. That PR deliberately describes them as implementation controls with no FR-* citation rather than inventing ids, since the routine may not add or renumber requirements — hence this issue.
Impact: each is a load-bearing security control with nothing above it in the authority order. A refactor that dropped the Host check would contradict no requirement, and docs/design/security.md would then describe a guard the code no longer has — the exact drift class docs/README.md's authority order exists to prevent. The traceability appendix cannot grade what it cannot see either: Appendix E's group-U row (srs.md:1377) covers FR-U01–FR-U38 and there is no id here to grade.
Note this is a traceability gap, not a vulnerability — all three controls are present, correct, and covered by tests in tests/integration/ui-server.test.ts. Nothing is exposed today.
Acceptance: the SRS gains requirements for the Host allowlist (stating that it is checked before the token and that its purpose is DNS rebinding), for Cache-Control: no-store on every Console response, and for constant-time token comparison — each with a *Verify:* pointer to the test that already covers it. The group-U id-range statements at srs.md:1322 and srs.md:1377 move with them, and docs/design/security.md's ### Console network exposure section is updated to cite the new ids. Whether ADR-0012 should also be amended, or whether the SRS alone suffices, is a judgment call for the implementer — ADR-0012 enumerates the Console's actions, not its transport controls.
Group U specifies the Console's loopback bind (FR-U02,
docs/design/srs.md:739-741) and its per-run token (FR-U04,srs.md:745-748), but three further controls thatsrc/ui/server.tsimplements and that CI exercises have no requirement at any authority level:Host-header allowlist.src/ui/server.ts:630-634refuses any request whoseHostis not exactly127.0.0.1:<boundPort>orlocalhost:<boundPort>, with a 403 evaluated before the token check. This is the DNS-rebinding guard — the control that stops a page in the Operator's own browser from driving the port through a hostname the attacker resolves to127.0.0.1. Loopback binding (FR-U02) does not cover this case.Cache-Control: no-storeon every response.respond()(src/ui/server.ts:233),respondMethodNotAllowed()(:242-247), and the SSE stream (:460-463). Relevant because the startup URL carries the token (src/ui/index.ts:112).tokenEquals(src/ui/server.ts:192-200) usestimingSafeEqualand treats a length mismatch as an ordinary miss rather than an early return. FR-U04 requires the token, not that comparing it leaks no timing.grep -ni 'host header\|host-header\|rebind\|no-store\|allowlist\|constant.time\|timing.safe' docs/design/srs.md docs/adr/0012-optional-local-ui-server.mdreturns zero hits, so none of the three is traceable from either the SRS or the governing ADR.Found while implementing #31 (PR #69), which documents all three in
docs/design/security.md. That PR deliberately describes them as implementation controls with noFR-*citation rather than inventing ids, since the routine may not add or renumber requirements — hence this issue.Impact: each is a load-bearing security control with nothing above it in the authority order. A refactor that dropped the
Hostcheck would contradict no requirement, anddocs/design/security.mdwould then describe a guard the code no longer has — the exact drift classdocs/README.md's authority order exists to prevent. The traceability appendix cannot grade what it cannot see either: Appendix E's group-U row (srs.md:1377) coversFR-U01–FR-U38and there is no id here to grade.Note this is a traceability gap, not a vulnerability — all three controls are present, correct, and covered by tests in
tests/integration/ui-server.test.ts. Nothing is exposed today.Acceptance: the SRS gains requirements for the
Hostallowlist (stating that it is checked before the token and that its purpose is DNS rebinding), forCache-Control: no-storeon every Console response, and for constant-time token comparison — each with a*Verify:*pointer to the test that already covers it. The group-U id-range statements atsrs.md:1322andsrs.md:1377move with them, anddocs/design/security.md's### Console network exposuresection is updated to cite the new ids. Whether ADR-0012 should also be amended, or whether the SRS alone suffices, is a judgment call for the implementer — ADR-0012 enumerates the Console's actions, not its transport controls.