fix(board): refuse foreign Host and Origin on every control-board route (#1507)

After a DNS rebind, a web page could read /api/board and POST /api/reply,
which pastes into a live seat pane. foreignRequest() now runs first and
returns 403 for a non-loopback Host, a wrong port, userinfo or a path in
Host, or any Origin other than http://<Host>. A missing Origin still passes,
which covers the WebUI proxy. Dewey authored it; Rocko approved de9ff942
(review 5a12f08e) with one low wording finding, now fixed in the notes.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
2026-09-26 15:40:34 -05:00
co-authored by Claude Opus 5.5
parent 401cc850bb
commit d1629d610d
8 changed files with 461 additions and 6 deletions
+17
View File
@@ -2980,3 +2980,20 @@ session transcript shows the real times: 20:19:22Z (tokens), 20:19:52Z
wrong times as well. None of the rulings changed. Rocko's 6b R1 verdict
(request changes) is noted under lead decision item 9, and the Discord
restart stays held.
## 2026-09-26: control board Host and Origin guard (Dewey, reviewed by Rocko)
Before: `packages/control-board/src/serve.mjs` checked only its bind address.
After a DNS rebind, a web page could read `/api/board` and POST `/api/reply`,
which pastes text into a live seat pane. This was never tested in a browser.
After: `foreignRequest(req)` runs first on every route and returns 403 with a
fixed error body. It refuses a Host that isn't a loopback name on the
connection's port, a Host carrying userinfo or a path, and any Origin other
than `http://<Host>`, including `null` and an empty value. A request with no
Origin still passes, which is what the WebUI proxy sends. Tests are in section
11 of `serve.test.mjs`. HEAD fails them, and six mutations each fail at their
own case. Rocko approved pin de9ff942 (serve d0a9bbed, tests e01d7bd9) and
captured the proxy's real headers: Host 127.0.0.1:<port>, no Origin. One low
finding is open: the notes' wording on raw Host syntax is broader than the
code (see DEFERRED Done). The running board keeps the old code until Sage
restarts it.