skip to content

A production API returns unhandled stack traces and still routes /debug/env — why is this an information-disclosure threat?

level: juniorimportance: must knowfreq 70%

answer

  1. Unauthenticated caller, zero effort
  2. Paths, versions, queries — and worse
  3. Environment variables hold the keys
  4. Generic body, correlation id, server-side log
  5. Suppressing does not un-disclose

basics

~20 s

Both hand an unauthenticated stranger internal detail for free: stack traces expose paths, versions and sometimes a connection string; a debug route dumps environment variables including keys. Return generic errors, keep the route out of production builds, and rotate anything exposed.

solid answer

~50 s

This is Information Disclosure on a process — the service itself is the leak channel, and no authentication is needed to trigger it. An unhandled stack trace tells an anonymous caller the framework and version, internal file paths and class names, often the failing query, and here a credential inside a connection string. A `/debug/env` route is worse: environment variables are where deployments keep API keys and database passwords, so one request becomes a credential dump. The fix has three parts. Return a generic error body with a correlation identifier and log the detail server-side, so support can still diagnose. Remove the debug route from the production build or gate it behind a deploy-time flag rather than renaming it to something unguessable. And treat anything that was exposed as burned — rotate the credential, because suppressing the message does not un-disclose what was already served.

go deeper

for a junior

Be ready to say what a stack trace hands an attacker, and to describe the standard fix: a generic error body plus a correlation identifier, with the full detail written to the server-side log.

for a middle

Explain why obscuring a debug route is not a control, and where the verbose-error default usually comes from — a non-production profile shipping to production. Set the handler globally, not per endpoint.

for a senior

Demonstrate the incident reflex: anything served in a trace or a dump is burned and gets rotated, and you assume exposure without proof of it. Then rate the two halves separately rather than filing both under error handling.

for a principal

Own how this class stops recurring: a default-deny error handler in the platform template, debug surfaces excluded from production builds by construction, and secrets that are rotatable cheaply enough that rotation is a routine response rather than an outage.

## Why this is the textbook Information Disclosure finding STRIDE's Information Disclosure category is data reaching someone not entitled to it — a loss of confidentiality. What makes this pair the canonical example is that the *system is the leak*. Nothing is broken into. The service is asked an ordinary question, and it answers with material it was never meant to hand out. The attacker position is the weakest possible one — an anonymous caller with no account — and the asset at stake is the most valuable one a system holds: credentials and keys, which are the raw material for every later threat. ## What a stack trace actually gives away Read it the way an attacker does: - **Framework and version strings** in the frame list, which narrow down what is worth trying next. - **Absolute file paths**, which reveal the runtime layout, the deployment user and sometimes the internal project or team name. - **Class, package and method names**, which sketch the internal design and the names of things not exposed publicly. - **The failing statement or query**, which reveals table and column names and how input is used. - **Values interpolated into the message** — the row that failed, the identifier that was not found, and in this scenario the connection string the driver was constructing when it threw. None of these are, individually, a compromise. Together they replace a week of guessing with a map. And the connection string is not a hint at all — it is the credential. ## What a debug route gives away An endpoint that prints the process environment is a different order of problem. Modern deployments inject configuration as environment variables, so the dump typically contains database passwords, third-party API keys, signing secrets and internal hostnames. One unauthenticated GET yields everything the process can authenticate as. Debug and diagnostic routes get added during an incident and survive because nothing links to them and nobody remembers. ## Fixing it properly **Errors.** Split the audience. The caller gets a stable, generic body — an error code, a human-readable sentence with no internals, and a correlation identifier. The full exception goes to the server-side log, keyed by that identifier, so an engineer can still find it in seconds. Set this at the framework level as a default handler rather than per-endpoint, because the leak comes from the paths nobody anticipated. Make sure the non-production configuration is not the one that ships: the classic root cause is a debug flag left on in the production profile. **Debug routes.** The strong answer is that the route does not exist in the production build, or is behind a compile-time or deploy-time flag. Weaker answers that interviewers hear and dislike: renaming it to an unguessable path (security by obscurity — it is in the build, in the repository and possibly in a client bundle), or relying on nothing linking to it (crawlers and word lists find it). **The exposed secret.** This is the part juniors miss. Disclosure is not reversible. Once a connection string or key has been served in a response, it must be rotated, and you should assume the response was seen even if you have no evidence it was. Fixing the handler stops the *next* leak; rotation is what closes this one. ## The wider family The same category covers channels that never involve an error at all: - **Verbose headers** that name the server, framework or version on every response. - **Predictable identifiers and filenames** — if attachments are served as sequential names, anyone with one link can walk the set, and each retrieval is a fresh disclosure. - **Document metadata** — an exported file typically keeps its author, the editing device and, for photographs, location fields, so a document that was reviewed for its visible text still ships facts about the person and the machine that made it. When you model a process on a diagram, the useful habit is to ask not only "what does this endpoint return" but "what does it return when it fails, and what rides along with what it returns". ## How to rate it Be honest that the two halves are not equal. The stack trace is high likelihood and moderate impact on its own — easy to trigger, useful rather than fatal — unless it carries the credential, which makes it critical. The environment dump is critical on both axes: trivially triggered, and it yields the keys. In a modelling session this is exactly the moment to show you rate the *consequence* rather than assigning one severity to a bucket called "error handling".

  • The team suppresses the trace and deletes the route. Is the finding closed?
    Not yet. Anything the trace or the dump served has to be treated as compromised and rotated — the database password, the API keys, the signing secret. Disclosure cannot be taken back, and you rarely have logs good enough to prove nobody saw it. Closing the finding means: handler fixed, route gone from the build, secrets rotated, and a check that the production profile does not re-enable verbose errors on the next deploy.
  • Where else does a system leak without ever returning an error?
    Response headers that advertise server and framework versions; predictable or sequential filenames that let one valid link become a walk through everyone's documents; and document metadata — author, editing device, and location fields on exported files — which survives a review of the visible text. All three are Information Disclosure on a process or a flow, and none of them look like a bug in testing.
  • Why is renaming the debug route to a random path a weak fix?
    Because the path is not a secret. It lives in the source repository, in build artefacts, in deployment configuration, and often in client-side bundles or logs, and it never rotates. Obscurity may slow an opportunistic scanner, but the route still exists and still returns the environment to anyone who learns the name. Removing it from the production build, or gating it on a deploy-time flag, is what actually changes the threat.

saying these in an interview costs you the question

  • Says the fix is authenticating the debug endpoint only
  • Renames the route to an unguessable path
  • Thinks suppressing the message makes the secret safe
  • Calls a stack trace harmless because it is not data
  • Leaves verbose errors on because support needs them

context