← All posts

How I Test for Shadow and Zombie APIs

How I Test for Shadow and Zombie APIs

Most organizations cannot tell you every API endpoint they expose. Teams ship features, merge branches, deploy microservices, and move on. Nobody updates the central inventory. What remains is a sprawl of undocumented routes, old versions still running in production, and endpoints that were supposed to be decommissioned months ago. These are shadow APIs and zombie APIs. Shadow endpoints were never documented. Zombie endpoints were documented once, then forgotten when a newer version launched.

The risk is straightforward. An endpoint you do not know about is an endpoint you are not monitoring, patching, or reviewing. I have seen authentication added to /api/v2/users while /api/v1/users still returns the same data with no token required. I have seen internal admin routes left exposed because someone assumed the load balancer would block them. The assumption is always that deployment equals decommission. It does not.

Why It Hides

These endpoints hide because there is no forcing function that makes them visible. API gateways route traffic, but they do not generate an authoritative list of what exists behind them. Documentation goes stale the moment it is written. Developers know the routes they work on, but not the routes three teams over or two years ago. Code review catches new vulnerabilities in new code, but it does not flag the presence of an old endpoint still listening. The application works, traffic flows, and nobody notices that ten percent of the surface area is undocumented or deprecated until someone external starts poking around.

The Method

  1. Map every route the application actually responds to, not just the ones in the OpenAPI spec. Use a proxy to capture traffic during normal use, then supplement with directory and version enumeration. Look for /v1/, /v2/, /internal/, /admin/, /debug/, and common resource names.
  2. Compare discovered endpoints against official documentation and the API gateway configuration. Anything that responds but is not documented is a shadow endpoint. Anything documented as deprecated but still processing requests is a zombie.
  3. Test whether deprecated or undocumented endpoints enforce the same security controls as current ones. Check for authentication, rate limiting, input validation, and logging. Often the old version has none of these.
  4. Look for internal or debug routes that were meant to be firewalled but are reachable from the public internet. Examples include health checks that leak version info, admin panels with default credentials, or GraphQL introspection left enabled in production.
  5. Check for endpoints that reference old domain names, acquisitions, or rebranded products. These often survive in DNS and routing config long after the product team considers them dead.
  6. Build or update an inventory that includes every responding endpoint, its intended audience, its authentication requirements, and its deprecation status. Treat this as a living artifact tied to deployment, not a document written once.

The Deeper Issue

Versioning does not mean isolation. Running /v2/ does not turn off /v1/. I see teams assume that deploying a new version is the same as removing the old one. It is not. Deprecation requires active decommissioning: removing routes from the gateway, returning 410 Gone, and blocking traffic. If the old code is still deployed and the route still resolves, it is still attack surface.

Why It Stays a Problem

This stays a problem because inventory is treated as a documentation task, not an operational one. Nobody is responsible for knowing the full surface area. Security teams scan what they know about. Developers ship what they are assigned. Operations deploy what is in the pipeline. The gaps between these workflows are where shadow and zombie APIs live. There is no automated enforcement that says a route must be in the inventory to be routed, and there is no process that removes a route when it is deprecated. The system is append only.

I treat API inventory as a continuous discovery problem, not a one time audit. If you cannot list every endpoint that will respond to a request, you cannot secure them all.