Skip to content

security: stop sending session token in SSE URL query params#1980

Open
sebastiondev wants to merge 1 commit intoMCPJam:mainfrom
sebastiondev:fix/cwe200-session-auth-token-daaa
Open

security: stop sending session token in SSE URL query params#1980
sebastiondev wants to merge 1 commit intoMCPJam:mainfrom
sebastiondev:fix/cwe200-session-auth-token-daaa

Conversation

@sebastiondev
Copy link
Copy Markdown

Summary

The inspector's session token is currently appended to SSE/EventSource URLs as a ?_token=<token> query parameter. URL-embedded credentials are well known to leak into:

  • Browser history
  • HTTP Referer headers if the SSE-loaded page navigates externally
  • Reverse-proxy and server access logs (which routinely record full request URLs)

This is a CWE-200 / CWE-598 (Information Exposure Through Query Strings in URL) hardening issue. Severity is modest — the inspector binds to localhost by default and origin validation blocks cross-origin requests — but the leak path is real and the token has no visible expiry, so anyone with retrospective access to logs or browser history can replay it.

Affected files (before fix):

  • mcpjam-inspector/client/src/lib/session-token.tsaddTokenToUrl() appended ?_token=<token> to every EventSource URL
  • mcpjam-inspector/server/middleware/session-auth.tssessionAuthMiddleware accepted c.req.query("_token") as a credential source

Data flow: __MCP_SESSION_TOKEN__getSessionToken()addTokenToUrl()new EventSource(url) → URL recorded in browser history / server logs.

Fix

Replace the URL query parameter with a same-origin cookie scoped to the API:

  • New helper setEventSourceAuthCookie(token) sets mcp_session_auth=<token>; Path=/api/; SameSite=Strict (and Secure when served over HTTPS). It is called from initializeSessionToken() and getSessionToken() so the cookie is in place before any EventSource connection is opened.
  • addTokenToUrl() no longer mutates the URL — it just ensures the cookie is set and returns the URL untouched.
  • sessionAuthMiddleware now reads the token from the Cookie header instead of ?_token=. The X-MCP-Session-Auth: Bearer <token> header path used by fetch() callers is unchanged.
  • The scrubTokenFromUrl() regex is left in place to redact any pre-existing log lines (defense-in-depth for historical logs).
  • Error hints returned to clients updated to point at the cookie rather than the query parameter.

Why a cookie and not just headers?

EventSource does not allow custom headers, which is exactly why the original code used a URL parameter. A same-origin cookie is the standard alternative recommended for this case. SameSite=Strict + Path=/api/ + the existing origin-validation middleware closes the typical CSRF concern for cookie-based auth on a localhost developer tool.

Tests

Updated tests reflect the new behavior:

  • client/src/lib/__tests__/session-token.test.tsaddTokenToUrl no longer adds ?_token=, and a new test asserts the auth cookie is set with the expected attributes.
  • server/middleware/__tests__/session-auth.test.ts — accepts cookie-based auth, rejects missing-token requests with the new hint string.
  • server/__tests__/auth-integration.test.ts and server/routes/mcp/__tests__/http-adapters.test.ts — SSE happy-path tests now send Cookie: mcp_session_auth=... instead of the query parameter.

I ran the affected vitest suites locally and they pass. No production code path still consumes _token (verified with grep across mcpjam-inspector/); the only remaining reference is the legacy log scrubber regex.

