Skip to content

VNX-352: Cross-Site Request Forgery protection disabled (Django, Flask-WTF, Node.js, Rails)

Reports a web framework's built-in CSRF protection switched off: Django @csrf_exempt and a MIDDLEWARE list without CsrfViewMiddleware, Flask-WTF WTF_CSRF_ENABLED = False and csrf exemptions, Node.js csrf: false / checkOrigin: false / Apollo csrfPrevention: false / csurf ignoreMethods with POST, and Rails skip_before_action :verify_authenticity_token, protect_from_forgery except:, allow_forgery_protection = false or an ApplicationController with no protect_from_forgery.

Updated 02 Nov 2026Checked against cli@de9b52a
Field Value
Rule ID VNX-352
Severity High
SARIF level error
Kind sast
Languages JavaScript / TypeScript, Python, Ruby
CWE CWE-352
CAPEC CAPEC-66
ATT&CK T1190
CWSS vector CWSS:1.0/TI:H/AP:A/AL:A/IC:N/FC:LT/RP:N/RL:A/AV:I/AS:N/IN:A/SC:A/BI:H/DI:H/EX:H/EC:N/P:H
Tags csrf cwe-352
Source internal/sast/rules/vnx-352.rego

Overview

VNX-352 maps to CWE-352: Cross-Site Request Forgery (CSRF). It reports the places where an application turns off the CSRF defence its framework already provides:

  • Django: @csrf_exempt, method_decorator(csrf_exempt, ...), csrf_exempt(view), and a MIDDLEWARE/MIDDLEWARE_CLASSES list without django.middleware.csrf.CsrfViewMiddleware (a commented-out entry counts as absent; MIDDLEWARE += [...CsrfViewMiddleware] elsewhere in the file counts as present);
  • Flask-WTF: WTF_CSRF_ENABLED = False, WTF_CSRF_CHECK_DEFAULT = False, app.config["WTF_CSRF_ENABLED"] = False, app.config.update(WTF_CSRF_ENABLED=False), @csrf.exempt, a form class Meta: csrf = False and meta={"csrf": False};
  • Node.js: csrf: false in an object literal (lusca, Sails config/security.js), SvelteKit/Astro checkOrigin: false, Apollo Server csrfPrevention: false, and csurf ignoreMethods listing POST, PUT, PATCH or DELETE;
  • Rails: skip_before_action (or Rails 4 skip_before_filter) :verify_authenticity_token, protect_from_forgery except:/only:, skip_forgery_protection, allow_forgery_protection = false outside config/environments/test.rb, and an ApplicationController < ActionController::Base with no protect_from_forgery when config/application.rb neither calls load_defaults 5.2 or later nor sets default_protect_from_forgery.

Not reported: settings in a class whose name contains Test or under an if ... testing branch, test files, the import of csrf_exempt, csrf_protect/ensure_csrf_cookie, text in string literals and comments, and ActionController::API controllers. Spring Security is covered by VNX-JAVA-010 and ASP.NET MVC by VNX-CS-005.

Why This Matters

With the CSRF check off, any page the victim visits can submit a form or a fetch to the application, and the browser attaches the victim’s session cookie. Transfers, password changes and deletions then run as the victim.

What Gets Flagged

# FLAGGED
@csrf_exempt
def cancel_order(request, order_id): ...

class Config:
    WTF_CSRF_ENABLED = False
// FLAGGED
app.use(lusca({ csrf: false }));
app.use(csurf({ cookie: true, ignoreMethods: ['GET', 'HEAD', 'OPTIONS', 'POST'] }));
# FLAGGED
class PaymentsController < ApplicationController
  skip_before_action :verify_authenticity_token
end
# NOT FLAGGED: the switch-off is confined to the test configuration
class TestingConfig(Config):
    WTF_CSRF_ENABLED = False

Remediation

  1. Remove the exemption and send the CSRF token from the client (Django {% csrf_token %}, Flask-WTF form.hidden_tag(), Rails csrf_meta_tags).
  2. Keep CsrfViewMiddleware in MIDDLEWARE and protect_from_forgery with: :exception (or load_defaults 5.2+) in Rails.
  3. For an endpoint that genuinely cannot carry a token (a signed webhook), verify the request’s signature instead, and keep the exemption to that single view.
  4. Set session cookies SameSite=Lax or Strict as an additional layer.

References

Suppressing this rule

Put a nosec comment on the line, or on line 1 to cover the whole file. Use the comment marker of the file’s language (#, //, -- or ;):

# nosec vnx-352
// nosec vnx-352

Or record an ignore rule: vulnetix ignore add --rule vnx-352 --reason "...". See Suppressing findings.

Edit this pageSources: cli/internal/sast/rules/vnx-352.rego, cli/internal/sast/docs/vnx-352.md
esc
↑ ↓ move↵ openSearches in your browser. A key like autonomy.max or a code like threshold_required jumps straight to it.