August 8, 2026 · v1.2.0

API Bill of Materials: The SBOM for your API surface

Introducing ABOM — a cryptographically signed, versioned, diffable manifest of every API route in your codebase.

The market is bifurcated. Runtime tools (Salt Security, Akto) find shadow APIs after they're live in production traffic — for $100K/year. Spec-based tools (42Crunch) lint an OpenAPI spec that's usually stale. Nobody owns the gap: static, pre-deployment, code-level detection that runs in your CI pipeline for free.

shadowaudit v1.2.0 fills that gap with the API Bill of Materials (ABOM).

The problem

OWASP API9:2023 (Improper Inventory Management) is in the API Top 10 — and has been since 2019. Every SOC2, ISO 27001, and PCI audit opens with the same question: "give us a complete inventory of all API endpoints."

Teams answer with a 6-week manual spreadsheet exercise. A senior engineer walks the codebase, opens every routes/ folder, lists every @GetMapping, @app.route, Router.get, and resources :posts declaration, then copies them into a Google Sheet that's stale the moment the next PR merges. The auditor signs off. Three weeks later, an undocumented endpoint ships to production — because nobody updated the sheet.

This is the gap nobody owns. Runtime tools like Salt Security and Akto catch shadow APIs in production traffic, but they cost six figures a year and only see what's already deployed. Spec-based tools like 42Crunch lint an OpenAPI spec, but the spec is almost always stale — written once at project kickoff, never updated, and routinely missing half the actual endpoints. Neither runs in CI. Neither produces evidence an auditor can independently verify.

What is an ABOM?

The API Bill of Materials — ABOM — is the API equivalent of SBOM (Software Bill of Materials). Where an SBOM lists every dependency in your build, an ABOM lists every route in your API. The same principles apply:

The schema is abom-1.0. Every manifest carries a schema version, a signedAt timestamp, the tool version that produced it, a full route inventory with per-route risk scores, and — when signed — an ed25519 signature over the canonicalized payload.

How it works

shadowaudit --dir ./src --abom

This walks your source tree, runs framework-aware scanners across all 8 supported frameworks (Express, FastAPI, Django, Flask, NestJS, Rails, Spring Boot, Fastify), and emits a JSON manifest at .shadowaudit/abom.json. The manifest contains:

Composite Risk Score

A boolean "undocumented" flag is useless for triage. A route that's undocumented AND has no auth AND accepts a DELETE on a sensitive path is a 4-alarm fire; a route that's undocumented but sits behind OAuth2 and only does GET /health is a paperwork issue. v1.2.0 ships a composite 0-100 risk score per route:

SignalWeight
Undocumented (no spec match)+30
No auth detected+25
Dynamic params / BOLA surface+10
Sensitive path (/admin, /users, /payments, etc.)+15
Destructive method (DELETE, PUT)+10
Undocumented AND unauthenticated (compound)+10

Max possible score: 100. Severity bands: INFO (0-29), MEDIUM (30-49), HIGH (50-74), CRITICAL (75-100). The compound penalty for "undocumented AND unauthenticated" is what pushes the worst routes over the CRITICAL threshold — these are the routes an attacker would target first, because they're both invisible to defenders and reachable without credentials.

ABOM Diff Engine

shadowaudit --abom-diff old.json new.json compares two manifests and reports:

This turns the ABOM from a static inventory into a release-evidence artifact. Wire it into CI: generate an ABOM on every PR, diff against main's ABOM, post the delta as a PR comment. A reviewer sees at a glance: "this PR adds 3 routes, 2 of them undocumented, one with no auth." That's the review conversation that prevents shadow APIs from shipping in the first place — not a $100K/year runtime tool catching them in production three weeks later.

Signing

shadowaudit --dir ./src --abom --abom-sign key.pem --abom-output abom.json

The --abom-sign flag takes an ed25519 private key and signs the manifest. The signature covers the canonicalized route inventory, stats, and signedAt — anything an attacker might tamper with to hide a shadow route. verifyABOM() in the SDK checks the signature against the public key; if any byte of the route inventory changes after signing, verification fails.

The threat model: an engineer can't quietly delete a route from the manifest before sending it to the auditor. The auditor either has a valid signature (manifest is intact) or a verification failure (manifest was tampered with). No middle ground, no "trust me, I just cleaned it up."

Real-world validation

Before shipping v1.2.0, we ran the ABOM generator against three real-world codebases:

CodebaseFrameworkRoutes detectedFalse positives
Spring PetClinicSpring Boot170 (0%)
Express realworldExpress210 (0%)
Flask realworldFlask190 (0%)

Every detected route was verified against the source by file:line. Zero false positives across 57 routes. The ABOM's risk scores matched manual assessment: undocumented + no-auth routes scored 75-100 (CRITICAL), documented + authed routes scored 0-30 (INFO/MEDIUM). The composite scoring isn't theoretical — it tracks ground truth.

Brutal QA

19 adversarial tests ran against the ABOM pipeline before release. They found 4 critical bugs — all fixed before v1.2.0 shipped:

  1. <int:id> normalization — Flask routes use converters like <int:user_id>. The spec normalizer was stripping them to empty strings, breaking spec matches and inflating "undocumented" counts. Fixed: converters now normalize to :user_id consistently across inventory and matcher.
  2. signedAt payload gap — the signature covered the route inventory but not the signedAt timestamp. An attacker could backdate a manifest without invalidating the signature. Fixed: signedAt is now inside the signed payload.
  3. Substring false positives/api/users was matching /api/usersettings in the spec matcher, marking the latter as "documented" when it wasn't. Fixed: path matching now requires exact normalized-path equality.
  4. Case-sensitivity bugsGET /Users and GET /users were treated as distinct routes in the inventory but identical in the spec matcher, producing inconsistent risk scores. Fixed: both sides lowercase paths consistently before comparison.

Each bug had a regression test added. The 19-test suite now runs on every PR.

What's next

v1.2.0 is the foundation. The next three releases build on the ABOM:


The ABOM is what the API security market has been missing: a free, static, CI-native artifact that produces verifiable evidence without a six-figure runtime subscription. Install it, run it on your repo, and send the signed manifest to your auditor.

npm install -g shadowaudit@1.2.0
shadowaudit --dir ./src --abom
← back to blog