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”.
Related
Section titled “Related”- X-Powered-By — the other, much smaller, disclosure
- Session secret and store — what a leaked trace helps an attacker reach