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 mounting — router.use(route.path, route.route) inside a forEach loop is not yet supported (tracked in issue #1)
  • Computed access — app['get']('/path', handler) is not detected (must use identifier property access)
  • Template literal paths — app.get(`/path`, handler) is not detected (must use string literals)