Skip to content

Commit 01d6839

Browse files
nichocharclaude
andcommitted
chore: add changeset and close task 0001
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
1 parent baa1ea6 commit 01d6839

2 files changed

Lines changed: 41 additions & 1 deletion

File tree

‎.changeset/quiet-moons-shave.md‎

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
---
2+
'@srcbook/api': minor
3+
'srcbook': minor
4+
---
5+
6+
Reject requests and websocket connections from non-local origins, and bind to loopback by
7+
default.
8+
9+
Srcbook has no authentication and executes code, so any web page the user visited could
10+
previously read their provider API keys, read files off disk, and run arbitrary code through
11+
the websocket. The server also listened on every network interface.
12+
13+
Requests now need a local `Origin` (any port), or one listed in the new
14+
`SRCBOOK_ALLOWED_ORIGINS` environment variable. Requests without an `Origin` header — the CLI,
15+
curl, same-origin navigations — are unaffected.
16+
17+
To reach Srcbook from another machine, set `HOST` explicitly (for example `HOST=0.0.0.0`) and
18+
add the origin you'll browse from to `SRCBOOK_ALLOWED_ORIGINS`. Only do this on a network you
19+
trust.

‎tasks/0001-lock-down-network-surface.md‎

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
---
22
id: 0001
33
title: Lock down the network surface (bind, CORS, websocket origin)
4-
status: TODO
4+
status: DONE
55
created: 2026-07-29
66
area: api
77
---
@@ -31,3 +31,24 @@ See `REVIEW.md` §1 for the reproduction.
3131

3232
Keep the allowlist shared between the HTTP and websocket paths — one source of truth, or they
3333
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

Comments
 (0)