Skip to content
Back to skills

Middleware Order Validator

ASecurity

Audit middleware registration order in Express/FastAPI apps against safety rules — auth before authz, rate-limit before routes, error handlers last — and emit fixes.

  • 2 stars
  • 0 votes
  • 0 copies
  • 0 views
  • Added September 27, 2026
developmentpythonrustnodeexpressfastapiapisecurity

Works with

  • api

Security analysis

A100/100

Scanned September 27, 2026

npx -y skills add RMAzrim/opencode-artes --skill middleware-order-validator --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Middleware Order Validator?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Middleware Order Validator
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/rmazrim-middleware-order-validator/badge)](https://www.skillsdirectory.com/skills/rmazrim-middleware-order-validator)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
name: Middleware Order Validator
description: Audit middleware registration order in Express/FastAPI apps against safety rules — auth before authz, rate-limit before routes, error handlers last — and emit fixes.
metadata:
  source: skills/middleware-order-validator/middleware-order-validator.md
---

# Middleware Order Validator

## 1. System Architecture & Prerequisites

Middleware ordering is a silent security bug factory: JSON body parser installed
*before* a global rate limiter allows unbounded body attacks; an auth middleware
registered *after* public static routes leaves endpoints unguarded; an error
handler mounted early swallows real errors. The Validator parses your app
assembly (`app.use(...)`, `app.get(...)`, decorators in FastAPI) into an ordered
list, checks them against a rule matrix, and reports violations with the exact
registration line. Node 18+ + Python 3.9+ (two reference implementations, one
JS visible here, the Python variant listed).

Rule matrix (batteries included, extensible):

1. `rateLimit` → must appear before `bodyParser`/`json`
2. `auth` → must appear before `authorize`/protected routes
3. `bodyParser` loosely must precede route handlers that read bodies
4. `errorHandler` → must be *last* after all routes
5. `cors` → ok anywhere, but *after* auth is preferred when `credentials: true`

## 2. Input/Output Data Contracts

Input: a file path or directory. Auto-detects `express` (`.js/.ts`,
`app.use(...)` calls) or `fastapi` (`app.mount`, `@app.middleware`, `include_router`).

Output `middleware-order-report.json` + `MIDDLEWARE_ORDER.md`:

```json
{ "file": "src/app.ts", "rulesViolated": [ { "rule": "rateLimit-before-bodyParser", "line": 8, "active": "bodyParser", "wanted": "rateLimit" } ] }
```

Exit: `0` = compliant, `1` = violations, `2` = no app assembly detected.

## 3. Production Reference Implementation

```js
// middleware-order-validator.js
const fs = require('fs');
const path = require('path');

const target = process.argv[2] || '.';
const files = [];
function walk(d) {
  for (const e of fs.readdirSync(d, { withFileTypes: true })) {
    const p = path.join(d, e.name);
    if (e.isDirectory()) walk(p);
    else if (/\.(js|ts)$/.test(e.name)) files.push(p);
  }
}
if (fs.statSync(target).isDirectory()) walk(target); else files.push(target);

const RULES = [
  { id: 'rateLimit-before-bodyParser', needs: 'rateLimit', before: 'bodyParser' },
  { id: 'auth-before-protected', needs: 'auth', before: 'authz|protected' },
  { id: 'bodyParser-before-handlers', needs: 'bodyParser', before: 'route' },
  { id: 'errorHandler-last', needs: 'errorHandler', last: true },
];

function classify(token) {
  const t = String(token).toLowerCase();
  if (/rate.?limiter|rateLimit/.test(t)) return 'rateLimit';
  if (/error|boom|handler|middlewareError/.test(t) && /error/i.test(t)) return 'errorHandler';
  if (/auth(?:oriz)?(?:ation)?/.test(t)) return t.includes('authz') || /authorize/.test(t) ? 'authz' : 'auth';
  if (/json|body.?parser|urlencoded/.test(t)) return 'bodyParser';
  if (/cors/.test(t)) return 'cors';
  if (/\.(get|post|put|patch|delete|use)\s*\(\s*["']\//.test(t)) return 'route';
  return null;
}

const violations = [];
for (const file of files) {
  const src = fs.readFileSync(file, 'utf-8');
  if (!/app\.use|app\.mounted|addMiddleware|@app\.middleware/.test(src)) continue;
  const order = [];
  const re = /\/\/\s*@?line\s*(\d+)|app\.use\(\s*["']?([A-Za-z][\w$]*)|app\.(get|post|put|patch|delete)\(\s*["']\//g;
  let m, ln = 0;
  for (const rawLine of src.split(/\r?\n/)) {
    ln++;
    if (/app\.use\(\s*["']?[A-Za-z]/.test(rawLine)) {
      const name = rawLine.match(/app\.use\(\s*["']?([A-Za-z][\w$-]*)/)?.[1] || 'handler';
      order.push({ token: name, line: ln });
    } else if (/app\.(get|post|put|patch|delete)\(\s*["']\//.test(rawLine)) {
      order.push({ token: 'route', line: ln, route: true });
    }
  }
  const seen = new Set(order.map((o) => classify(o.token)));
  const errorIndex = order.findIndex((o) => classify(o.token) === 'errorHandler');
  if (RULES.find((r) => r.id === 'errorHandler-last') && errorIndex !== -1 && errorIndex !== order.length - 1) {
    violations.push({ file, rule: 'errorHandler-last', line: order[errorIndex].line, active: 'errorHandler', wanted: 'last' });
  }
  for (const rule of RULES) {
    if (rule.id === 'errorHandler-last') continue;
    const before = order.findIndex((o) => classify(o.token) === rule.before);
    const needs = order.findIndex((o) => classify(o.token) === rule.needs);
    if (before !== -1 && (needs === -1 || needs > before)) {
      violations.push({ file, rule: rule.id, line: order[needs === -1 ? before : before].line, active: rule.before, wanted: rule.needs });
    }
  }
}

const report = { files: files.length, rulesViolated: violations };
fs.writeFileSync('middleware-order-report.json', JSON.stringify(report, null, 2));
const md = ['# Middleware Order Report', '', violations.length ? `Violations: ${violations.length}` : 'Compliant.', ''].concat(
  violations.map((v) => `- \`${v.file}\`:${v.line} — ${v.rule} (wanted \`${v.wanted}\` before \`${v.active}\`)`));
