Skip to content

Harden the hosted worker: no operator-key fallback, browser-bound OAuth state - #37

Merged
rafalzawadzki merged 2 commits into
mainfrom
sec/hardening
Oct 27, 2026
Merged

rafalzawadzki merged 2 commits into
mainfrom
sec/hardening

Conversation

@rafalzawadzki

@rafalzawadzki rafalzawadzki commented Oct 25, 2026 •

Copy link
Copy Markdown
Contributor

Summary

  • Operator key fallback removed (src/worker.ts). If the SUPADATA_API_KEY worker secret was set, a POST /mcp with no API-key header ran every tool on that key. OAuth Bearer requests took the same branch, so their tokens were never validated. Header-less requests now always go through OAuth. CLAUDE.md now says the variable is for stdio only.
  • Job ids stay in their path segment (src/mcp.ts). The three *_check_*_status tools put the id into the URL without encoding it, so ../x, x?y=1 or ..\x could send the authenticated GET to another API path. Ids are now encodeURIComponent-ed, and ./.. are refused.
  • OAuth state is bound to the browser (src/auth-handler.ts). /oauth/authorize stores a random binding value with the state and sets it in a __Host-mcp_auth_<state> cookie (HttpOnly, Secure, SameSite=Lax, 1 h). /oauth/callback requires that cookie (constant-time compare) before it consumes the state. A state someone else started can't be completed from a victim's browser. The dashboard redirect now also carries redirect_uri (the client's registered redirect), so the consent screen can say where the grant goes. The flow itself is unchanged.
  • Release bound to main (publish.yml). The job has if: github.ref == 'refs/heads/main' and environment: release.
  • Signed hand-off hints. The dashboard URL also carries sig = hex HMAC-SHA256(MCP_JWT_SECRET, state\nredirect_uri\nclient), with client '' when absent. The dashboard can verify it before showing the redirect host and client name.

Owner follow-up

  • GitHub → Settings → Environments → release (created on the first run): restrict deployment branches to main.
  • npm → @supadata/mcp → Trusted publishing: set environment release. Without this, a branch copy of the workflow still matches the publisher.
  • After deploying, delete the worker secret if it is set (wrangler secret delete SUPADATA_API_KEY).
  • Dashboard: show the redirect_uri host on the MCP consent screen, and keep pinning callback to the worker origin.

Tests

  • npm test (111 passed), tsc --noEmit, eslint clean. New tests: env key never used (with and without a Bearer token), job-id traversal cases for all three status tools, and a callback with a missing or forged binding cookie is refused without consuming the state.

🤖 Generated with Claude Code

…th state

- POST /mcp without an API-key header no longer runs tools with the
  SUPADATA_API_KEY worker secret, and OAuth Bearer requests no longer skip
  token validation through that branch.
- The status tools encode the job id as a single path segment and refuse
  dot segments, so an id can't send the authenticated GET to another path.
- /oauth/authorize sets a __Host- cookie with a random binding value stored
  next to the state; /oauth/callback requires it (constant-time compare)
  before it consumes the state. The dashboard also receives the client's
  redirect_uri so its consent screen can show where the grant goes.
- The release job only runs on main and runs in a `release` environment.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@cloudflare-workers-and-pages

cloudflare-workers-and-pages Bot commented Oct 25, 2026 •

Copy link
Copy Markdown

Deploying with  Cloudflare Workers  Cloudflare Workers

The latest updates on your project. Learn more about integrating Git with Workers.

Status Name Latest Commit Updated (UTC)
✅ Deployment successful!
View logs
supadata-mcp 0029e02 Oct 25 2026, 08:16 PM

The dashboard URL now carries sig = hex HMAC-SHA256(MCP_JWT_SECRET,
state + "\n" + redirect_uri + "\n" + client), so the consent screen can
verify the redirect host and client name it shows.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@rafalzawadzki
rafalzawadzki merged commit c46415a into main Oct 27, 2026
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant