Shadowaudit 0.1.0: Catch Shadow APIs Before They Hit Production

Developers spin up quick test endpoints and forget to remove them. These shadow APIs bypass documentation, skip auth middleware, and get deployed to production silently — invisible to network scanners and security teams. Shadowaudit solves this by statically analyzing your codebase, comparing every route definition against your OpenAPI/Swagger spec, and failing your CI pipeline before the PR merges if it finds undocumented or unauthenticated routes.

Shadowaudit v0.1.0 supports Express.js (full AST-based route extraction via Babel), with FastAPI and Django support coming soon. It can be installed globally (npm install -g shadowaudit) or used via npx (npx shadowaudit --dir ./src --spec ./openapi.json). It also ships as a GitHub Action for easy CI integration.

How It Works

Shadowaudit's pipeline has four stages:

  1. Express Scanner — Uses @babel/parser and @babel/traverse to walk every .js/.ts file's AST and extract route definitions (app.get(), router.post(), router.route().get().post() chaining, etc.).
  2. Auth Detector — Scans a ±15-line window around each route for auth middleware patterns (e.g., .authenticate(, requireAuth, passport.authenticate), with sibling-route lines stripped to avoid false positives.
  3. Spec Parser — Reads OpenAPI 3.x / Swagger 2.0 files (JSON or YAML), extracts documented routes, and normalizes path syntax ({id}:id).
  4. Delta Comparator — Cross-references scanned routes against documented routes, reconciling Swagger basePath and OpenAPI servers[0].url prefixes, then scores each undocumented route as CRITICAL (no auth) or HIGH (has auth).

Severity Levels and CI Behavior

SeverityConditionCI behavior (--fail-on critical)
CRITICALUndocumented route + no auth middlewarePipeline fails
HIGHUndocumented route + auth middleware presentPipeline passes (review recommended)
INFOInformational findingPipeline passes

The --fail-on flag (default: critical) controls the threshold. With --fail-on high, both CRITICAL and HIGH findings fail the pipeline. With --fail-on info, any finding fails.

CLI Options

Usage: shadowaudit [options]

Options:
  -V, --version       output the version number
  --dir         Directory to scan (default: current directory)
  --spec        Path to OpenAPI/Swagger spec file
  --format      Output format: table, json, sarif (default: "table")
  --fail-on    Fail CI pipeline on: critical, high, info (default: "critical")
  --framework   Force framework: express, fastapi, django (default: "auto")
  -h, --help          display help for command

Output Formats

Table (default) — Human-readable colored table with severity, method, path, file, line, and auth status. Includes a summary box and pipeline recommendation.

JSON — Structured JSON for CI dashboards and log aggregators:

{
  "scanMeta": {
    "tool": "shadowaudit",
    "version": "0.1.0",
    "timestamp": "2026-01-15T12:00:00.000Z",
    "stats": { "total": 7, "documented": 5, "undocumented": 2, "critical": 2, "high": 0, "info": 0 }
  },
  "findings": [
    {
      "severity": "CRITICAL",
      "method": "GET",
      "path": "/api/debug/reset",
      "file": "routes.js",
      "line": 21,
      "hasAuth": false,
      "reason": "Undocumented route with no authentication middleware detected — publicly accessible shadow API"
    }
  ]
}

SARIF 2.1.0 — Industry-standard format for GitHub Code Scanning, Azure DevOps, and other security dashboards. Maps findings to two rules: SHADOW001 (CRITICAL, error) and SHADOW002 (HIGH/INFO, warning/note).

Configuration

Shadowaudit loads config from two sources (CLI flags take priority):

  • CLI flags (highest priority)
  • .shadowaudit.yml or .shadowauditrc.yml in your project root

Example .shadowaudit.yml:

spec: ./openapi.json
dir: ./src
format: table
failOn: critical
authPatterns:
  - myCustomAuth
  - requireLogin
ignore:
  - node_modules
  - dist
  - build

Default auth patterns (always detected): .authenticate(, .authorize(, requireAuth, isAuthenticated, verifyToken, checkAuth, ensureLoggedIn, passport.authenticate, jwt.verify, bearerAuth, apiKeyAuth, basicAuth.

Demo Output

In a test project with 7 Express routes — 5 documented, 2 undocumented shadow routes with no auth — the tool produced:

[INFO] No config file found, using defaults
╔════════════════════════════════╗
║     shadowaudit v0.1.0         ║
║     API Shadow Route Scanner   ║
╚════════════════════════════════╝
[INFO] Directory : /tmp/auth-test
[INFO] Spec file : /tmp/auth-test/openapi.json
[INFO] Format    : table
[INFO] Fail on   : critical
[INFO] Framework : express
[INFO] Running Express.js route scanner...
[✓] Found 7 routes in /tmp/auth-test
[INFO]   GET /public/health → routes.js:6 [auth]
[INFO]   GET /public/products → routes.js:7 [auth]
[INFO]   GET /api/users → routes.js:9 [auth]
[INFO]   POST /api/users → routes.js:13 [auth]
[INFO]   GET /api/admin/dashboard → routes.js:17 [auth]
[INFO]   GET /api/debug/reset → routes.js:21 [no auth]
[INFO]   POST /api/test/seed → routes.js:22 [no auth]
[INFO] Detected spec: OpenAPI 3.x
[INFO] Spec contains 5 documented routes
[INFO] ──────────────────────────────────────────────────
[INFO] Total routes scanned : 7
[INFO] Documented           : 5
[INFO] Undocumented         : 2
[INFO] ──────────────────────────────────────────────────
╔══════════════════════════════════════════════╗
║           shadowaudit — Scan Report          ║
╚══════════════════════════════════════════════╝
┌──────────┬────────┬───────────────────────────────────┬───────────────────────────────────┬──────┬──────┐
│ SEVERITY │ METHOD │ PATH                              │ FILE                              │ LINE │ AUTH │
├──────────┼────────┼───────────────────────────────────┼───────────────────────────────────┼──────┼──────┤
│ CRITICAL │ GET    │ /api/debug/reset                  │ routes.js                         │ 21   │ NO   │
├──────────┼────────┼───────────────────────────────────┼───────────────────────────────────┼──────┼──────┤
│ CRITICAL │ POST   │ /api/test/seed                    │ routes.js                         │ 22   │ NO   │
└──────────┴────────┴───────────────────────────────────┴───────────────────────────────────┴──────┴──────┘
┌─────────────────────────────────────────────┐
│  SCAN SUMMARY                               │
│  Total routes scanned : 7                   │
│  Documented           : 5                   │
│  Undocumented         : 2                   │
│  ─────────────────────────────────────────  │
│  🔴 CRITICAL          : 2                   │
│  🟡 HIGH              : 0                   │
│  🔵 INFO              : 0                   │
└─────────────────────────────────────────────┘
⛔ Pipeline will FAIL — 2 critical shadow route(s) detected
Exit code: 1

CI/CD Integration

GitHub Actions — Add a workflow file that runs on pull requests:

name: Security Scan
on: [pull_request]
jobs:
  shadowaudit:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: '20'
      - run: npm ci
      - run: npx shadowaudit --dir ./src --spec ./openapi.json --format sarif > shadowaudit.sarif
        continue-on-error: true
      - uses: github/codeql-action/upload-sarif@v3
        with:
          sarif_file: shadowaudit.sarif

What's Next

The author plans to add FastAPI and Django support. For now, if you're using Express.js, you can drop Shadowaudit into your CI pipeline today. Install it and run npx shadowaudit --dir ./src --spec ./openapi.json to catch shadow APIs before they reach production.