DEV Community

Dark Master
Dark Master

Posted on

I Built a Free Shadow API Scanner and Found 269 Forgotten Routes in Ghost CMS

How a weekend project turned into a production tool with 875+ npm downloads in 48 hours — and why your codebase probably has shadow APIs too.

There's a route in your codebase right now that nobody remembers adding.
Maybe a developer spun up app.get('/api/debug/reset') on a Friday to test something. No auth middleware. No documentation. Committed and forgotten. It's been sitting in production for months - invisible to your API scanner, invisible to your security team, visible to anyone who reads your source code.
These are called shadow APIs. And the scary part is: you probably have more of them than you think.
I know because I built a tool to find them, and the results surprised me.

What I built

shadowaudit is a free, open-source CLI tool that scans your source code statically - no agents, no traffic mirrors, no $70K enterprise contracts - and finds API routes that exist in your code but aren't in your OpenAPI spec.
It compares what your code actually does against what your documentation says it does. Any route in the code but not in the spec gets flagged. If that route also has no authentication middleware? That's a CRITICAL finding. Your CI pipeline fails. The PR doesn't merge.
Simple idea. Surprisingly effective.

The Ghost CMS test

To see if shadowaudit actually works on real code (not just toy examples), I ran it against Ghost CMS - the publishing platform that powers 404Media, Platformer, and The Browser. It's a production-grade Express.js app with a large API surface.
The first scan found 269 routes.
But the initial results had problems. Ghost uses a custom auth middleware called mw.authAdminApi that shadowaudit didn't recognize - so 213 routes that actually had auth were reported as "no auth detected." That's a 70% false negative rate. Embarrassing.
So I fixed it. The v0.3.0 update added AST-based auth detection - instead of matching hardcoded pattern strings, it walks the route's arguments in the code's abstract syntax tree and checks if any middleware name contains auth-related words like "auth," "login," "token," "jwt," or "passport."
After the fix: 235 routes correctly authenticated, 0 false positives, 34 routes legitimately public (login, password reset, setup endpoints - expected).
Ghost's security was solid. Zero real vulnerabilities. But the scan proved shadowaudit works on real, production-grade codebases - not just examples.

The features nobody asked for but I built anyway

What started as "compare routes against a spec" grew into a full security tool:
4 framework support. Express.js, FastAPI, Django, and Flask. The tool auto-detects which framework your project uses - just point it at a directory and it figures out the rest.
Three output formats. A colored terminal table for humans. JSON for CI pipelines and jq piping. SARIF 2.1.0 for GitHub's Security tab - findings appear right in your PR with file locations and remediation guidance.
GitHub Action. Published to the GitHub Marketplace. Add 3 lines to your workflow YAML and every pull request gets scanned automatically. A bot posts a findings table directly on the PR as a comment.
Auto-spec generation. Don't have an OpenAPI spec? Run shadowaudit - generate-spec and it creates one from your code. Not perfect - but a starting point.
Mount prefix reconciliation. If you mount routers with app.use('/api/v1', router), shadowaudit figures out that router.get('/users') is actually served at /api/v1/users. This was the hardest feature to build - it required cross-file require tracking and variable name resolution.

The numbers

In 48 hours since the first npm publish:

  • 875+ downloads across all versions
  • 9 releases (v0.1.0-beta through v0.5.1)
  • 113 tests across 13 test files
  • 4 frameworks supported
  • Socket.dev audit: 100/100 on vulnerability, quality, and license scores
  • 0 open issues (all 5 created and closed)
  • 1 GitHub Marketplace listing
  • 1 documentation site (built with Docusaurus, deployed to GitHub Pages) ## What it looks like
$ npm install -g shadowaudit
$ shadowaudit - dir ./src - spec ./openapi.json
╔══════════════════════════════════════════════╗
║ shadowaudit - Scan Report ║
╚══════════════════════════════════════════════╝
┌──────────┬────────┬───────────────────┬──────┬──────┐
│ SEVERITY │ METHOD │ PATH │ AUTH │ │
├──────────┼────────┼───────────────────┼──────┼──────┤
│ CRITICAL │ GET │ /api/debug/reset │ NO │ │
│ HIGH │ POST │ /api/shadow │ YES │ │
└──────────┴────────┴───────────────────┴──────┴──────┘
⛔ Pipeline will FAIL - 1 critical shadow route(s) detected
Enter fullscreen mode Exit fullscreen mode

That CRITICAL finding? That's the forgotten /api/debug/reset route with no auth. shadowaudit just stopped it from reaching production.

How to use it

# Install
npm install -g shadowaudit
# Scan your code
shadowaudit - dir ./src - spec ./openapi.json
# Don't have a spec? Generate one
shadowaudit - dir ./src - framework express - generate-spec > openapi.json
# Add to GitHub Actions (3 lines)
- uses: darkmaster0345/shadow-Audit@v0.5.1
 with:
 dir: './src'
 spec: './openapi.json'
 fail-on: 'critical'
Enter fullscreen mode Exit fullscreen mode

The honest part

shadowaudit isn't perfect. It has limitations:

  • Django class-based view methods aren't fully detected (all routes show as GET - fix planned for v0.6.0)
  • Dynamic route mounting (routes.forEach(route => router.use(route.path, route.route))) isn't supported (too complex for static analysis)
  • Flask methods= with tuple syntax had a bug in v0.5.0 that was fixed in v0.5.1 (real Flask projects use methods=('GET',) not methods=['GET']) But it's honest about its limitations. Every known issue is documented in the ROADMAP.md. And every release gets a full QA pass before shipping - the v0.5.1 release was driven entirely by bugs found in adversarial QA testing. ##Why I built it Enterprise API security tools (Salt Security, Traceable, Checkmarx) cost $50,000+ per year. They're runtime-based - they watch your traffic and find shadow APIs after they're already deployed and receiving requests. That's reactive. shadowaudit is preventive. It finds the shadow route in the source code before the PR merges, before the endpoint ships, before anyone can send it traffic. And it's free. MIT licensed. No telemetry. No license keys. No phone-home. The only network call is when you use the GitHub Action's PR comment bot, which talks to api.github.com using your own token. ## Try it

npx shadowaudit - dir ./src - framework express
If it finds zero routes - great, you're clean. If it finds a CRITICAL - you just caught a security issue before it reached production.
Either way, it takes 2 seconds and costs nothing.
GitHub: darkmaster0345/shadow-Audit
npm: shadowaudit
Docs: darkmaster0345.github.io/shadow-Audit
 - -
If this helped you find a shadow route, consider sponsoring the project. Free tools stay free when people support them.

Top comments (1)

Some comments may only be visible to logged-in visitors. Sign in to view all comments.