Security analysis

  • Exploitability: A user opens the inspector (default http://127.0.0.1:6274). Any SSE connection made by the UI persists ?_token=<token> in browser history. If the same machine has a browsing-history sync extension, screen-recording / observability agent, or a reverse proxy in front (some Docker users do this), the token is exposed at rest. The token alone is sufficient to impersonate the session against the API.
  • Preconditions: local-mode deployment (default), and either (a) read access to access logs / history, or (b) a referrer leak triggered by clicking an external link from an inspector page. None of these preconditions already grant API access — the token is the missing piece.
  • Mitigations the fix provides: removes the leak vector entirely. The cookie is not exposed in URLs, is SameSite=Strict (no cross-site delivery), Secure over HTTPS, and scoped to Path=/api/ (not sent to static assets). The X-MCP-Session-Auth header path is unchanged so fetch callers are unaffected.

Adversarial review

Before submitting, I tried to disprove this finding. The two most credible objections are: (1) "the inspector binds to localhost so logs aren't exposed," and (2) "origin validation already prevents abuse." Neither fully closes the gap — localhost binding doesn't prevent a local proxy, screen recorder, or browser-history sync from capturing the URL, and origin validation doesn't help once the token has already been read out of a log file. The fix is small, behavior-preserving for the header path, and removes a textbook URL-credential anti-pattern.

cc @lewiswigmore

@chatgpt-codex-connector
Copy link
Copy Markdown

Codex usage limits have been reached for code reviews. Please check with the admins of this repo to increase the limits by adding credits.
Credits must be used to enable repository wide code reviews.

@dosubot dosubot Bot added the size:M This PR changes 30-99 lines, ignoring generated files. label Apr 30, 2026
@chelojimenez
Copy link
Copy Markdown
Contributor

Snyk checks have passed. No issues have been found so far.

Status Scan Engine Critical High Medium Low Total (0)
Open Source Security 0 0 0 0 0 issues

💻 Catch issues earlier using the plugins for VS Code, JetBrains IDEs, Visual Studio, and Eclipse.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Apr 30, 2026

Walkthrough

This pull request transitions authentication for Server-Sent Events (SSE) endpoints from URL-based query parameters (_token) to cookie-based authentication (mcp_session_auth). The client-side code now sets an authentication cookie via a new setEventSourceAuthCookie function when the session token is available, while the addTokenToUrl helper is refactored to return URLs unchanged. The server middleware and route handlers are updated to extract and validate the session token from cookies instead of query parameters. Tests across client and server are updated to reflect this authentication mechanism shift.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share
Review rate limit: 7/8 reviews remaining, refill in 7 minutes and 30 seconds.

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@mcpjam-inspector/server/middleware/session-auth.ts`:
- Around line 117-122: The cookie extraction for the session token assigns to
token but doesn't URL-decode it; update the logic that builds token (the
expression that finds "mcp_session_auth=" and calls substring) to pass the found
value through decodeURIComponent (e.g., decodeURIComponent(foundValue)) and
handle potential decode errors (try/catch or a safe decode helper) so the token
used by the session validation code (the token variable used downstream) matches
the client-side encodeURIComponent() from session-token.ts.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 152047cc-ce62-4a2c-a800-9755b3387064

📥 Commits

Reviewing files that changed from the base of the PR and between f049d4e and 77a798e.

📒 Files selected for processing (6)
  • mcpjam-inspector/client/src/lib/__tests__/session-token.test.ts
  • mcpjam-inspector/client/src/lib/session-token.ts
  • mcpjam-inspector/server/__tests__/auth-integration.test.ts
  • mcpjam-inspector/server/middleware/__tests__/session-auth.test.ts
  • mcpjam-inspector/server/middleware/session-auth.ts
  • mcpjam-inspector/server/routes/mcp/__tests__/http-adapters.test.ts

Comment on lines +117 to +122
token = c.req
.header("Cookie")
?.split(";")
.map((part) => part.trim())
.find((part) => part.startsWith("mcp_session_auth="))
?.substring("mcp_session_auth=".length);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Missing URL decoding for cookie token value.

The client encodes the token with encodeURIComponent() (see session-token.ts:192-193), but this extraction does not decode it. While session tokens are typically alphanumeric and thus unaffected, this asymmetry could cause validation failures if tokens contain characters like + or =.

🛡️ Proposed fix to decode the cookie value
   if (!token) {
-    token = c.req
+    const rawCookie = c.req
       .header("Cookie")
       ?.split(";")
       .map((part) => part.trim())
-      .find((part) => part.startsWith("mcp_session_auth="))
-      ?.substring("mcp_session_auth=".length);
+      .find((part) => part.startsWith("mcp_session_auth="));
+    if (rawCookie) {
+      try {
+        token = decodeURIComponent(rawCookie.substring("mcp_session_auth=".length));
+      } catch {
+        // Malformed encoding - leave token undefined, will fail validation
+      }
+    }
   }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
token = c.req
.header("Cookie")
?.split(";")
.map((part) => part.trim())
.find((part) => part.startsWith("mcp_session_auth="))
?.substring("mcp_session_auth=".length);
if (!token) {
const rawCookie = c.req
.header("Cookie")
?.split(";")
.map((part) => part.trim())
.find((part) => part.startsWith("mcp_session_auth="));
if (rawCookie) {
try {
token = decodeURIComponent(rawCookie.substring("mcp_session_auth=".length));
} catch {
// Malformed encoding - leave token undefined, will fail validation
}
}
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@mcpjam-inspector/server/middleware/session-auth.ts` around lines 117 - 122,
The cookie extraction for the session token assigns to token but doesn't
URL-decode it; update the logic that builds token (the expression that finds
"mcp_session_auth=" and calls substring) to pass the found value through
decodeURIComponent (e.g., decodeURIComponent(foundValue)) and handle potential
decode errors (try/catch or a safe decode helper) so the token used by the
session validation code (the token variable used downstream) matches the
client-side encodeURIComponent() from session-token.ts.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:M This PR changes 30-99 lines, ignoring generated files.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants