Skip to content

fix(http): stop leaking internal error details in /mcp 500 responses - #36

Closed
andesyteoss wants to merge 1 commit into
mcpdotdirect:mainfrom
andesyteoss:fix/cwe209-http-server-internal-b265
Closed

fix(http): stop leaking internal error details in /mcp 500 responses#36
andesyteoss wants to merge 1 commit into
mcpdotdirect:mainfrom
andesyteoss:fix/cwe209-http-server-internal-b265

Conversation

@andesyteoss

@andesyteoss andesyteoss commented Jul 10, 2026

Copy link
Copy Markdown

Summary

The HTTP MCP server's three /mcp handlers (POST, GET, DELETE) currently embed the raw error object in the JSON response returned to the client on 500 responses:

res.status(500).json({ error: `Internal server error: ${error}` });

Because ${error} stringifies whatever exception was thrown by the underlying transport, viem, or Node itself, the response can contain stack traces, internal file paths, dependency version fingerprints, RPC endpoint URLs, or partially formed request state. This is a classic CWE-209 (Generation of Error Message Containing Sensitive Information) exposure. The HTTP server binds 0.0.0.0:3001 by default (see startHttpServer in src/server/http-server.ts), so any network-adjacent caller can trigger it with a malformed request.

Fix

Return a static "Internal server error" string to the client and keep the full error in console.error, where operators can still see it. Three-line change, no behavioural change for successful requests, no new dependencies.

res.status(500).json({ error: "Internal server error" });

Full details continue to be logged server-side via the existing console.error(...) calls immediately above each response.

Proof of concept

With the server running (bun run start:http — mounts routes defined in src/server/http-server.ts):

# Malformed JSON body → transport throws → error object interpolated into response
curl -sS -X POST http://127.0.0.1:3001/mcp \
  -H 'Content-Type: application/json' \
  -H 'accept: application/json, text/event-stream' \
  --data-binary '{"jsonrpc":"2.0"'

Before the patch the response body contains a rendered error (e.g. {"error":"Internal server error: SyntaxError: Unexpected end of JSON input\n at ..."}), leaking parser internals and file paths from the bundled node_modules. After the patch the body is {"error":"Internal server error"} and the stack remains only in the server log.

The DELETE /mcp and GET /mcp (SSE) paths have the same pattern and are fixed identically; sending either without a valid mcp-session-id header reaches the same catch block.

Security analysis

  • CWE: 209 — Generation of Error Message Containing Sensitive Information
  • Severity: Low (CVSS ~5.3 AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:N/A:N). Information disclosure only; no code execution or state change.
  • Preconditions: Network reachability to the HTTP MCP port (default 0.0.0.0:3001) and the ability to send a request that causes the handler to throw. No authentication is required at the transport layer.
  • Mitigation provided: Removes the untrusted-length, unstructured error string from the response surface while preserving diagnosability via server logs.

Adversarial review

Before submitting we tried to talk ourselves out of this one. The obvious counter-argument is "the error strings are usually harmless" — but ${error} on a Node Error renders the full stack including absolute filesystem paths from the running host, and viem/StreamableHTTP errors include RPC URLs and request fragments. There is no framework-level filter in front of these responses (the handler writes directly with res.status(500).json(...)), and Express does not sanitize user-supplied error content. The fix is a strict subtraction — no user-visible behaviour changes for successful requests, and operators keep full detail in logs — so we don't see a downside to landing it independently of the larger auth/session work.

Testing

  • bun install and bun run build succeed on the branch.
  • tsc is clean; the change is a pure string replacement with no type impact.
  • Manually reproduced the leak against bun run start:http on main and confirmed the patched build returns the static message while the server log still shows the full stack.

Interpolating the caught Error into the JSON response exposed stack
traces and internal paths to unauthenticated HTTP callers. Return a
generic message and keep full details in server logs only.
@andesyteoss

Copy link
Copy Markdown
Author

Closing this as inactive — no maintainer response after 14 days.

The security finding and fix remain valid. If this is still relevant, I'm happy to reopen, rebase, or re-submit against a different branch. Just drop a comment.

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