Skip to main content
interlace
Plugin: express-securityRules

require-csrf-protection

The rule provides LLM-optimized error messages (Compact 2-line format) with actionable security guidance:

Require CSRF protection middleware for state-changing HTTP methods

Severity: 🔴 High
CWE: CWE-352

Error Message Format

The rule provides LLM-optimized error messages (Compact 2-line format) with actionable security guidance:

🔒 CWE-352 OWASP:A01 CVSS:8.8 | Cross-Site Request Forgery (CSRF) detected | HIGH
   Fix: Review and apply the recommended fix | https://owasp.org/Top10/A01_2021/

Message Components

ComponentPurposeExample
Risk StandardsSecurity benchmarksCWE-352 OWASP:A01 CVSS:8.8
Issue DescriptionSpecific vulnerabilityCross-Site Request Forgery (CSRF) detected
Severity & ComplianceImpact assessmentHIGH
Fix InstructionActionable remediationFollow the remediation steps below
Technical TruthOfficial referenceOWASP Top 10

Rule Details

This rule detects an Express route that mutates state on behalf of a browser-held credential without a CSRF token.

The two preconditions

CSRF is not "a state-changing method without a token". It is the browser attaching a credential the attacker cannot read but can cause to be sent. Two things must hold before the control means anything, and the rule abstains unless both do:

  1. Ambient credential material exists in the file. A cookie, a session, a cookie-parser / express-session / passport import, req.session, req.cookies, res.cookie(…). With none of them — a bearer-token API, an OAuth callback, a form-post demo — a cross-site request carries no authority and a token would protect nothing.
  2. The route is authenticated. Auth middleware in its own chain, a handler that reads a principal, or a router-wide app.use(requireAuth). An endpoint that requires no principal has nothing for a forged request to ride.

Partition with require-route-authentication

Precondition 2 is the exact complement of require-route-authentication's test, so the two rules never report the same route. An unauthenticated critical route is CWE-306 and that rule owns it; adding a CSRF token there fixes no vulnerability. Before 2026-08-12 both rules reported the same seven routes on the 8-repo corpus.

Examples

❌ Incorrect

import express from 'express';
import session from 'express-session';

const app = express();
app.use(session({ secret }));
app.use(requireAuth);

// A session cookie, an authenticated route, no token - VULNERABLE
app.post('/transfer', (req, res) => {
  transferFunds(req.body);
});

✅ Correct

import express from 'express';
import csrf from 'csurf';

const app = express();
const csrfProtection = csrf({ cookie: true });

// Global CSRF protection
app.use(csrfProtection);

app.post('/transfer', (req, res) => {
  transferFunds(req.body);
});

// Or per-route protection
app.post('/transfer', csrfProtection, (req, res) => {
  transferFunds(req.body);
});
// No cookie and no session anywhere in the file: nothing to forge with.
// Not reported.
const router = express.Router();
router.post('/select-authenticator', (req, res, next) => proceed(req.body));

// Unauthenticated route: require-route-authentication owns this site.
// Not reported here.
app.post('/signup', createAccount);

Options

OptionTypeDefaultDescription
allowInTestsbooleanfalseAllow missing CSRF in test files
protectedMethodsstring[]["post","put","patch","delete"]HTTP methods that require CSRF protection
ignorePatternsstring[][]Route patterns to ignore
{
  "rules": {
    "express-security/require-csrf-protection": [
      "error",
      {
        "ignorePatterns": ["/api/webhook", "/api/public/.*"]
      }
    ]
  }
}

When Not To Use It

Disable for:

  • Stateless API-only backends using token-based auth (JWT)
  • Webhook endpoints that use signature verification
  • Public APIs without session-based authentication

Known False Negatives

The following patterns are not detected due to static analysis limitations:

CSRF Middleware in External Router

Why: Middleware applied in other modules is not tracked.

// ❌ NOT DETECTED - CSRF in external router file
// routes.ts applies csrf, but main.ts doesn't see it
import { router } from './routes';
app.use(router);

Mitigation: Apply CSRF globally in main file. Document middleware location.

Custom CSRF Implementation

Why: Custom CSRF token validation is not recognized.

// ❌ NOT DETECTED - Custom CSRF check
app.post('/transfer', (req, res) => {
  if (req.headers['x-csrf-token'] !== req.session.csrf) {
    return res.status(403).send('Invalid CSRF');
  }
  // ... handle request
});

Mitigation: Configure rule to recognize custom middleware names.

Framework CSRF Abstraction

Why: Framework-specific CSRF is not detected.

// ❌ NOT DETECTED - Next.js API routes
export async function POST(req) {
  // Next.js has different CSRF handling
}

Mitigation: Use framework-specific linting. Configure ignorePatterns.

Token-Based API with Session Fallback

Why: Rule can't determine if endpoint uses session or JWT.

// ❌ FALSE POSITIVE RISK - JWT API doesn't need CSRF
app.post('/api/data', jwtAuth, (req, res) => {
  // Safe: JWT auth, not session-based
});

Mitigation: Use ignorePatterns for API routes. Document auth strategy.

Further Reading

Did this rule catch something? Star the repo to get new CWE coverage as we ship it — or follow the AI-code-security benchmarks behind these rules.