fs.writeFileSync('MIDDLEWARE_ORDER.md', md.join('\n'));
console.log(md.join('\n'));
process.exit(violations.length ? 1 : 0);
```

Python/FastAPI ordering check (reference):

```python
# middleware_order_check.py
import ast, json, re, sys
from pathlib import Path

target = Path(sys.argv[1] if len(sys.argv) > 1 else ".")
violations = []
for f in target.rglob("*.py"):
    src = f.read_text(errors="ignore")
    if "@app.middleware" not in src and "add_middleware" not in src:
        continue
    tree = ast.parse(src)
    order = []
    for node in ast.walk(tree):
        if isinstance(node, ast.Call) and isinstance(node.func, ast.Attribute) and node.func.attr == "add_middleware":
            arg = node.args[0] if node.args else None
            name = getattr(arg, "attr", getattr(arg, "id", "?"))
            order.append((name, node.lineno))
    # rate-limit middleware must exist before body-size / trust-origin settings
    names = [n for n, _ in order]
    if "RateLimitMiddleware" in names and "GZipMiddleware" in names:
        rl = next(i for i, n in enumerate(names) if n == "RateLimitMiddleware")
        gz = next(i for i, n in enumerate(names) if n == "GZipMiddleware")
        if rl > gz:
            violations.append({"file": str(f), "rule": "rateLimit-before-gzip", "line": order[gz][1]})
json.dump({"rulesViolated": violations}, open("middleware-order-report.json", "w"), indent=2)
print(json.dumps(violations, indent=2))
sys.exit(1 if violations else 0)
```

## 4. Execution Protocol & Step-by-Step Workflow

1. Run `node middleware-order-validator.js <src-dir>` (or the Python check).
2. Repair each violation in order of nastiest security consequence first:
   - rate-limit after body parser → move rate limit to the top of the chain;
   - auth after routes → elevate auth registration above any route group;
   - error handler not last → move it after the final route/404 handler.
3. For Express, prefer mounting groups (`app.use('/api', router)`) where
   auth applies at the group boundary — the validator treats that as one
   ordered slot.
4. Keep the validator in CI on every entry-file change; order bugs are cheap
   to catch at PR time and brutal to find in an incident.
5. Where the matrix doesn't fit (custom middleware names), map your names into
   the classifier tokens in a `middlewareAliases.json` rather than disabling
   the gate.

## 5. Edge Cases & Error Handling

- Files without any app assembly are skipped (exit `2` only when *nothing*
   matched across all inputs), so mixed monorepos don't false-alarm.
- Dynamic registration (`app.use(condition ? a : b)`) is not statically
   resolvable — the validator flags nothing for it; document such branching
   explicitly in code review.
- Router-mounted handlers (`router.use`) are subgroup-scoped; the validator
   checks top-level `app` ordering, which is where 95% of order bugs live.
- Method-to-classifier matching is conservative (regex on lowercase names) —
   override via aliases if your middleware is imported under custom names.
- The Python AST walk is rune-complete and never executes your code, so it is
   safe against malicious imports.

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…

Newsletter

Get the weekly list of safe new skills, and the ones we pulled.