SEC008-csrf-missing — State-Changing HTTP Handler Without CSRF Protection
Dimension: Security Default severity: Medium CWE: CWE-352 OWASP: A01:2021 – Broken Access Control Languages: All (Python, JS/TS, Rust) Last reviewed: 2026-05-07
What it detects
Flags handler functions in recognised web-framework files that accept state-changing HTTP methods (POST, PUT, DELETE, PATCH) when no CSRF protection token is present anywhere in the file.
A finding is emitted for each handler function satisfying all three conditions:
-
Recognised web framework import — the file imports at least one of:
express,koa,fastify,body-parser(JS/TS);flask,fastapi,django(Python);axum,actix_web,rocket,warp(Rust). -
State-changing handler marker in the source region surrounding the function declaration:
- JS/TS:
app.post(,app.put(,app.delete(,app.patch(,router.post(,router.put(,router.delete(,router.patch( - Python:
@app.post(,@app.put(,@app.delete(,@app.patch(,@app.route(,@router.post(,@router.put(,@blueprint.route( - Rust:
Router::new(),.route(,#[post,#[put,#[delete,#[patch,web::post(),web::put(),web::delete()
- JS/TS:
-
No CSRF protection — none of the following tokens appear in imports or non-comment source lines:
csrf,csurf,csrf_protect,CSRFProtect,flask_wtf,csrf_token,XSRF,xsrf.
One finding is emitted per matching handler function.
Why it matters
Cross-Site Request Forgery (CSRF) exploits the trust a web application has in the user's browser. An attacker can trick an authenticated user into involuntarily submitting a state-changing request (fund transfer, password change, account deletion) by embedding a hidden form or image on a malicious page. OWASP A01:2021 (Broken Access Control) covers this class of vulnerability.
Stateless REST APIs using Authorization headers are not vulnerable; traditional
session-cookie applications and SPAs that rely on cookies for authentication are.
Being conservative, the analyzer only emits findings when the file both imports a
recognised web framework and lacks any CSRF token in its source.
Configuration
This rule has no configurable threshold. Disable per project:
[rules.SEC008-csrf-missing]
enabled = false
Examples flagged
Python — Flask handler with no CSRF protection:
from flask import Flask, request
app = Flask(__name__)
@app.post("/transfer")
def transfer(): # flagged: state-changing handler, no CSRF protection
amount = request.json.get("amount", 0)
return {"status": "ok", "amount": amount}
JS/TS — Express handler without csurf middleware:
import express from "express";
const app = express();
app.use(express.json());
app.post("/withdraw", (req, res) => { // flagged: no csurf middleware
const amount = req.body.amount;
res.json({ status: "ok", amount });
});
Rust — Axum router without CSRF middleware:
use axum::Router;
use axum::routing::post;
pub async fn transfer_handler() -> &'static str { "transferred" }
pub fn app() -> Router { // flagged: .route( with post handler, no csrf layer
Router::new().route("/transfer", post(transfer_handler))
}
Examples not flagged
Python — Flask-WTF CSRFProtect present:
from flask import Flask, request
from flask_wtf.csrf import CSRFProtect
app = Flask(__name__)
csrf = CSRFProtect(app) # not flagged: CSRFProtect in imports
@app.post("/transfer")
def transfer():
return {"status": "ok"}
JS/TS — csurf middleware applied:
import express from "express";
import csurf from "csurf"; // not flagged: csurf import present
const app = express();
app.use(csurf());
app.post("/withdraw", (req, res) => {
res.json({ status: "ok" });
});
Rust — CSRF layer from tower_csrf:
use axum::Router;
use axum::routing::post;
use tower_csrf::CsrfLayer; // not flagged: tower_csrf import present
pub async fn transfer_handler() -> &'static str { "transferred" }
pub fn app() -> Router {
Router::new()
.route("/transfer", post(transfer_handler))
.layer(CsrfLayer::new())
}
Fix guidance
- Express / Node.js: install
csurfand apply it as middleware before state-changing routes:app.use(csurf({ cookie: true })). - Flask / Python: use
Flask-WTF; callCSRFProtect(app)and include{{ form.csrf_token }}in every HTML form. - Axum / Rust: add the
axum-csrfortower_csrfcrate and register the CSRF layer:.layer(CsrfLayer::new()). - For JSON APIs that do not use cookies, ensure the API requires an
Authorizationheader (Bearer token) so browser-initiated cross-origin requests cannot automatically include credentials.
Implementation
- Source:
crates/zuit-analyzers/src/csrf_missing.rs - Severity / supported languages: see
RuleMetain source.