Skip to main content

Express.js scanner

shadowaudit's Express.js scanner uses Babel AST parsing to extract route definitions from .js and .ts files. It's the most mature scanner — supporting route chaining, mount prefix reconciliation, and AST-based auth detection.

How it works

The scanner parses each .js/.ts file with @babel/parser (with TypeScript, JSX, and decorators plugins) and walks the AST looking for route definition patterns.

Detected patterns

Pattern 1 — Direct app methods

app.get('/api/users', getUsers);
app.post('/api/users', createUser);
app.put('/api/users/:id', updateUser);
app.delete('/api/users/:id', deleteUser);

Pattern 2 — Router methods

const router = express.Router();

router.get('/products', listProducts);
router.post('/products', createProduct);

Pattern 3 — Route chaining

router.route('/orders')
.get(getOrders)
.post(createOrder);

Pattern 4 — Mount prefixes (v0.2.0+)

shadowaudit reconciles app.use('/prefix', router) mount points:

// app.js
app.use('/api/v1', require('./routes/users'));

// routes/users.js
router.get('/users', getUsers);
// → scanned as GET /api/v1/users

Example scan output

shadowaudit --dir ./src --spec openapi.json --framework express
[INFO] Running Express.js route scanner...
[✓] Found 18 routes in ./src
[INFO] GET /api/users → routes/users.js:9 [auth]
[INFO] POST /api/users → routes/users.js:14 [auth]
[INFO] GET /api/debug/reset → routes/debug.js:3 [no auth]

┌──────────┬────────┬───────────────────┬──────────────┬──────┐
│ SEVERITY │ METHOD │ PATH │ FILE │ AUTH │
├──────────┼────────┼───────────────────┼──────────────┼──────┤
│ CRITICAL │ GET │ /api/debug/reset │ routes/debug │ NO │
└──────────┴────────┴───────────────────┴──────────────┴──────┘

Auth detection

shadowaudit detects auth middleware by:

  1. AST-based detection (v0.3.0+) — walks the route's CallExpression arguments and checks if any middleware name contains auth, login, session, token, jwt, passport, permission, require, protect, secure, or verify.
  2. Hardcoded patterns.authenticate(, passport.authenticate, jwt.verify, auth(, etc.
  3. Custom patterns — any patterns you add to authPatterns in your config.

Known limitations

  • Dynamic route mountingrouter.use(route.path, route.route) inside a forEach loop is not yet supported (tracked in issue #1)
  • Computed accessapp['get']('/path', handler) is not detected (must use identifier property access)
  • Template literal pathsapp.get(`/path`, handler) is not detected (must use string literals)