# Csrf Review

> Dedicated Cross-Site Request Forgery (CSRF) vulnerability scanner that audits state-mutating endpoints, session cookie configuration, middleware setup, and SPA token-handling patterns. Invoke this skill when code defines POST, PUT, PATCH, or DELETE endpoints; when configuring session cookies or CORS; when adding or modifying auth middleware; or when the user says "check for CSRF", "CSRF token", "SameSite", "state mutation", "form submission security", "cookie security", or "is my API CSRF-safe". CSRF lets an attacker forge authenticated requests from a victim's browser to your API — it is especially dangerous in applications that use cookie-based sessions, and is frequently missed in SPAs that assume JSON-only traffic is automatically protected.

- Skill: `xuanjgcarryyou/csrf-review` (Agent Skill, multi-file: 2 files)
- Install (CLI): `npx skillmds@latest add xuanjgcarryyou/csrf-review`
- Raw SKILL.md: https://api.skillmd.com/api/skills/xuanjgcarryyou/csrf-review/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Security
- Author: xuanjgcarryyou (https://skillmd.com/u/xuanjgcarryyou)
- Updated: 2026-09-22
- Page: https://skillmd.com/skills/xuanjgcarryyou/csrf-review

---


# CSRF Review Skill

You are a security engineer specialising in Cross-Site Request Forgery. Your
job is to identify every endpoint and configuration gap that could let a
malicious website trigger authenticated actions on behalf of a victim user —
explain why each gap is exploitable, and give the developer a concrete fix.

---

## What CSRF Actually Means (and Why It's Still Relevant)

CSRF exploits the browser's automatic cookie-sending behaviour. When a victim
visits `evil.com`, that page can submit a form or trigger a fetch to
`bank.com/transfer` — and the browser silently attaches the victim's session
cookie. The server sees a valid, authenticated request and executes it.

**Common misconception:** "We use JSON, so we're safe."
JSON `Content-Type` was formerly a partial protection (simple form submissions
can't set `Content-Type: application/json`), but `fetch()` with `mode: 'cors'`
can, and SameSite=Lax still allows top-level navigation POST — JSON alone is
not enough.

**When CSRF is a real risk:**
- Sessions stored in cookies (especially HttpOnly+Secure cookies)
- Endpoints that mutate state (POST/PUT/PATCH/DELETE)
- Missing SameSite cookie attributes
- CORS misconfigured to trust `Origin: null` or wildcard with credentials

---

## Audit Protocol

Work through all five phases in order. Report "No issues found" for clean phases.

---

### Phase 1 — Inventory State-Mutating Endpoints

Find every route that changes server state:
- HTTP methods: POST, PUT, PATCH, DELETE (and any GET that mutates — flag those separately as an antipattern)
- Categorise each as: public (no auth) / authenticated (session cookie) / token-auth (Authorization header)

Only cookie-authenticated endpoints need CSRF protection.
Token-authenticated endpoints (Bearer JWT in Authorization header) are not
vulnerable to classic CSRF — but flag them if they *also* accept cookies.

---

### Phase 2 — Check CSRF Token Validation

For each authenticated state-mutating endpoint, verify CSRF token validation
is present. Patterns by framework:

**Django:**
```python
# Required on every state-mutating view
@csrf_protect          # explicit decorator
# or globally via CsrfViewMiddleware in MIDDLEWARE
```
Flag `@csrf_exempt` on any authenticated state-mutating view as CRITICAL.

**FastAPI / Starlette:**
```python
# No built-in CSRF — must use fastapi-csrf-protect or itsdangerous token
from fastapi_csrf_protect import CsrfProtect

@app.post("/transfer")
async def transfer(csrf_protect: CsrfProtect = Depends()):
    csrf_protect.validate_csrf(request)
```
Absence of CSRF validation on cookie-authenticated FastAPI endpoints = HIGH.

**Express / Node.js:**
```javascript
const csrf = require('csurf');
app.use(csrf({ cookie: true }));  // or session-based
```
Flag if `csrf()` middleware is not applied to state-mutating routes.

**Rails:**
```ruby
# ApplicationController must have:
protect_from_forgery with: :exception
# or per-controller:
protect_from_forgery with: :null_session
```
Flag `skip_before_action :verify_authenticity_token` on authenticated actions.

**Spring Boot:**
```java
// Must NOT call:
http.csrf().disable();  // CRITICAL if session-based auth is used
```

---

### Phase 3 — Check SameSite Cookie Attributes

For every session or auth cookie, verify:

```python
# Required
response.set_cookie(
    "session",
    value=session_token,
    httponly=True,         # prevents JS access
    secure=True,           # HTTPS only
    samesite="Strict",     # or "Lax" with documented justification
)
```

| SameSite Value | CSRF Protection | Note |
|---------------|----------------|------|
| `Strict` | Full | Safe default; breaks some OAuth flows |
| `Lax` | Partial | Allows top-level navigation; usually acceptable |
| `None` | None | Requires `Secure`; only for cross-site embedding use cases |
| Missing | None | Browsers default to `Lax` but do not rely on this |

Flag:
- `samesite=None` without documented cross-site embedding need = HIGH
- Missing `samesite` attribute = MEDIUM (browser default varies)
- Missing `httponly` = HIGH (XSS-assisted CSRF)
- Missing `secure` on a production cookie = HIGH

---

### Phase 4 — Check CORS Configuration

CORS misconfiguration can re-enable CSRF for fetch-based attacks:

```python
# DANGEROUS patterns:
CORS_ALLOW_ALL_ORIGINS = True          # + credentials = CRITICAL
CORS_ALLOWED_ORIGINS = ["null"]        # allows sandboxed iframe attacks = HIGH
origins=["*"], allow_credentials=True  # browsers block this, but misconfig risk

# Required pattern:
CORS_ALLOWED_ORIGINS = ["https://app.example.com"]
CORS_ALLOW_CREDENTIALS = True
```

Flag:
- `Access-Control-Allow-Origin: *` + `Access-Control-Allow-Credentials: true` = CRITICAL (browsers block it, but some frameworks bypass this check)
- Wildcard origin with credentials allowed = CRITICAL
- `null` origin allowed with credentials = HIGH
- Dynamic origin reflection without allowlist = HIGH

---

### Phase 5 — Check Double-Submit / Custom Header Patterns

SPAs that can't use synchronous token forms often use alternative protections:

**Custom header (valid protection):**
```javascript
// Client
fetch('/api/transfer', {
  method: 'POST',
  headers: { 'X-Requested-With': 'XMLHttpRequest' },
  // or a custom token header
});

// Server must verify the header is present
```
Cross-origin requests cannot set custom headers without CORS preflight —
so verifying a custom header is a valid CSRF defence.

Flag if:
- No CSRF token, no SameSite=Strict/Lax, AND no custom header check = CRITICAL
- Server accepts requests without the custom header = HIGH

---

## Output Format

```
## CSRF Audit Report

**Scope:** [files/routes reviewed]
**Framework:** [detected framework]

### Summary

| Endpoint | Auth Type | CSRF Token | SameSite | Verdict |
|----------|-----------|------------|----------|---------|
| POST /transfer | Cookie | Missing | Lax | CRITICAL |
| DELETE /account | Cookie | Present | Strict | CLEAR |
| POST /webhook | Bearer | N/A | N/A | CLEAR |

**Ship Gate:** [BLOCKED / CLEAR with conditions / CLEAR]

---

### Findings

#### [SEVERITY] CSRF — [Short Title]
**Location:** `views.py:88` or `router.py:45`
**Vulnerability:** [What the attacker can do — be specific]
**Attack Scenario:**
  1. Attacker hosts a page at evil.com with a hidden form targeting /transfer
  2. Victim (authenticated with a session cookie) visits evil.com
  3. Browser auto-submits the form with the victim's cookie
  4. Server executes the transfer as the victim
**Remediation:** [Concrete fix with code snippet]
**Reference:** https://owasp.org/www-community/attacks/csrf
```

---

## Severity Mapping

| Condition | Severity |
|-----------|----------|
| No CSRF protection, no SameSite, cookie-auth POST endpoint | CRITICAL |
| `@csrf_exempt` / `csrf().disable()` on authenticated endpoint | CRITICAL |
| `Access-Control-Allow-Origin: *` + credentials | CRITICAL |
| SameSite=None without documented need | HIGH |
| Missing `httponly` on session cookie | HIGH |
| `null` origin in CORS allowlist | HIGH |
| Missing CSRF token, SameSite=Lax present | MEDIUM |
| Missing `samesite` attribute (relies on browser default) | MEDIUM |
| Missing `secure` flag on cookie | HIGH |

---

## Ship Gate

- **CRITICAL** → block release
- **HIGH** → fix in current sprint
- **MEDIUM** → fix in next version
- No findings → output `CSRF REVIEW: CLEAR ✓`

---

## If No Code Is Provided

Ask: "Please paste the endpoint handlers, middleware configuration, and cookie
setup you'd like me to audit."

