Leftover Debug Code
A debug endpoint dumps process.env on every call because the NODE_ENV gate uses a typo of the variable name. An attacker pulls the JWT_SECRET and DB password, then you ship the build-time exclusion and the fail-closed allow-list.
What Is Leftover Debug Code?
Leftover debug code is a special case of security misconfiguration where the bug is not in the security-sensitive logic itself but in the gate that was supposed to disable it. The classic shape: a development-only endpoint guards itself with a NODE_ENV check, the env-var name is misspelled, the check resolves to undefined !== 'production' which is always true, and the endpoint answers in every environment. The endpoint then leaks whatever it was designed to leak in development, usually env vars, internal config, stack traces, request dumps, and the leak includes production secrets. This exercise puts you on both sides. As Bob, a content-discovery sweep of common debug paths lands on /api/_debug/env on Vessura's procurement portal, which returns a JSON object containing the DATABASE_URL (with password), JWT_SECRET, AWS keys, and Stripe live key. You forge an admin session token with the leaked signing secret and read the internal vendor ledger through a public API. As Alice, on the receiving end of a DLP rule matching an AWS key in an outbound response, you open the handler, see process.env.NODE_ENVIRONMENT !== 'production' , recognise the typo, and ship the durable fix: build-time exclusion of the route plus a fail-closed positive-allow-list gate on the correct NODE_ENV . The exercise closes with quiz questions on why the original gate failed open, why runtime gates are fragile for production-disabled features, and the systemic shape (build-time exclusion) that closes the class.
What You'll Learn in Leftover Debug Code
- Recognize debug-route exposure as a security-misconfiguration failure mode, the gate is the bug, not the logic the gate protects
- Understand that runtime env-var gates fail silently on typos (<code>process.env.UNDEFINED</code> returns undefined, no error), so positive allow-lists with explicit equality are safer than negative deny-lists
- Apply the immediate fix (rotate every leaked credential, flip the gate to an explicit allow-list with the correct env-var name) and the systemic fix (build-time exclusion via DefinePlugin / esbuild --define / conditional require)
- Audit every debug, status, health, metrics, and admin-internal endpoint for response content that includes secrets, internal IPs, dependency versions, or stack traces
- Internalize: routes that should not exist in production should be absent from the production bundle, not present-but-gated
Leftover Debug Code — Training Steps
-
Size up the target
Bob is a malicious actor. He makes his living pulling data out of companies that left something exposed, then selling it or using it to get at their money. Today his target is Vessura, a procurement platform other businesses run on. He starts where anyone would, on the public site, sizing the company up before he looks for a way in.
-
Map what is exposed
Nothing on the public site is unusual: a login, a vendor directory, a support page. But apps ship with more than their front door. Developer scaffolding left in a build stays reachable by anyone who goes looking, so Bob maps every path the portal's API actually answers.
-
Hit the debug route
Bob rebuilds the request the scan flagged. He points the API Tester at the debug path and sends a plain GET, no login and no token, to see what it hands back.
-
Every secret at once
The response is not an error page. It is the entire process environment of a production server, handed to a request that carried no login.
-
Forge an admin token
The dump handed Bob the JWT_SECRET , the key the app uses to sign every session token. Back in the same terminal, he mints his own token, stamps the role as admin, and the server will accept it as a genuine login.
-
Walk in as admin
Now Bob uses the token. He calls the internal vendor API, a page meant only for signed-in admins, and carries the forged token in an Authorization header so the server treats him as one. The terminal on the left still holds the token he just minted.
-
The ledger is open
The server trusted the token and answered. Bob is now reading the internal vendor ledger from a public API, with no account of his own.
-
Knowledge check
You just watched a single leftover path hand over every secret and then an admin session. Lock in why.
-
The DLP alert lands
You own Vessura's procurement portal. Overnight, outbound data-loss monitoring tripped on a response that carried live secrets off the server. Security operations has emailed you the details.
-
Open the handler
Open the debug handler and look at how it decides whether to answer.