Stack traces in error responses
Express delegates unhandled errors to finalhandler, whose documented behaviour is
unambiguous:
The body will be the HTML of the status code message if
envis'production', otherwise will beerr.stack.
And from Express’s own lib/application.js:
var env = process.env.NODE_ENV || 'development';So the default is development, and the default behaviour on an unhandled error is
to send the stack trace to whoever triggered it. This is Express’s analogue of
Django’s DEBUG, with one difference worth knowing: Express leaks less — a stack
trace, not the settings object and local variables. It is still absolute paths,
dependency names and versions, and a map of your internal structure.
NODE_ENV=production node server.jsThe value must be exactly production. Production, prod, and an unset variable
all leave you in development mode, and none of them warn.
Add an error handler so you control the response rather than relying on the absence of a leak:
// last middleware — four arguments is what marks it as an error handlerapp.use((err, req, res, _next) => { req.log?.error({ err }); // full detail server-side res.status(err.status || 500).json({ error: "Internal Server Error" });});The four-argument signature is load-bearing. An error handler written with three parameters is registered as ordinary middleware, never receives the error, and the default handler runs instead — silently.
curl -s https://example.com/route-that-throws | head -5You want the status text, not a stack. Test a route that genuinely throws rather than a 404, because 404s take a different path and will look fine either way.
Setting NODE_ENV=production does more than change error output. npm install skips
devDependencies, Express enables view caching, and some libraries change behaviour on
the same variable. That is all desirable in production and surprising if you set it
locally to test this one behaviour.
If your platform sets NODE_ENV for you, check what it actually sets rather than
assuming — this is a common source of “it leaked in staging but not production”.
NODE_ENVenvExpress reads process.env.NODE_ENV or 'development', and the default error handler sends the stack trace in every environment except production. So an unset variable leaks stack traces, and the leak is invisible in local development because it looks identical there.
- accepts
"production"|<anything else>- default
unset — Express falls back to "development"- set in
process environment
Error: Cannot set headers after they are sent to the clientThe response was already sent when the error handler tried to send another. Usually a handler that responds and then calls next(err), or one that forgets to return after responding — the original response still reaches the client, so the failure shows up only in the log.
Read in: Node.js ERR_HTTP_HEADERS_SENT — reproduced on Express 5.2.1Related
Section titled “Related”- X-Powered-By — the other, much smaller, disclosure
- Session secret and store — what a leaked trace helps an attacker reach