Express security scan
Skill Dolphinllc/claude-security-skills/skills/defensive/web/express-security-scan
Defensive security scan for Express.js applications. Detects missing helmet, unsafe body-parser limits, broken trust-proxy config, weak cookie/session options, missing CSRF, middleware-ordering bugs (auth registered after route), unvalidated res.sendFile, and unsafe eval of request data. Invoke when the user asks to "review", "audit", or "scan" an Express project.From its SKILL.md
npx -y skills add Dolphinllc/claude-security-skills --skill express-security-scanAssembled from the repository path, not quoted from the project. Check it against their README if it does not work.
One thing to look at
- 1 stars1 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.
SKILL.md
4.5 KB, ~1.1k tokens by cl100k_base, as published. Nobody here has run it
Express Security Scan
Defensive scan for Express.js (4.x / 5.x) applications. Reports findings using the shared scoring schema.
Scope
- Files importing
express,express.Router, or registering middleware on anapp - Session / cookie / CSRF middleware setup
- Route handlers under
routes/,controllers/,api/
Procedure
- Find the
app = express()initializer and followapp.use(...)calls in order — middleware order matters. - For each route handler, trace
req.body/req.params/req.queryinto sinks. - Apply the rules below.
Rules
| ID | Severity | Detection | Fix |
|---|---|---|---|
| EXP-HDR-001 | medium | helmet() not registered before routes | app.use(helmet()) early in middleware chain |
| EXP-BODY-001 | high | express.json() / express.urlencoded() without limit: (default 100kb is fine, explicit is safer; missing limit on body-parser is the actual hit) | Set limit: '1mb' (or smaller) explicitly |
| EXP-PROXY-001 | high | app.set('trust proxy', true) (boolean true) when running behind a single known proxy | Use a numeric hop count or specific subnet — true lets clients spoof X-Forwarded-For |
| EXP-COOKIE-001 | high | Cookie/session set without httpOnly: true and secure: true | Add both; add sameSite: 'lax' or 'strict' |
| EXP-SESS-001 | high | express-session with secret literal in code, or secret: 'keyboard cat' example secret | Read from env; rotate on deploy |
| EXP-SESS-002 | medium | express-session default MemoryStore in production code path | Use Redis/SQL store for prod |
| EXP-CSRF-001 | high | State-changing routes (POST/PUT/PATCH/DELETE) accept session cookies but no CSRF token check | Use csurf (or framework equivalent) on cookie-auth endpoints; or require custom header + SameSite=strict |
| EXP-ORDER-001 | critical | Auth middleware registered after a sensitive route on the same router | Register auth (app.use(auth)) before routes |
| EXP-CORS-001 | high | cors({ origin: true, credentials: true }) (reflects any Origin) | Pin origin to allowlist function |
| EXP-FILE-001 | high | res.sendFile(path.join(BASE, req.params.x)) without a containment check | Resolve and verify with path.resolve(BASE, x).startsWith(BASE + path.sep) |
| EXP-EVAL-001 | critical | eval(req...) / new Function(req...) / vm.runInNewContext(req...) | Never; redesign |
| EXP-JWT-001 | high | jsonwebtoken.verify(token, secret) without algorithms: option | Pass algorithms: ['HS256'] (or your alg); rejects none/alg confusion |
| EXP-ERR-001 | medium | Default error handler missing → stack traces leak in production | Add (err, req, res, next) => { logger.error(err); res.status(500).send('error'); } |
| EXP-RATE-001 | medium | No express-rate-limit on /login, /register, /forgot | Apply per-IP limiter |
Wrong vs. right
EXP-ORDER-001 (auth registered after route)
// ❌ /admin is reachable without auth
app.get('/admin', (req, res) => res.json(stats));
app.use(requireAuth);
// ✅ Auth first
app.use(requireAuth);
app.get('/admin', (req, res) => res.json(stats));
EXP-FILE-001 (path traversal)
// ❌ ../../etc/passwd
app.get('/files/:name', (req, res) => {
res.sendFile(path.join('/var/uploads', req.params.name));
});
// ✅ Containment check
const BASE = path.resolve('/var/uploads');
app.get('/files/:name', (req, res) => {
const target = path.resolve(BASE, req.params.name);
if (!target.startsWith(BASE + path.sep)) return res.sendStatus(404);
res.sendFile(target);
});
EXP-JWT-001 (algorithm not pinned)
// ❌ Attacker can submit alg=none or HS256-signed token where RS256 expected
jwt.verify(token, secret);
// ✅ Pinned
jwt.verify(token, publicKey, { algorithms: ['RS256'], audience, issuer });
References
- Express security best practices: https://expressjs.com/en/advanced/best-practice-security.html
- helmet: https://helmetjs.github.io/
- OWASP Cheat Sheet — Node.js: https://cheatsheetseries.owasp.org/cheatsheets/Nodejs_Security_Cheat_Sheet.html
What ships with it
Read from the repository
Just SKILL.md. No reference files, no scripts.