API Discovery

API Discovery: Killing the Shadow and Zombie API Sprawl

API Discovery
Security Deep Dive
Your API inventory lies. Every engineering team has endpoints that aren't in the docs, aren't behind the gateway, and have no clear owner. Two categories of undocumented APIs drive this drift: shadow APIs and zombie APIs. Both sit outside your security controls. Attackers find them before you do.

What Is a Shadow API

A shadow API is an endpoint that exists in your running application but has no entry in your API specification, no configuration in your gateway, and no owner on your team. Shadow APIs aren't planted by attackers. Your own developers create them and forget them.
They appear through predictable patterns:
Debug routes added during incidentsA developer bypasses the gateway to diagnose a production problem, adds a debug endpoint directly to the service, ships the fix, and moves on. The endpoint stays.
Libraries that self-register routesAn admin panel or monitoring library registers its own management routes at initialization. Nobody reads the changelog carefully enough to know the new endpoints appear in production.
Management interfaces on public portsA service binds its health, metrics, and management surface to the same port as its public API. Firewall rules protect the documented routes — not the management surface nobody documented.
QA routes that ship to productionA pull request adds a /test or /qa route for manual verification. The feature ships. The route stays.
Shadow APIs bypass every security control you've built for known routes. WAF rules don't protect endpoints not in your gateway. Rate limiting, IP allowlisting, authentication middleware — none of it applies to a route nobody configured it on.

What Is a Zombie API

A zombie API is an old version that should be dead but still responds. The pattern repeats across organizations: v1 gets deprecated when v2 ships. The team updates the docs and the gateway routing. The route registration in the application code stays.
Both versions registered — both respondingGET /api/v1/users/{id} ← deprecated, still responds, security posture from three years agoGET /api/v2/users/{id} ← current, all mitigations applied
v1 and v2 rarely share the same security posture. The input validation added in v2 doesn't exist in v1. The rate limiting added after a credential-stuffing incident protects v2 only. The authentication tightening — the one that fixed the algorithm confusion vulnerability — never made it back to v1. The security headers middleware runs in the v2 pipeline, not the v1 pipeline.
Attackers probe for this deliberately. When v2 deflects their scanner, they try /api/v1/, /v1/, /api/v0/, and /legacy/ on the same host. They find v1. They use it.

The Attacker's View

An attacker who finds a zombie API has found a gap in your defensive perimeter. The attack sequence is simple:
Reconnaissance against a v2-only API with a zombie v1GET /api/v2/users/42 → 200 OK # rate-limited after 10 req/minGET /api/v1/users/42 → 200 OK # no rate limit, no monitoring alertGET /api/v1/users/1200 OKGET /api/v1/users/2200 OKGET /api/v1/users/3200 OK # full enumeration, undetected
No alert fires. No rate limit triggers. No anomaly detection sees it, because the v1 route generates no baseline traffic — every request looks novel. The team has no idea until data turns up somewhere it shouldn't.

How They Appear Across Stacks

Shadow and zombie APIs aren't tied to a single language or framework. Every stack that maps URLs to handlers can accumulate them. A route registered without an authorization check — or an old version never removed — appears in all of them.
Node.js / Express// No auth middleware — ships to productionapp.get('/internal/db-stats', (req, res) => res.json(db.stats()))
Java / Spring Boot// Missing @PreAuthorize@GetMapping("/internal/db-stats")public ResponseEntity dbStats() { ... }
Python / Django# No @login_requiredpath('internal/db-stats/', views.db_stats),
Go / Gin// No authMiddleware in the chainr.GET("/internal/db-stats", handlers.DbStats)
Ruby / Rails# Controller missing before_action :authenticate_user!get '/internal/db-stats', to: 'internal#db_stats'
The gap isn't in the language or framework. It's in review processes that treat route registration and authorization as separate concerns — and in deployment pipelines that never verify the actual route inventory against the intended API surface.

Four Ways to Find Them

1 — Compare traffic logs to your API specPull 30 days of traffic logs from your gateway or load balancer. Any route appearing in logs that has no entry in your OpenAPI spec is a shadow candidate. This method finds only endpoints that have received traffic — dead routes nobody has hit yet stay invisible.
2 — Scan source code for route registrationsEvery framework uses a finite set of patterns to register routes. Scan your entire codebase for those patterns — not just the controller directories. Map every registered route. Compare that list to your spec. The gaps are undocumented endpoints.
3 — Search for version prefix patterns in codeSearch your entire codebase for /v1/, /api/v0/, /legacy/, and similar prefixes. Map all registered version prefixes. Any prefix absent from your current spec with no active deprecation handling is a zombie candidate.
4 — Map test coverage against your specInstrument your test suite with a traffic recorder. Any endpoint your tests call that doesn't appear in your documented API spec was never intentionally included in the public surface. Some will be internal test helpers — many will be undocumented exposures.
Automated Detection

How ApiPosture Pro Catches Shadow and Zombie API Risks

ApiPosture Pro reads your source code directly — not just route metadata, but actual method body implementations — and flags the patterns that shadow and zombie APIs introduce. It works across multiple languages and frameworks, scanning the same way attackers reason: look at what the code actually does, not just what the docs say it does.
Routes without authorization checks — AP101 flags public endpoints that access a database or perform writes with no authorization check in the method body. These are the debug and internal routes that ship without access control.
Missing audit logging on DELETE endpoints — AP107 catches destructive operations with no audit trail. Zombie DELETE endpoints are particularly exposed: they stay functional long after the team stops monitoring them.
Hardcoded secrets in legacy endpoints — AP201 scans method bodies for credentials, tokens, and API keys. Old endpoints often carry hardcoded secrets from before your team adopted a secrets manager — and those endpoints never got the rotation treatment.
SQL injection in old endpoint implementations — AP103 detects raw SQL execution with string interpolation or concatenation. This pattern is common in legacy code that predates parameterized query adoption — code that lives on in zombie endpoints long after the active codebase moved on.
All scans run 100% locally — no code leaves your machine. Add apiposture-pro scan . --fail-on high to your CI/CD pipeline and block merges that introduce unprotected routes before they reach production.

Share this article:
>_ Keep Reading

Explore more security insights

Choose which optional cookies to allow. You can change this any time.