|
1 | 1 | --- |
2 | 2 | id: 0001 |
3 | 3 | title: Lock down the network surface (bind, CORS, websocket origin) |
4 | | -status: TODO |
| 4 | +status: DONE |
5 | 5 | created: 2026-07-29 |
6 | 6 | area: api |
7 | 7 | --- |
@@ -31,3 +31,24 @@ See `REVIEW.md` §1 for the reproduction. |
31 | 31 |
|
32 | 32 | Keep the allowlist shared between the HTTP and websocket paths — one source of truth, or they |
33 | 33 | will drift. |
| 34 | + |
| 35 | +Done in `server/security.mts`. Two things worth remembering: |
| 36 | + |
| 37 | +**CORS headers alone are not a fix.** They stop an attacker _reading_ a response, but the |
| 38 | +request still executes — which is all you need for `POST /api/settings`. Disallowed origins |
| 39 | +have to be rejected outright, so there's a `verifyOrigin` middleware alongside the `cors` |
| 40 | +middleware rather than just `cors` configured with an allowlist. |
| 41 | + |
| 42 | +**Checking the origin in the websocket `connection` handler is too late.** `ws` has already |
| 43 | +completed the handshake by then, so the client sees a socket open and only afterwards get |
| 44 | +torn down. Verified this happening before switching to `verifyClient`, which runs before the |
| 45 | +handshake — a rejected client now gets a 401 and never connects. |
| 46 | + |
| 47 | +Requests with no `Origin` header are allowed on purpose: browsers always set it on |
| 48 | +cross-origin requests, so its absence means a non-browser client (the CLI, curl), and |
| 49 | +rejecting those would break `srcbook import`. |
| 50 | + |
| 51 | +`HOST` was already set by `docker-compose.yml` and read by nothing. It's real now — but note |
| 52 | +it has to be `0.0.0.0` _inside_ a container, because Docker forwards published ports to the |
| 53 | +container IP. Host-side exposure is controlled by `HOST_BIND` on the ports line, which is the |
| 54 | +knob that actually matters there. |
0 commit comments