Back

API09: Improper Inventory Management - Prevention

Prevention Strategy Overview

Preventing improper inventory management is a program, not a patch. It combines knowing what you run (inventory and discovery), governing how it changes (versioning, deprecation, environment separation), and watching it continuously (documentation as source of truth, monitoring, external scanning). The defenses below are layered so that a miss at one layer is caught by another.

Core Principles

1. Build and Maintain an API Inventory

The foundation is a single authoritative catalog of every API: host, version, owner, data classification, auth model, and lifecycle status. Treat it as code so it stays current.

# api-inventory.yaml -- version-controlled, reviewed on every change
apis:
  - name: users-api
    host: api.example.com
    versions:
      - version: v3
        status: active
        auth: oauth2 + scopes
        data_classification: PII
        owner: identity-team
        openapi: specs/users-v3.yaml
      - version: v2
        status: deprecated
        sunset_date: 2024-06-30
        owner: identity-team
      - version: v1
        status: retired          # MUST return 410, verified by CI
        retired_date: 2023-01-15
  - name: payments-api
    host: payments.example.com
    versions:
      - version: v1
        status: active
        auth: mTLS
        data_classification: PCI
        owner: payments-team

Enrich the catalog automatically from the sources that already know about your traffic: the API gateway config, service mesh, load-balancer rules, DNS, and cloud provider inventories. Reconcile discovered endpoints against the declared inventory and flag anything unaccounted for.

2. Versioning and a Real Deprecation Policy

Adopt an explicit versioning scheme and, crucially, a lifecycle with enforced sunset dates. Announce deprecation with headers so clients migrate before the shutdown.

# Advertise deprecation and sunset on responses (RFC 8594 Sunset header)
HTTP/1.1 200 OK
Deprecation: true
Sunset: Sat, 30 Jun 2024 23:59:59 GMT
Link: <https://api.example.com/v3/users>; rel="successor-version"
Warning: 299 - "v2 is deprecated; migrate to v3 before the Sunset date"

When the sunset date passes, the version must return 410 Gone — and that behavior should be asserted by an automated test so a retired version can never silently come back to life.

# CI guard: retired versions MUST be gone
def test_retired_versions_return_410():
    for host, path in RETIRED_ENDPOINTS:
        r = requests.get(f"https://{host}{path}", timeout=5)
        assert r.status_code == 410, f"{host}{path} is still live!"

3. Environment Separation

Non-production must never be reachable with production data over the public internet. Enforce separation at the network, data, and configuration layers.

# Example: block public exposure of non-prod hosts at the edge (nginx)
map $host $is_nonprod {
    default        0;
    "~*^(dev|staging|qa|uat)-" 1;
}
server {
    if ($is_nonprod) {
        # only allow the corporate VPN range
        allow 10.20.0.0/16;
        deny  all;
    }
}

4. Documentation (OpenAPI) as the Source of Truth

Make a machine-readable specification the authoritative contract, generate or validate it in CI, and diff it on every change so a new or undocumented endpoint cannot ship unnoticed.

# CI step: fail the build if the running app exposes routes not in the spec
# (spec-vs-implementation drift detection)
spectral lint openapi.yaml                 # lint the spec itself
schemathesis run openapi.yaml --checks all # test impl against the spec

# Diff the generated spec against the committed one
oasdiff breaking committed-openapi.yaml generated-openapi.yaml \
    && echo "No undocumented drift" \
    || (echo "Undocumented endpoint or breaking change detected"; exit 1)

Do not serve internal specs publicly. Interactive docs, openapi.json, and GraphQL introspection should be disabled or authenticated in production. The spec is a governance tool, not a public map of your attack surface.

5. Disable Framework Diagnostic Endpoints in Production

Framework defaults are a leading source of shadow exposure. Turn them off explicitly.

