fix(security): reject non-local origins, bind loopback by default - #507
Open
nichochar wants to merge 2 commits into
Open
fix(security): reject non-local origins, bind loopback by default#507nichochar wants to merge 2 commits into
nichochar wants to merge 2 commits into
Conversation
nichochar
force-pushed
the
docs/project-memory
branch
from
July 29, 2026 15:24
b0af817 to
ea93a9b
Compare
nichochar
force-pushed
the
fix/network-security
branch
from
July 29, 2026 15:24
01d6839 to
9c1c3f0
Compare
Srcbook has no authentication and can execute arbitrary code, so the only thing separating the user's own tab from any other page they have open is the request origin. Nothing was checking it. Verified before this change, against a running instance: any web page could read every provider API key from GET /api/settings, read arbitrary files from disk via POST /api/file, and — because the websocket accepted any origin — open a socket and drive cell:update + cell:exec to run code as the user. The server also bound 0.0.0.0, so all of it was reachable from the whole network. Changes: - New server/security.mts owns the origin allowlist. Local origins on any port are allowed, plus anything in SRCBOOK_ALLOWED_ORIGINS. Requests with no Origin header are allowed, since browsers always set it on cross-origin requests and non-browser clients (the CLI, curl) legitimately omit it. - HTTP: the 41 per-route cors() calls become one cors + verifyOrigin pair on the router. Disallowed origins get a 403 rather than just a missing CORS header — CORS alone stops an attacker reading a response, but the request still executes, which is all you need for POST /api/settings. - WebSocket: origin checked via verifyClient, so rejection happens at the handshake. Checking in the connection handler is too late; the client observes an open socket that is only torn down afterwards. - Bind 127.0.0.1 unless HOST says otherwise, and warn when it doesn't. This makes docker-compose's HOST variable real — it was previously read by nothing. Compose now sets HOST=0.0.0.0 because Docker forwards published ports to the container IP; host-side exposure stays controlled by HOST_BIND, which already defaulted to loopback. - Guard process.send in the CLI server. It only exists under IPC, so running the server directly crashed it right after printing "running at ..." — which is how this was found while testing the bind change. 12 tests for the allowlist, covering the substring cases that make this kind of check go wrong (localhost.evil.com, example.com.evil.com).
nichochar
force-pushed
the
fix/network-security
branch
from
July 29, 2026 16:48
9c1c3f0 to
191312a
Compare
Contributor
Author
This stack of pull requests is managed by Graphite. Learn more about stacking. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.

Stacked on #506. Closes task 0001.
Srcbook has no authentication and executes arbitrary code, so the request origin is the only thing separating the user's own tab from any other page they have open. Nothing was checking it.
What was possible before this
Verified against a running instance, not theorized:
GET /api/settingsfrom any originAccess-Control-Allow-Origin: *POST /api/filefrom any origincell:update+cell:exec*:2150— the whole network, not just the machineThe websocket is what makes this remote code execution rather than disclosure: a page lists sessions over HTTP, writes a cell, runs it. No interaction beyond visiting the page.
What changed
New
server/security.mtsowns the allowlist — local origins on any port, plus anything inSRCBOOK_ALLOWED_ORIGINS. Both the HTTP and websocket paths use it, so they can't drift.HTTP. The 41 per-route
cors()calls collapse into onecors+verifyOriginpair on the router. Disallowed origins get a 403, not just a missing CORS header — CORS alone stops an attacker reading a response, but the request still executes, which is all you need forPOST /api/settings.WebSocket. Origin is checked in
verifyClient, before the handshake. I first put the check in theconnectionhandler and confirmed it's too late: the server logged the rejection, but the client still sawopenfire and only afterwards got closed.verifyClientgives a 401 at the handshake and no socket at all.Bind.
127.0.0.1unlessHOSTsays otherwise, with a warning when it doesn't. This makes docker-compose'sHOSTvariable real — it was previously read by nothing, while reading as though it controlled exposure. Compose now setsHOST=0.0.0.0, because Docker forwards published ports to the container IP; host-side exposure stays controlled byHOST_BIND, which already defaulted to loopback.Bonus. Guarded
process.sendin the CLI server — it only exists under IPC, sonode dist/src/server.mjscrashed immediately after printingrunning at http://localhost:2150. Found while testing the bind change; it's one line and in a file this PR already touches.Verification
Re-ran the original attacks against the patched build:
12 new tests for the allowlist, including the substring cases that make this kind of check go wrong (
localhost.evil.com,example.com.evil.com). Suite is 23 tests, up from 11. Build, lint, and format all pass.Compatibility
Anyone deliberately reaching Srcbook from another machine now needs
HOSTplus their origin inSRCBOOK_ALLOWED_ORIGINS. That's a real behavior change, henceminorin the changeset, and the whole point — it was previously open to the network by default.Not covered here — task 0002, next in the stack: path traversal in
DELETE /api/srcbooks/:id, shell injection via cell filenames, containingPOST /api/filetoSRCBOOKS_DIR, and validating the settings body.🤖 Generated with Claude Code