fix: reject requests with missing Origin header in origin validation middleware (CWE-346)#1161
Conversation
…middleware The originValidationMiddleware only checked whether a present Origin header matched the allowlist. When the Origin header was absent (as with curl, scripts, or any non-browser HTTP client), the check was skipped entirely, allowing unauthenticated access to all protected endpoints. Changed the condition from `if (origin && !allowedOrigins.includes(origin))` to `if (!origin || !allowedOrigins.includes(origin))` so that requests without an Origin header are also rejected with 403 Forbidden. This prevents non-browser CSRF and unauthorized access from tools that do not send an Origin header, which is especially critical when combined with DANGEROUSLY_OMIT_AUTH=true. CWE-346: Origin Validation Error
travisbreaks
left a comment
There was a problem hiding this comment.
Good catch on the fail-open to fail-closed flip. A few observations:
-
Missing Origin vs mismatched Origin: The current implementation rejects both the same way, but the security implications differ. A missing
Originheader could be a legitimate non-browser client (curl, Postman, server-to-server), while a mismatched Origin is almost always a cross-origin attack. Worth considering whether the middleware should return different status codes or error messages to help operators debug, or if treating both as hostile is the intended posture whenDANGEROUSLY_OMIT_AUTH=true. -
Same-origin navigations: Browsers omit
Originon same-origin GET requests and some redirects (per Fetch spec,Originis only sent for CORS requests and POST). If the inspector serves any HTML pages that make same-origin fetches, this could break them. Worth verifying the middleware only applies to the API routes, not static asset serving. -
Test coverage: Does the test suite cover the rejection path for missing Origin? If not, a simple test asserting a 403 (or whatever status) when no Origin header is present would solidify this.
Looks correct to me overall. The fail-closed default is the right call for a tool that explicitly opts out of auth.
Vulnerability Summary
CWE: CWE-346 — Origin Validation Error
Severity: High (when auth is disabled); Low (when auth is enabled, the default)
Affected file:
server/src/index.ts,originValidationMiddleware(line ~206)Data Flow
The
originValidationMiddlewareis applied to 7 routes (/mcp,/stdio,/sse,/message,/config) and runs beforeauthMiddleware. The current condition:uses a fail-open pattern — when the
Originheader is absent (i.e.undefined), the entire check is skipped andnext()is called, allowing the request through without origin validation.This is exploitable via DNS rebinding: same-origin requests from a rebound domain do not include an
Originheader, so the middleware passes them through. When combined withDANGEROUSLY_OMIT_AUTH=true(a documented configuration option), the/stdioendpoint can be reached, which spawns local processes viaStdioClientTransport— leading to remote code execution.Exploit Sketch (DNS Rebinding + Auth Disabled)
DANGEROUSLY_OMIT_AUTH=true npx @modelcontextprotocol/inspectorevil.comin their browserevil.comresolves to127.0.0.1after TTL expiresevil.com:6277/stdio?transportType=stdio&command=bash&args=-c%20idif (undefined && ...) → false→next()→ request passesauthMiddlewarecallsnext()bash -c "id"→ RCEPreconditions
DANGEROUSLY_OMIT_AUTH=true(documented in README)When auth is enabled (default), the origin bypass alone is not independently exploitable because the 256-bit random auth token cannot be guessed.
Fix Description
One-line change — converts fail-open to fail-closed:
Rationale
Origin→ condition isfalse→ request passes (fail-open)Origin→!originistrue→ request is rejected with 403 (fail-closed)Originheader continue to work exactly as before15ecb59by Felix Weinberger)Test Results
The fix was validated by tracing the logic for all three cases:
http://localhost:6274)http://evil.com)undefined)The change is minimal (1 line, 1 file) and does not alter any other behavior.
Disprove Analysis
We systematically attempted to disprove the finding across 9 dimensions:
Auth Check
Strong auth exists:
authMiddlewarerequires aX-MCP-Proxy-Auth: Bearer <token>header with a 256-bit random token verified viatimingSafeEqual. However, auth can be disabled withDANGEROUSLY_OMIT_AUTH=true, a documented and actively used option.Network Check
Server binds to
localhostby default, limiting direct network access. However, DNS rebinding bypasses localhost binding — that is precisely whatoriginValidationMiddlewarewas designed to prevent. Docker usage withHOST=0.0.0.0(documented in README, discussed in issue #639) further exposes the server.Deployment Context
Dockerfile exists. The Docker example in README uses
-e HOST=0.0.0.0. The proxy server can spawn local processes via the/stdioendpoint.Caller Trace
originValidationMiddlewareis applied to all 7 protected routes, always beforeauthMiddleware. The/stdioendpoint callscreateTransport()which can spawn arbitrary local processes.Prior Reports
HOST=0.0.0.0encountering origin-related errorsCommit History
The
originValidationMiddlewarewas added in commit15ecb59with the explicit purpose of preventing DNS rebinding. The buggyif (origin && ...)logic was present from the very first commit of this middleware.Mitigations Found
cors()uses permissive defaults (Access-Control-Allow-Origin: *) — does not help; noted as an issue in open PR server: restrict default CORS to allowed origins #1074Fix Adequacy
The fix changes the only origin validation point. When auth is disabled, origin validation is the sole defense against browser-based attacks. No parallel path provides equivalent protection.
Verdict
CONFIRMED_VALID — High confidence. The vulnerability is a clear fail-open logic error. The fix is minimal, correct, and directly addresses the root cause.
Related
Disclosure: This issue was identified through automated security analysis. The project's
SECURITY.mdrequests disclosure through GitHub Security Advisories; however, this is a one-line logic fix for a publicly visible code pattern, and a PR enables transparent review by maintainers.