Your Stack Traces Are Leaking Secrets to Every Client
Von Seaworthy · 8. November 2026
Photo by Towfiqu barbhuiya on Unsplash
I keep seeing the same thing in code reviews, and it makes me wince every time. Someone asks an AI assistant to add error handling, and the assistant happily returns a full stack trace, database error string, and internal file path directly to the HTTP response. The code looks helpful. It is also a data-exposure bug sitting in plain sight.
This is not a rare edge case. It is the default shape of a lot of generated code. When you prompt for "robust error handling," the model gives you res.status(500).json({ error: err.message, stack: err.stack }) because that is what most tutorials and old examples do. It reads like good debugging practice. It is good debugging practice, for a local console. Sending it to the client is something else entirely.
What actually leaks
Consider this pattern, which I have seen more than once in production repos:
app.get('/api/user/:id', async (req, res) => {
try {
const user = await db.query('SELECT * FROM users WHERE id = $1', [req.params.id]);
res.json(user);
} catch (err) {
res.status(500).json({
error: err.message,
stack: err.stack,
query: err.query,
});
}
});
A database error from that handler can return the table name, column names, the failed SQL fragment, the Postgres version, internal hostnames, the path to node_modules, and the shape of your code. An attacker who can trigger errors gets a free reconnaissance pass. They learn your framework, your ORM, your schema hints, your deployment paths. None of that should be public, and honestly, most of the people shipping it do not realize they are shipping it, because the response only shows up when something breaks.
Why AI assistants make this worse
The people most exposed here are developers who lean on AI coding assistants and trust the output. I get it. The assistant writes a route, it works, tests pass, you move on. But the model is optimizing for code that runs, not code that is safe under failure. It does not know your logging pipeline. It does not know that your error middleware is supposed to catch this. So it inlines the error details into the response, because that is the shortest path to something that looks complete.
And the ops basics that would catch this (centralized logging, a generic error boundary, a review checklist that asks "what goes over the wire?") are exactly the steps people skip when they are moving fast with generated code. That combination is what worries me. Speed plus convenience plus a plausible-looking default equals leaked internals.
The fix is boring, which is why people ignore it
Log the detail. Return a generic message plus a correlation ID. That is it.
app.get('/api/user/:id', async (req, res) => {
try {
const user = await db.query('SELECT * FROM users WHERE id = $1', [req.params.id]);
res.json(user);
} catch (err) {
const errorId = crypto.randomUUID();
logger.error({ errorId, err, path: req.path });
res.status(500).json({ error: 'Internal server error', errorId });
}
});
The client gets a safe message and an ID. Your team gets the full error in the logs, tagged so support can match a user report to a stack trace. No schema hints, no paths, no library versions. If you want to go further, add a global error handler so individual routes cannot leak by accident, and add a test that asserts an error response does not contain stack or message keys from the underlying exception.
I would also push back on the idea that verbose errors help end users. They do not. "relation users does not exist" tells a user nothing, and it tells an attacker plenty. A generic message plus a reference ID is better for everyone.
Make it a review item
If you use AI assistants heavily, add one line to your prompt and one line to your review checklist. Ask for errors that log server-side and return generic client messages. Then actually check the response body in a failure test. The assistant will follow the instruction more often than you expect, and the checklist catches the times it does not.
This is not exotic security work. It is the kind of basic hygiene that gets skipped precisely because it feels too simple to matter. It matters. Error handling is one of the few places where a helpful default can quietly expose your internals to anyone who pokes the right input.
Seaworthy is a developer security company that reviews application code and cloud configuration for exposure issues like this one.
Dieser Artikel wurde von KI generiert und fasst öffentlich verfügbare Quellen zusammen.
Seaworthy scannt dein Repository nach den Problemen, die in Artikeln wie diesem behandelt werden.
Sicherheitslücken, offengelegte Geheimnisse und Fehlkonfigurationen — erkannt, bevor du bereitstellst. Kostenlos, kein Konto nötig.