agentsclimarketplace

Express security scan

Skill Dolphinllc/claude-security-skills/skills/defensive/web/express-security-scan

Defensive security skills for Claude Code and the Claude Agent SDK — web applications and generative AI systems.

Install
npx -y skills add Dolphinllc/claude-security-skills --skill express-security-scan

Assembled 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.

What its author says it does

Copied from the file, not written here

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.

SKILL.md

4.5 KB, 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 an app
  • Session / cookie / CSRF middleware setup
  • Route handlers under routes/, controllers/, api/

Procedure

  1. Find the app = express() initializer and follow app.use(...) calls in order — middleware order matters.
  2. For each route handler, trace req.body/req.params/req.query into sinks.
  3. Apply the rules below.

Rules

IDSeverityDetectionFix
EXP-HDR-001mediumhelmet() not registered before routesapp.use(helmet()) early in middleware chain
EXP-BODY-001highexpress.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-001highapp.set('trust proxy', true) (boolean true) when running behind a single known proxyUse a numeric hop count or specific subnet — true lets clients spoof X-Forwarded-For
EXP-COOKIE-001highCookie/session set without httpOnly: true and secure: trueAdd both; add sameSite: 'lax' or 'strict'
EXP-SESS-001highexpress-session with secret literal in code, or secret: 'keyboard cat' example secretRead from env; rotate on deploy
EXP-SESS-002mediumexpress-session default MemoryStore in production code pathUse Redis/SQL store for prod
EXP-CSRF-001highState-changing routes (POST/PUT/PATCH/DELETE) accept session cookies but no CSRF token checkUse csurf (or framework equivalent) on cookie-auth endpoints; or require custom header + SameSite=strict
EXP-ORDER-001criticalAuth middleware registered after a sensitive route on the same routerRegister auth (app.use(auth)) before routes
EXP-CORS-001highcors({ origin: true, credentials: true }) (reflects any Origin)Pin origin to allowlist function
EXP-FILE-001highres.sendFile(path.join(BASE, req.params.x)) without a containment checkResolve and verify with path.resolve(BASE, x).startsWith(BASE + path.sep)
EXP-EVAL-001criticaleval(req...) / new Function(req...) / vm.runInNewContext(req...)Never; redesign
EXP-JWT-001highjsonwebtoken.verify(token, secret) without algorithms: optionPass algorithms: ['HS256'] (or your alg); rejects none/alg confusion
EXP-ERR-001mediumDefault error handler missing → stack traces leak in productionAdd (err, req, res, next) => { logger.error(err); res.status(500).send('error'); }
EXP-RATE-001mediumNo express-rate-limit on /login, /register, /forgotApply 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

Keep looking

Skills are one crate of 328,083. Ordering is by how many stacks a row turns up in, so the top of any crate is what has actually been picked rather than what has the most stars.