Why AI tools produce this: AI coding tools produce this regularly, typically when prompted for a shortcut or a quick fix.
Why this matters
A static state provides ZERO CSRF protection: the whole point is an unguessable, per-request value that you store and then compare on the callback. A literal that ships in your source is known to everyone and identical on every request, so an attacker can forge a matching callback.
Generate state fresh per request from a CSPRNG (crypto.randomBytes(32).toString('hex') / crypto.getRandomValues), persist it in the session/cookie, and verify it when the provider redirects back.
VULNERABLE
vulnerable.ts
// Hardcoded, constant `state` — identical on every request, so it provides no// CSRF protection at all.// ruleid: auth.oauth.static-stateexport const authorizeUrl = 'https://accounts.google.com/o/oauth2/v2/auth?client_id=abc&response_type=code&state=xyz123&redirect_uri=https%3A%2F%2Fapp.example.com%2Fcb';export function buildAuthorize(res: { redirect: (url: string) => void }) { // ruleid: auth.oauth.static-state const params = new URLSearchParams({ client_id: 'abc', response_type: 'code', redirect_uri: 'https://app.example.com/cb', scope: 'openid email', state: 'static-state-value', }); res.redirect(`https://accounts.google.com/o/oauth2/v2/auth?${params.toString()}`);}
SAFE
safe.ts
import { randomBytes } from 'node:crypto';// Per-request `state` from a CSPRNG, stored for callback verification.export function buildAuthorize( res: { redirect: (url: string) => void }, store: (s: string) => void,) { const state = randomBytes(32).toString('hex'); store(state); // ok: auth.oauth.static-state const params = new URLSearchParams({ client_id: 'abc', response_type: 'code', redirect_uri: 'https://app.example.com/cb', scope: 'openid email', state, }); res.redirect(`https://accounts.google.com/o/oauth2/v2/auth?${params.toString()}`);}// Inline URL whose state is interpolated per request (template literal) — not a// hardcoded constant.export function inlineDynamic(state: string): string { // ok: auth.oauth.static-state return `https://accounts.google.com/o/oauth2/v2/auth?client_id=abc&response_type=code&state=${state}`;}// Per-literal proof / negative control: `response_type=` lives in ONE string// literal and a constant `state=` in a DIFFERENT one. The old two-regex form// combined them across literals into a false positive; the single per-literal// regex must not flag either of these.// ok: auth.oauth.static-stateexport const authEndpoint = 'https://accounts.google.com/o/oauth2/v2/auth?client_id=abc&response_type=code';// ok: auth.oauth.static-stateexport const someStateString = 'https://example.com/cb?state=teapot';
Suppressing this rule
If a finding is a genuine false positive, scope the suppression to the exact line and leave a reason, never disable the rule project-wide. Disable directives are line-scoped by design.