fix(security): bind the HTTP/SSE surface to loopback and authenticate remote access#55
Merged
Merged
Conversation
… remote access Closes #54. `POST /sse` hands its body straight to handleMCPRequest, which drives the browser through the extension, and it was reachable two ways it should not have been: the Express app listened on all interfaces, and CORS answered every preflight with `Access-Control-Allow-Origin: *`. So any LAN peer could call it, and any website the user visited could call it cross-origin and read the reply. #53 closed the equivalent hole on the WebSocket channel; this is the other half. Three changes, mirroring what #53 established: - Bind the HTTP server to 127.0.0.1. `ngrok http` runs on this machine and forwards to localhost, and the extension fetches /ports from localhost, so nothing legitimate needed the wider bind. --http-host= widens it deliberately. - Gate every request on Origin. Browsers send Origin cross-origin, so page requests (http(s), and "null" from a sandbox) are refused 403 while extension service workers and non-browser MCP clients pass. CORS now reflects only allowed origins instead of `*`, so a page cannot read a response either. - Require a bearer token once the surface leaves this machine — --tunnel or a non-loopback --http-host. Generated and printed at startup, or pinned with --token=. The tunnel was previously unauthenticated to anyone who had the URL. Verified against a running server. Default (local) mode: bind address 127.0.0.1:5556 (was *:5556) LAN POST /sse ECONNREFUSED loopback, no Origin 200 loopback, Origin https://evil 403 loopback, simple req, no preflight 403 loopback, Origin null 403 GET /ports, chrome-extension:// 200 preflight from evil page 403, no ACAO header preflight from extension 204, ACAO reflects the extension With --http-host=0.0.0.0: LAN POST /sse, no token 401 LAN POST /sse, wrong token 401 LAN POST /sse, token prefix 401 LAN POST /sse, correct token 200 LAN POST /sse, token + evil Origin 403 The WebSocket channel stays on loopback regardless of --http-host. Co-Authored-By: Claude <noreply@anthropic.com>
aaronjmars
added a commit
that referenced
this pull request
Jul 20, 2026
…changes (#57) * docs: add a changelog and a release checklist that surfaces breaking changes #55 made `--tunnel` require a bearer token on /sse. That is a real break for anyone running a tunnelled connector today — it will present as "my ChatGPT connector stopped working" the moment they upgrade — and right now nothing in the repo would remind us to say so at tag time. Release notes are hand-written per release, and `.github/workflows/publish.yml` pushes to npm on a `v*` tag, so the tag is the point of no return. Add `.github/CHANGELOG.md`, seeded with an Unreleased section covering everything landed since v1.1.1 (13 commits): the tunnel token under⚠️ Breaking with the migration line spelled out, the three security fixes (#53, #55, #56), the node-forge/brace-expansion bump (#43), and the dependency and docs maintenance. Add a Releasing section to CONTRIBUTING that makes reading Unreleased step 1 of cutting a tag, and states that a⚠️ Breaking entry means a major bump with the migration step at the top of the release notes. Also ask PR authors to add their own entry, so the file stays accurate instead of being reconstructed later. No code changes. Co-Authored-By: Claude <noreply@anthropic.com> * docs: link the adm-zip override to its removal issue (#58) --------- Co-authored-by: Claude <noreply@anthropic.com>
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.
Closes #54.
POST /ssehands its body straight to handleMCPRequest, which drives thebrowser through the extension, and it was reachable two ways it should not have
been: the Express app listened on all interfaces, and CORS answered every
preflight with
Access-Control-Allow-Origin: *. So any LAN peer could call it,and any website the user visited could call it cross-origin and read the reply.
#53 closed the equivalent hole on the WebSocket channel; this is the other half.
Three changes, mirroring what #53 established:
ngrok httpruns on this machine andforwards to localhost, and the extension fetches /ports from localhost, so
nothing legitimate needed the wider bind. --http-host= widens it deliberately.
requests (http(s), and "null" from a sandbox) are refused 403 while extension
service workers and non-browser MCP clients pass. CORS now reflects only
allowed origins instead of
*, so a page cannot read a response either.non-loopback --http-host. Generated and printed at startup, or pinned with
--token=. The tunnel was previously unauthenticated to anyone who had the URL.
Verified against a running server. Default (local) mode:
bind address 127.0.0.1:5556 (was *:5556)
LAN POST /sse ECONNREFUSED
loopback, no Origin 200
loopback, Origin https://evil 403
loopback, simple req, no preflight 403
loopback, Origin null 403
GET /ports, chrome-extension:// 200
preflight from evil page 403, no ACAO header
preflight from extension 204, ACAO reflects the extension
With --http-host=0.0.0.0:
LAN POST /sse, no token 401
LAN POST /sse, wrong token 401
LAN POST /sse, token prefix 401
LAN POST /sse, correct token 200
LAN POST /sse, token + evil Origin 403
The WebSocket channel stays on loopback regardless of --http-host.
Co-Authored-By: Claude noreply@anthropic.com