fix(server): sanitize error responses to prevent stack trace exposure#1187
Conversation
Replace raw error objects passed to res.json() with generic sanitized messages so internal error details are not leaked to clients. Full errors continue to be logged server-side via console.error. Also adds a missing return in the /sse ECONNREFUSED branch, which previously fell through and attempted a second response after headers had already been sent. Resolves CodeQL js/stack-trace-exposure alerts in server/src/index.ts. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
This PR addresses CodeQL js/stack-trace-exposure findings in the server by ensuring Express routes no longer serialize raw caught Error objects into HTTP JSON responses, reducing the risk of leaking stack traces/internal details to clients.
Changes:
- Added a
sendErrorResponse(res, status, message)helper to return sanitized{ error: "<message>" }responses. - Replaced multiple
res.status(...).json(error)call sites across/mcp,/stdio,/sse,/message, and/configwith sanitized responses. - Fixed a
/sseerror-path fallthrough by adding a missingreturnafter responding in theECONNREFUSEDbranch.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
|
Nice work — this is a clean, well-scoped security fix. The 1.
|
- Sanitize /fetch route catch block: previously leaked error.message to
clients and silently swallowed errors with no server-side logging.
Now logs via console.error and returns a generic sanitized response.
- Replace JSON.stringify(error).includes("ECONNREFUSED") in the /sse
handler with a check against error.message and String(error.cause).
Error fields are non-enumerable so JSON.stringify(new Error(...))
produces "{}" — the old check only worked by accident when the error
happened to carry enumerable properties.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
|
Thanks @olaservo — both great catches. Addressed in f08dc65: 1.
|
olaservo
left a comment
There was a problem hiding this comment.
Both feedback items addressed cleanly in f08dc65:
/fetchroute now logs viaconsole.errorand returns a sanitized response instead of leakingerror.message.ECONNREFUSEDdetection replaced with propererror instanceof Errorcheck againsterror.messageandString(error.cause)— no more fragileJSON.stringify(error).
Verified that all remaining error.message usages are server-side only (boolean checks and console.error calls). CI green. LGTM.
This review was conducted with assistance from Claude Code.
Summary
Resolves the 11 open CodeQL
js/stack-trace-exposurealerts inserver/src/index.ts. Each was a spot where a raw caughterrorobject was handed tores.json(...), which can leak internal error details (including stack-trace-derived data) to clients.The fix introduces a small
sendErrorResponse(res, status, message)helper that returns a generic sanitized JSON body, and replaces every flagged call site with it. Full error objects continue to be logged server-side via the existingconsole.errorcalls, so debuggability is unchanged.Alerts addressed
sendProxiedUnauthorizedfallback)Incidental fix
The
/ssehandler'sECONNREFUSEDbranch was missing areturn, so after sending its 500 it fell through to the genericError in /sse routebranch and attempted a second response on the already-sent headers. Added the missingreturnsince the sanitization pass touched those lines anyway.Behavior changes
Errorobjects on 4xx/5xx failures from/mcp,/stdio,/sse,/message, or/config. They now receive{ "error": "<generic message>" }.sendProxiedUnauthorizedno longer takes anerrorparameter — the upstream-capture path was already ignoring it, and the fallback path now returns a plain{ error: "Unauthorized" }instead of JSON-encoding the caught exception.console.errorbefore responding.Test plan
npm run build-serverpasses (tsc)npm run prettier-fixclean/mcpto an unreachable URL) returns{ "error": "Internal server error" }rather than a serialized exception, and thatconsole.errorstill logs full details server-sideWWW-Authenticateheader and body (the non-fallback path insendProxiedUnauthorized)🤖 Generated with Claude Code