Website problems
Stack traces in production responses
When a request fails, the response should tell the caller what went wrong in terms they can act on — and nothing else. A stack trace names your source files and their line numbers, a database error quotes a failed statement including table and column names, and a framework debug page frequently renders environment variables alongside both. None of these is a way in by itself; together they are a description of your stack more precise than anything anyone could infer from outside.
What each kind of disclosure gives away
A stack trace: your language, your framework, your directory structure, your dependency versions and the account the process runs as.
A database error: the engine, the schema, and usually the exact table and column involved in the failed statement.
A framework debug page: all of the above rendered readably, plus configuration values and frequently environment variables.
An absolute filesystem path anywhere in the body: the deployment layout, which is enough to guess at file locations.
A message that differs depending on whether a record exists, which lets a caller confirm identifiers one request at a time without ever seeing data.
Why it is almost always a configuration flag
Every major framework has a debug or development mode that produces exactly this, and a production mode that does not.
The flag is set by an environment variable, which means a deployment to a new environment can miss it entirely.
A framework major upgrade frequently resets error handling to its default, re-enabling behaviour that was previously suppressed.
Introducing an error-tracking service sometimes turns detailed errors back on, because the same setting controls reporting and rendering in some stacks.
So the fix is usually one line, and the finding is usually a deployment or an upgrade rather than a design choice.
What a good error response contains
A status code that matches what happened, which every client, proxy and monitor reads.
A short, stable, machine-readable identifier the caller can branch on, rather than a sentence that changes when someone improves the wording.
A correlation identifier the caller can quote in a support request.
Enough detail to act on where the caller could act — which field failed validation, which limit was exceeded.
Nothing about your internals. That detail belongs in your log, keyed by the same correlation identifier, where it is not lost and not published.
Errors are normal; the response is the finding
An API that never returns a 500 is an API nobody uses. Failures are expected and are not what is being reported here.
What is reported is what the caller is told when one happens.
Which means the fix costs nothing in diagnosability: the detail moves to the log rather than disappearing.
It is also the cheapest hardening available, because it changes no application logic.
And it is worth doing on the error paths nobody exercises, which are exactly the ones that were never configured.
How to check it yourself
Request an endpoint with a malformed identifier — a string where a number is expected — and read the body rather than the status.
Request a path that does not exist under the API prefix, which frequently produces a framework error page rather than a clean 404.
Look for file paths, line numbers, engine names and configuration values in whatever comes back.
Do the same against production rather than staging, since the configurations differ and staging is where debug output is correct.
Check the endpoints least used, since those are the ones whose error handling was never reviewed.
How VeriFixScan detects it
`api.errors.disclosure` reads the observed public responses for stack traces, database errors, debug flags and server paths.
`api.surface.detected` decides which endpoints are observed, so nothing is reached beyond the discovered inventory.
`exposure.internal.paths` reports server paths found in public assets, which is the same disclosure through a different channel.
`availability.error_pages` covers error pages on the site itself rather than on the API.
Only responses the scan already received are read: nothing is crafted or malformed to provoke a failure.
What this does not cover
Error paths that stayed quiet during the scan, which includes most of them on a working service.
Errors that only occur under load or on a specific record.
Unusual error formats that no pattern recognises, which is why a clean result is evidence rather than proof.
What your logs contain, which is where all of this detail should be and is not observable from outside.
Frequently asked questions
- Is returning a 500 itself the problem?
- No. The status is not the finding — what the body discloses is. A 500 carrying a bare error identifier is a correct answer to a failure.
- Will hiding the detail make debugging harder?
- No, because the detail moves rather than disappears. The trace, the query and the path go to your log, retrievable by the correlation identifier the caller was given.
- Do you deliberately break my API to see the errors?
- No. Only the responses received during ordinary requests are read. Nothing is malformed, fuzzed or crafted, which also means quiet error paths are not examined.
Sources
Related
VeriFixScan crawls a site and applies its checks to every page it reaches, keeping the evidence behind each finding. Scanning one website is free.
Scan a website