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

Leftover Debug Code — Training Steps

  1. 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.

  2. 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.

  3. 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.

  4. 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.

  5. 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.

  6. 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.

  7. 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.

  8. Knowledge check

    You just watched a single leftover path hand over every secret and then an admin session. Lock in why.

  9. 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.

  10. Open the handler

    Open the debug handler and look at how it decides whether to answer.