Error Messages – When They Leak Too Much Detail

Error Messages – When They Leak Too Much Detail

A developer debugging a failed checkout at 2 a.m. wants as much detail as possible, while an attacker probing that same endpoint wants exactly the same thing. Verbose error messages are one of the oldest information disclosure problems in web application security, and they still turn up in security audits of mature production systems. This article explains what error messages typically leak, how attackers use that detail, how to find leaks on your own site, and how to fix them without losing the diagnostics your team needs.

What Verbose Error Messages Actually Reveal

A raw stack trace is a map of your application. It shows absolute file paths like /var/www/shop/vendor/laravel/framework/src/, framework and library versions, class and method names, and sometimes the exact SQL query that failed. MITRE tracks this weakness as CWE-209, “Generation of Error Message Containing Sensitive Information.”

The worst cases go further. Django with DEBUG = True renders a page listing settings, installed apps and request metadata. Laravel with APP_DEBUG=true has historically exposed environment variables through its debug page, and the Ignition debug component behind that page was the entry point for CVE-2021-3129, a remote code execution flaw in versions before 2.5.2. A debug page can stop being a leak and become the attack surface.

Smaller leaks matter too. A MySQL error reading “You have an error in your SQL syntax near ”’ at line 1″ tells a tester that a single quote broke the query. That turns a guess into a confirmed lead for SQL injection within seconds.

How Attackers Turn Error Output Into an Attack Plan

Error leakage rarely causes a breach by itself. What it does is cut down the attacker’s reconnaissance time. When an error shows Spring Framework 5.3.17, the attacker checks it against known CVEs instead of fingerprinting blind, the same way server banner disclosure hands over version numbers for free.

Here is a pattern that shows up again and again in penetration testing. A team ships a new REST endpoint for order lookups. A tester sends a malformed JSON body, for example a string where an integer is expected, and gets an HTTP 500 back with a full Symfony exception. The response includes the internal database hostname (db-replica-02.internal), the ORM version and the file path of the controller. Nobody configured it to do that on purpose. A staging .env file was copied into the production container during a rushed release, and APP_ENV=dev went with it.

That took one request and under a minute. The tester now knows the internal network naming scheme, the ORM’s injection history and the code layout. Any later SSRF finding becomes far more dangerous because the internal targets are already known.

The Myth: “A Custom Error Page Means We’re Covered”

Many teams believe that once they’ve designed a branded 404 and 500 page, error handling is done. It isn’t. Custom pages usually cover HTML routes only. API endpoints, GraphQL resolvers, file upload handlers and AJAX calls often return the framework’s default JSON error, stack trace included.

Apollo Server is a good example. In Apollo Server 4, stack traces appear in the extensions field of GraphQL errors unless NODE_ENV is set to production or includeStacktraceInErrorResponses is explicitly disabled. Express behaves the same way: its default error handler prints the stack whenever NODE_ENV isn’t “production.” The HTML page can look polished while the /graphql endpoint dumps internals.

Leaks also aren’t limited to stack traces. A login form that says “No account with that email” for one input and “Wrong password” for another enables user enumeration. Different error codes, response lengths or timings can leak just as much as text.

Finding Error Leakage on Your Own Site

An experienced application security engineer doesn’t wait for errors to show up on their own. They provoke them deliberately and in a structured way:

1. Send malformed input to every parameter: single quotes, very long strings, null bytes, arrays where scalars are expected (id[]=1), and invalid JSON.
2. Request paths that don’t exist under API prefixes, not just the site root, for example /api/v2/doesnotexist.
3. Switch HTTP methods (PUT or DELETE against GET-only routes) and send incorrect Content-Type headers.
4. Compare the responses for valid and invalid usernames on login, registration and password reset.
5. Search the response bodies for markers such as “Traceback”, “at java.”, “stack”, “vendor/”, “SQLSTATE” and internal hostnames.

Automated vulnerability scanning handles the repetitive part of this well, and daily scans catch regressions that appear after deployments. Authenticated areas and multi-step flows still need manual follow-up, because a scanner that can’t reach a page can’t trigger its errors.

Common Mistakes Teams Make When Fixing Error Leakage

Fixing the symptom at the edge only. Teams often configure Nginx or Cloudflare to replace 500 responses with a generic page. This breaks when the application returns sensitive detail with a 200 or 400 status code, which validation errors frequently do. The fix belongs in the application’s exception handler.

Forgetting the configuration drift path. Settings like display_errors = On in php.ini, customErrors mode=”Off” in ASP.NET web.config, or server.error.include-stacktrace=always in Spring Boot often arrive through copied config files, Docker base images or environment variables. A fix in code means nothing if the next deploy brings the debug flag back. Add a CI check that fails the build when production config contains debug values.

Stripping all detail and blinding the support team. Overcorrecting is a real problem. If users get “Something went wrong” and the logs contain nothing useful, engineers will quietly turn debug mode back on to troubleshoot. Return a correlation ID to the client (for example, “Error reference: 7f3a9c21”) and write the full stack trace to the server-side logs under that ID. Users can report it, and engineers can find it.

Edge Cases Where the Standard Advice Shifts

Internal admin tools on an air-gapped network can reasonably show more detail, but only if they really are unreachable from outside. “Internal” tools exposed through a misconfigured reverse proxy are a recurring finding. Public developer APIs are a different case: integrators need useful validation errors such as “field ‘quantity’ must be a positive integer”, so the goal is precise messages about the input, never about the internals.

Logs carry their own risk. A stack trace containing a customer’s email address or a session token becomes personal data under GDPR, so retention and access controls on error logs matter as much as what the browser sees. This all fits the broader category of security misconfigurations in the OWASP Top 10. The OWASP Top 10 2025 also added a dedicated category, A10 “Mishandling of Exceptional Conditions.”

FAQ

Is showing the framework name in an error message a real risk?
On its own it’s low severity, but it speeds up targeting. Combined with a version number, it lets an attacker match your stack against public CVEs in minutes, so it’s worth removing.

Should validation errors be generic too?
No. Errors about the user’s own input should be specific and helpful. Hide internal state such as queries, paths, hostnames and stack traces, and use the same message for authentication failures so they don’t reveal which accounts exist.

How often should error handling be retested?
After every deployment that touches configuration, dependencies or routing. Continuous automated scanning catches most regressions, and a manual review once or twice a year covers authenticated and business-critical flows.

Summary: Log Everything, Show Almost Nothing

Error messages should help two audiences without crossing the line between them. Users get a clear, generic message and a reference ID, and engineers get the full trace in protected logs. Handle exceptions in the application rather than only at the edge, block debug flags in your build pipeline, and deliberately provoke errors on every endpoint, including APIs and GraphQL. A good first check takes ten minutes: send a single quote and a malformed JSON body to your three most-used endpoints, then read exactly what comes back.