# Spring Boot: expose nothing by default, secure what you keep
management.endpoints.web.exposure.include=health
management.endpoints.web.exposure.exclude=env,heapdump,mappings,beans
management.endpoint.health.show-details=never
# And put /actuator behind authentication + a private network
# FastAPI / Flask: no interactive docs or introspection in prod
app = FastAPI(
    docs_url=None,       # disable Swagger UI
    redoc_url=None,      # disable ReDoc
    openapi_url=None,    # do not serve the spec publicly
)

6. External-Facing Asset Discovery

Attackers enumerate your surface; you must enumerate it first. Run continuous external discovery and reconcile the results against your inventory.

# Scheduled discovery pipeline (illustrative)
# 1. Pull hostnames from certificate transparency
curl -s "https://crt.sh/?q=%25.example.com&output=json" | jq -r '.[].name_value' \
  | sort -u > discovered_hosts.txt

# 2. Resolve + probe which are actually live
httpx -l discovered_hosts.txt -status-code -title -tech-detect > live_hosts.txt

# 3. Diff against the declared inventory; alert on anything new
comm -23 <(sort live_hosts.txt) <(sort inventory_hosts.txt) > UNKNOWN_ASSETS.txt
[ -s UNKNOWN_ASSETS.txt ] && notify-security "Unmanaged assets found"

Complement scanning with a DNS hygiene process: remove records for retired hosts and watch for dangling records that could be subdomain-takeover targets.

7. Enforce Control Parity Across Versions and Hosts

Centralize security so that a new control automatically applies everywhere, rather than being re-implemented per version. An API gateway is the natural enforcement point — but only if all traffic routes through it.

# Gateway policy applied to every route, every version (illustrative)
policies:
  - match: "/api/**"          # all paths, all versions
    require_auth: oauth2
    require_tls: "1.2+"
    rate_limit: 100/min
    deny_if_unlisted: true    # reject routes not in the registered inventory

The deny_if_unlisted pattern is powerful: the gateway rejects any path that is not explicitly registered, so a shadow endpoint deployed behind the gateway fails closed instead of silently serving traffic.

TLS and Access Parity

Old versions and stale hosts frequently run outdated TLS or accept credentials the current surface has rotated. Enforce a single TLS baseline and a single credential lifecycle across every host — deprecated endpoints must not be a soft spot for downgrade or replay.

8. Monitoring and Detection

Every endpoint in the inventory must also be in your telemetry. Alert specifically on signals of inventory failure.

def monitor_inventory_signals(request):
    alerts = []
    if request.path.startswith(("/api/v1", "/api/v2/legacy")):
        alerts.append(f"Traffic to deprecated endpoint: {request.path}")
    if request.path not in REGISTERED_ROUTES:
        alerts.append(f"Request to unregistered route: {request.path}")
    if any(p in request.path for p in ("/actuator", "/_debug", "/swagger", "/metrics")):
        if not is_internal(request.remote_addr):
            alerts.append(f"External access to diagnostic path: {request.path}")
    if alerts:
        log.warning("inventory_signal", extra={"alerts": alerts, "src": request.remote_addr})
        send_security_alert(alerts)

9. Govern Third-Party Data Flows

Extend the inventory to cover integrations: for each partner or vendor connection, record what data is shared, in which direction, under what authentication, and who owns the relationship. Review these on the same cadence as your own endpoints, and include them in breach-impact analysis.

Prevention Checklist

Key Takeaways

  1. Inventory-as-code — a reviewed, machine-readable catalog is the foundation.
  2. Deprecation needs teeth — enforce sunset with 410 and CI assertions.
  3. Separate environments hard — non-prod off the internet, data masked.
  4. Spec drives deployment — detect drift in CI, don't publish the spec.
  5. Discover before attackers do — automate external scanning.
  6. Enforce parity centrally — one gateway policy for all versions; fail closed on unlisted routes.
  7. Monitor the whole inventory — every known endpoint feeds telemetry.

Next Steps