Description
Including err.stack directly in a response body or HTTP header can disclose internal paths, call flows, and library or framework details. Attackers may use this information to locate vulnerable code and refine further attacks.
Potential impact
- File paths, source line numbers and module or version details may be disclosed.
- Call stacks may help an attacker identify vulnerable code or APIs and refine injection attempts.
- Operating-system usernames and absolute paths may expose details useful for further compromise.
Remediation
- Return a generic error message to clients and record necessary stack traces only in restricted server logs.
- Send errors to a central Express error handler to provide consistent messages and status codes.
- Do not include
err.stackin response bodies, JSON or headers. - Disable development error pages in production. Use a
traceIdto correlate an error with internal logs when needed. - Mask secrets in logs and limit log access and retention. Moving a stack trace into a log does not protect the log itself.
Examples
Before
javascript
const express = require('express');
const app = express();
app.get('/item/:id', async (req, res) => {
try {
// Simulate an error during processing
throw new Error('DB query failed');
} catch (err) {
// BAD: Expose the stack trace in the response body
return res.status(500).send('Error occurred: ' + err.stack);
}
});
module.exports = app;
After
javascript
const express = require('express');
const crypto = require('crypto');
const app = express();
// Example route
app.get('/item/:id', async (req, res, next) => {
try {
// Simulate an error during processing
throw new Error('DB connection timeout');
} catch (err) {
return next(err); // Delegate to the central error handler
}
});
// Central handler: record the stack only in server logs
app.use((err, req, res, next) => {
const traceId = crypto.randomUUID();
console.error(`[${traceId}]`, err.stack); // Internal server log
res.status(500).json({ message: 'Internal Server Error', traceId }); // Do not expose the stack
});
module.exports = app;
The first example exposes the stack directly in the response body. Node.js rejects header values containing characters such as newlines, so the example focuses on body disclosure.
The second uses a central handler to return only a generic message and traceId, recording the stack in server logs. Its console.error call does not provide production log protection: remove sensitive values and restrict access to logs.