Express Middleware Analyzer
Express has no file that declares what is protected. Protection is whatever the route lines and middleware files say. This analyzer reads both: it checks each route for a guard among its own arguments, judges the auth middleware files, and then looks for rate limiting, security headers, CORS and tenant isolation.
Route coverage
The analyzer runs only when the repository contains an Express app: a file that mentions express together with app.listen(, express() or express.Router(). Four routes below go through three gates in order.
1The route mapper reads each app.get(path, ...args) line. Only the arguments after the path count as guards. The app.use("/api", authMiddleware) line is recorded as a mount point and covers no route in this check.
2/api/admin/users: no guard in its arguments, not a public path, and the path holds /api/ and /admin. Medium, 0.85, one finding per HTTP method. The global app.use above it does not rescue it.
3/api/health has no guard either, but /health is on the public list with /ping, /status, /docs, /swagger, /openapi and /webhook. It is skipped.
4/api/tasks names requireAuth among its arguments. Guard names are matched on word boundaries, so checkAuthorEmail would not pass for checkAuth. Skipped.
5/ has no guard and is not public, but nothing in its path says it is sensitive: the list is /api/, /trpc, /graphql, /admin, /user, /account, /settings, /payment, /billing, /profile, /dashboard. One finding in all.
Auth middleware quality
A guard that exists can still be useless. Two middleware files, one that only checks a header is present and one that verifies a token.
1The analyzer collects the middleware files of an Express app and reads them for three kinds of pattern: strong verification, weak existence tests, and 401 or 403 enforcement.
2if (req.headers.authorization) only asks whether the header is truthy. With no verification pattern yet seen, the file is a High finding, 0.80: any string sent as a Bearer token gets through.
3jwt.verify( is a strong pattern: the token is checked, not just present. This file is clean, and a strong hit also protects weak files that come after it in the scan.
4If no strong pattern exists in any file at all, and the repository has Express routes, one High finding, 0.90 reports that there is no verifying middleware anywhere.
The four hygiene checks
These checks have no per-route logic. Each searches the middleware files, and for some checks the main files too, for a named tool.
1Besides auth, the Express analyzer runs four hygiene checks on the same files. They share one pattern: look for the protective tool by name, and report its absence.
2Rate limiting. No limiter anywhere is Medium, 0.80. A limiter that keeps counters in a Map without a shared store is Medium, 0.70, because each instance counts separately and an attacker multiplies the limit by the instance count.
3Security headers. helmet clears all of them. Without it, each of four missing headers is its own Low finding, 0.85: Strict-Transport-Security, X-Frame-Options, X-Content-Type-Options and Content-Security-Policy.
4CORS. Both halves have to sit in one file: origin: '*' and credentials: true. High, 0.85. It lets any website make credentialed requests.
5Tenant isolation. A repository that mentions tenant_id and defines tenants is multi-tenant; if no tenant middleware name appears anywhere, High, 0.75.
Why it matters
Express runs its functions in the order they are registered, and a guard only protects the routes that name it. A single route without the guard is open to anyone, and the rest of the API looking protected hides it. This is the Express counterpart of the Next.js matcher gap.
Peloton (2021). Unauthenticated API endpoints exposed user data. The markdown reads this as an API that likely had auth middleware which was not applied to every route, the pattern this scanner looks for.
Vulnerable vs fixed code
// BAD: no guard among this route's arguments
app.get("/api/admin/users", getUsers)
// BAD: middleware that only tests that a header exists
function auth(req, res, next) {
if (req.headers.authorization) return next()
return res.status(401).end()
}
// BAD: auth applied after the routes it was meant to cover
app.use(authMiddleware)// GOOD: apply auth middleware before the routes
app.use("/api", authMiddleware)
// GOOD: name the guard on the route, which is what this analyzer reads
app.get("/api/admin/users", requireAuth, getUsers)
// GOOD: the middleware verifies the token
function authMiddleware(req, res, next) {
req.user = jwt.verify(token, SECRET)
next()
}The analyzer does not model registration order or wildcard bypass by route specificity. It treats a route as guarded only when a guard name appears in that route's own arguments, so app.use("/api", authMiddleware) placed correctly above the routes still leaves per-route findings.
Limits and scope
| Behaviour | Detail |
|---|---|
| Input | The repository source. Runs only when a file mentions express with app.listen(, express() or express.Router(). tRPC and Next.js route.ts files are excluded from the route list. |
| Severity and confidence | No verifying middleware: High 0.90. Weak token check: High 0.80. Route without guard: Medium 0.85 per method. Rate limit: Medium 0.80, in-memory 0.70. Headers: Low 0.85 each. CORS: High 0.85. Tenant isolation: High 0.75. |
| Route guard | A known guard name (such as requireAuth, verifyAuth, authenticate, isAuthenticated, verifyToken, authMiddleware) among the route's arguments on its line. Global app.use is not counted. |
| Which routes are judged | Not public (/health, /ping, /status, /docs, /swagger, /openapi, /webhook) and sensitive (/api/, /trpc, /graphql, /admin, /user, /account, /settings, /payment, /billing, /profile, /dashboard). |
| Not implemented | Middleware ordering, and bypass by route specificity. |