API Response Leaking Sensitive Stack Trace in 500 Error Payload
Quick answer
An API returning a full stack trace, internal file paths, or raw database error details in its error responses is a real information disclosure issue — this...
An API returning a full stack trace, internal file paths, or raw database error details in its error responses is a real information disclosure issue — this content helps an attacker understand your internal architecture, technology stack, and potential vulnerabilities, and the fix is implementing environment-aware error handling that returns detailed errors only in development, never in production.
The Problem
An API error response includes far more internal detail than it should, visible to any client that triggers the error:
{
"error": "TypeError: Cannot read property 'id' of undefined",
"stack": "at UserController.getProfile (/app/src/controllers/user.js:42:18)\n at ...",
"sql": "SELECT * FROM users WHERE id = '' AND deleted_at IS NULL"
}
This response reveals your internal file structure, the specific framework/language in use, exact code line numbers, and even raw SQL — all details that should never reach an external client, regardless of whether the underlying bug itself is genuinely serious or trivial.
Why It Happens
This typically happens because default error handling behavior — in many frameworks, showing detailed errors is the helpful, convenient default specifically for local development — was never explicitly overridden or made environment-aware before deploying to production:
- A framework's default error handler, genuinely useful during local development for quickly diagnosing issues, is left unchanged and still active in the production deployment
- Custom error-handling code was written without distinguishing between development and production environments, always including full error detail regardless of where the code is actually running
- An error-handling middleware or library's default configuration wasn't explicitly reviewed or adjusted before shipping, silently carrying forward development-friendly defaults into production
The Fix
Implement explicit, environment-aware error handling that returns detailed information only when running in a genuine development context, and a generic, safe message in production:
app.use((err, req, res, next) => {
console.error(err); // always log full detail server-side, for your own debugging
if (process.env.NODE_ENV === 'production') {
res.status(500).json({ error: 'Internal server error' });
} else {
res.status(500).json({
error: err.message,
stack: err.stack,
});
}
});
Critically, always log the full error detail server-side regardless of environment — the goal is to prevent this information from reaching the external client in production, not to lose the diagnostic detail entirely, which you still genuinely need for your own debugging and monitoring purposes.
For frameworks with their own built-in default error-handling behavior, explicitly confirm and configure the production-appropriate setting rather than assuming a safe default — many frameworks require an explicit environment variable or configuration flag to switch from verbose development error pages to generic production ones:
# Example: various frameworks have their own specific mechanism -
# confirm and explicitly set the production-appropriate value
# rather than assuming it happens automatically
FLASK_DEBUG=false
NODE_ENV=production
RAILS_ENV=production
Never include raw SQL queries, internal file paths, environment variable values, or any other internal implementation detail in an error response under any circumstance, even in error messages that seem otherwise generic — audit your custom error messages specifically for accidentally embedded internal detail, not just the framework's own default stack-trace behavior:
// Avoid including raw internal detail even in seemingly custom, controlled error messages
throw new Error(`Failed to query users table with filter: ${JSON.stringify(rawQuery)}`);
// Prefer generic, safe messaging for anything client-facing
throw new Error('Unable to process request');
// (log the detailed version server-side separately, not in the thrown error's own message)
Set up proper server-side error monitoring and alerting (a dedicated error tracking service, structured logging with alerting) so you retain full diagnostic capability without needing to expose any of that detail to the client at all — this ensures removing detail from client-facing responses doesn't come at the cost of your own ability to actually diagnose and fix issues.
Test your production error handling explicitly, not just your happy-path functionality, deliberately triggering error conditions against your actual production (or production-equivalent staging) environment to confirm detailed information genuinely isn't leaking, rather than assuming your environment-aware configuration works correctly without having actually verified it under real conditions:
curl -X POST https://api.example.com/endpoint -d 'deliberately malformed payload'
# review the actual response for any unexpected internal detail
Still Not Working?
If you've implemented environment-aware error handling in your main application code but still occasionally see detailed errors leak through, check whether a different layer is generating some error responses independently of your application's own error handler — a reverse proxy, API gateway, or the underlying web server itself sometimes has its own separate default error pages for certain failure conditions (like a request that fails before ever reaching your application code at all, such as a malformed request the web server itself rejects), and these layers need their own separate, explicit configuration to avoid leaking their own internal detail, independent of anything configured within your application code.