What a shadow API actually is
A shadow API is any endpoint your systems still answer that nobody owns, nobody documents and nobody monitors. It is not a design flaw, it is sediment: the leftovers of a mobile app you retired, a partner integration that ended, a migration that ran in parallel for six months and was never fully switched off.
The dangerous part is not that the endpoint is undocumented. It is that undocumented means unreviewed. It never appears in a security assessment, never gets a rate limit, never gets the authorisation fix the rest of the application received two years ago.
- ▸Zombie endpoints: old versions still live because one client might still call them.
- ▸Orphaned endpoints: the team that built them left, and the service kept running.
- ▸Internal endpoints that became external when a firewall rule, a proxy or a cloud migration quietly changed the boundary.
- ▸Debug and admin routes shipped for a launch and never removed, usually with the weakest authentication in the estate.
Where shadow endpoints come from
They come from normal engineering under time pressure, not negligence. Every migration, acquisition, mobile release and integration adds endpoints, and very few projects have a funded step that removes them. Over ten years that delta compounds into an attack surface larger than the product itself.
The recurring sources, in rough order of how often we find them:
- ▸Versioning without retirement. Version three ships, versions one and two stay online indefinitely because turning them off requires knowing who calls them, and nobody does.
- ▸Mobile app backends. Old app versions must keep working, so the endpoint that serves them keeps working, including for people who are not using the app at all.
- ▸Partner and B2B integrations. A contract ends, the credentials are never revoked, the endpoint stays reachable.
- ▸Copy-paste environments. Staging and UAT clones exposed to the internet with production data and weaker controls.
- ▸Platform plugins. A CMS extension registers routes you never asked for, which is one reason The WordPress Security Reality Check Every Business Owner Avoids is worth reading before assuming your surface is only what you built.
- ▸Half-finished migrations. The old system is left running "just in case" and stays running for years, a pattern covered in Leaving WordPress: A Migration Playbook That Does Not Lose Your SEO.
How attackers find the endpoints you forgot
They enumerate, they do not guess. Certificate transparency logs reveal hostnames you never advertised. Old mobile app packages are decompiled to extract base URLs and keys. JavaScript bundles are parsed for every path the front end has ever called. Public code repositories and archived pages give up the rest.
The reconnaissance workflow is cheap, automated and continuous:
- ▸Subdomain and host discovery from certificate logs and passive DNS, which surfaces api-old, legacy, uat and similar names with no linking required.
- ▸Client-side extraction. Your single page application ships a map of your API in its bundle. Mobile binaries contain base paths, header names and occasionally embedded secrets.
- ▸Path and parameter fuzzing against known naming conventions, guided by whatever the client-side extraction revealed.
- ▸Error message mining. Verbose stack traces from an old framework identify the technology and version precisely, which is then matched against the CISA Known Exploited Vulnerabilities catalogue to pick a working exploit.
Assume anything reachable is already catalogued by someone. The question is not whether an unknown endpoint will be found, it is whether you find it first.
Why the old endpoints are worth more than the new ones
Because the security work of the last five years usually landed only on the current version. The shadow endpoint runs the authorisation logic from before the rewrite, the rate limits from before the abuse incident, and the input validation from before anyone knew better. It is the same database with worse doors.
The failure patterns that make them valuable to an attacker:
- ▸Broken object level authorisation. The endpoint checks that you are logged in but not that the record belongs to you, so changing an identifier in the request returns someone else's data. This is the first entry in the OWASP API Security Top 10 for a reason.
- ▸No authentication at all. Internal-only endpoints written when the network was the perimeter, later exposed by a routing change.
- ▸Excessive data exposure. An old endpoint returns the whole user object and lets the client hide the fields, so the raw response contains password hashes, tokens or internal notes.
- ▸Mass assignment. An update endpoint accepts fields the current one rejects, including role or account status.
- ▸No rate limiting, which turns a legitimate lookup into a bulk export.
Discovery: build the inventory that is true, not the one that is documented
Start from traffic and code, never from documentation. Your specification describes intent. Your gateway logs, DNS records and source code describe reality. Reconcile all four and the gap between them is your shadow surface, which in a legacy estate is routinely larger than the documented one.
Run these five passes and merge the results into one list:
1. Traffic analysis. Pull every distinct path from load balancer, gateway and web server logs over the longest window you retain. Anything answering that is not in your spec is a candidate. 2. External enumeration. Do to yourself what an attacker does: certificate transparency, DNS records, port scans of your ranges, and a look at what is exposed from cloud accounts nobody claims. 3. Code and route extraction. Enumerate route definitions from every repository and every deployed artifact, including the ones with no recent commits. 4. Client-side extraction. Parse front end bundles and mobile binaries for every path and host they reference. 5. Dependency and component inventory. Generate a software bill of materials so you know what libraries are exposing what, using CISA guidance on SBOM and a machine-readable format such as CycloneDX.
For every endpoint, record four fields: what it does, who owns it, who calls it, and whether it touches personal or financial data. Endpoints with no owner and unknown callers are your priority list.
Triage: keep, fix or kill
Sort by exposure and data sensitivity, not by how old the code looks. An unauthenticated endpoint returning customer records outranks everything, even if it serves ten requests a day. Then give each entry one of three fates, recorded with a name and a date beside it.
- ▸Keep and document. It has a real caller and a real owner. Bring it into the specification, the gateway, the authentication scheme and the monitoring, then treat it as a supported product surface.
- ▸Fix and keep. The function is needed but the implementation predates your current controls. Add authorisation checks per object, response filtering, rate limits and logging before anything else.
- ▸Kill. No known caller, no owner, or duplicated by a newer endpoint. This is the majority in most legacy estates, and every one you remove is permanently removed risk rather than managed risk.
Anything unauthenticated that reads or writes personal data goes to the front of the queue regardless of traffic volume, and anything on a host you cannot name should be assumed hostile until proven otherwise.
Decommissioning without breaking a customer you forgot about
The fear that stops decommissioning is real: somewhere a client integration might depend on that endpoint. Handle it with evidence instead of courage. Instrument first, warn second, degrade third, remove fourth. Every step is reversible until the last one, and each produces the data the next step needs.
The sequence that works:
1. Instrument. Log every call with caller identity, source address, user agent and timestamp. Run it for a full business cycle, so quarterly and annual jobs appear. 2. Notify. Contact every identified caller with a date. Silence from a caller you can name is a decision they have made, not a reason to delay. 3. Deprecate loudly. Return a deprecation header and add latency, so integrations that ignore email still notice. 4. Brownout. Return errors for scheduled windows that grow over time. This surfaces the callers who never answered, safely and on your schedule. 5. Disable, do not delete. Turn the route off but keep the code and the ability to restore it for a defined period. 6. Remove and revoke. Delete the code, revoke the credentials, close the firewall rule, retire the DNS record and remove the host so it cannot be resurrected by an old deployment.
Keep the credential revocation step. An endpoint that is gone but whose API keys still work elsewhere has moved the problem, not solved it.
Keeping the inventory honest after the cleanup
A one-off audit decays within a quarter. The durable fix is making the inventory a build-time artifact rather than a document. If a route can reach production without appearing in the specification, the gateway and the monitoring, you will repeat this exercise in eighteen months.
- ▸Route registration as a gate. No endpoint reaches production unless it is declared, owned and authenticated. Fail the pipeline when the code exposes something the specification does not.
- ▸Continuous external enumeration. Run attacker-style discovery on a schedule and alert on anything new, including hosts from cloud accounts nobody remembers creating.
- ▸Ownership metadata that expires. An endpoint with no confirmed owner after a set period is automatically flagged for the kill list.
- ▸An expiry date on every version. Ship version three with the retirement date of version two already agreed.
- ▸Regulatory pressure is now on your side. Vulnerability handling and product security obligations under the EU Cyber Resilience Act make an accurate component and interface inventory a compliance asset rather than an engineering nicety.
This is exactly the work that a maintenance retainer should include and usually does not, which is the point of What Website Maintenance Should Actually Include (and What You Are Probably Paying For).
How TuniCyberLabs helps
We run the discovery for you, from traffic, code, clients and external enumeration, then produce one inventory with owners, callers and data classification. We fix what must stay, decommission the rest with a brownout schedule that does not break real integrations, and wire route registration into your pipeline so the surface stops growing.
Send us your domains and repositories and we will show you what is answering today: see how we work.
