Last reviewed: 2026-10-17

Direct answer

Prevent cross-site request forgery (CSRF) in a coding-agent web patch by identifying every state-changing browser request, deciding whether the endpoint authenticates with ambient browser credentials, and requiring evidence that the request was intentionally created by the application. For cookie-backed sessions, use a framework’s maintained CSRF middleware or a synchronizer token. For stateless APIs, prefer an explicit bearer credential in a header that an attacker cannot cause the browser to attach automatically, while still reviewing any cookie, refresh-token, or browser bootstrap path. Add defense in depth with deliberate SameSite cookie settings and an Origin or Referer check where the deployment can define a trusted origin.

Do not accept a patch because it enables CORS, checks only the Content-Type, or hides the session cookie behind HttpOnly. CORS governs whether a browser may expose a response to another origin; it is not a general guarantee that a cross-origin state-changing request cannot be sent. HttpOnly prevents JavaScript from reading a cookie, but the browser still sends that cookie on requests that meet its cookie rules. The OWASP CSRF Prevention Cheat Sheet describes token patterns, custom headers, Fetch Metadata, SameSite limits, and origin checks. MDN’s Set-Cookie reference defines the browser cookie attributes that form the credential boundary.

This is a review and test workflow for an agent-written change, not a claim that one header or middleware setting secures every application. Bind the final decision to the session mechanism, browser clients, API clients, proxy behavior, and supported framework versions in the repository.

Who this is for

This guide is for maintainers reviewing agent-written route handlers, controllers, server-rendered forms, single-page applications, browser SDKs, and API gateways. Start when a patch adds a POST, PUT, PATCH, or DELETE route; changes session or cookie settings; introduces a fetch wrapper; enables cross-origin requests; moves a form into a template; or adds an endpoint that can create, update, delete, purchase, upload, or enqueue work.

It also applies to endpoints that appear read-only but trigger a side effect, such as a GET that signs a user up for notifications, changes a preference, rotates a token, or starts a job. Make the method and side-effect contract explicit before deciding which checks are required.

The examples assume a browser can carry a session cookie. A service-to-service endpoint with no browser credential has a different threat model, but a patch can accidentally add cookie authentication or a browser-facing proxy later. Review the credential path, not just the route’s current caller.

Key takeaways

  • Inventory every state-changing route and every browser credential that can reach it. Do not rely on HTTP method names alone.
  • Use the framework’s CSRF protection consistently. A token embedded in a form or sent in a custom request header must be validated before the handler reads or changes protected state.
  • Treat a synchronizer token or signed double-submit token as a request-integrity proof, not as a secret that a page can freely echo into a third-party origin.
  • Set SameSite deliberately, but document its limits. It is defense in depth and can interact with redirects, subdomains, embedded flows, and browser compatibility.
  • Check Origin, then a carefully defined Referer fallback when the deployment can state its trusted origins. Fail closed on an absent or unexpected value for operations that require the check.
  • Distinguish CORS response exposure from request authorization. An allowlisted origin does not replace a CSRF token or an equivalent request proof.
  • Test an ordinary allowed browser flow and known hostile cross-site cases through the real middleware, router, cookie, and proxy stack.
  • Make failures side-effect free. A rejected request must not mutate a record, enqueue a job, rotate a credential, or leak a useful token in the response.

Sources checked

  • OWASP: Cross-Site Request Forgery Prevention Cheat Sheet — fetched live on 2026-10-17. It supports synchronizer tokens, signed double-submit cookies, custom headers, Fetch Metadata, SameSite limitations, origin checks, and client-side CSRF review.
  • MDN: Set-Cookie header — fetched live on 2026-10-17. It supports the browser behavior and attributes for SameSite, Secure, HttpOnly, Domain, Path, and cookie prefixes.
  • Django: How to use CSRF protection — fetched live on 2026-10-17. It documents middleware, form tokens, the X-CSRFToken AJAX pattern, same-origin fetch, default 403 handling, cache interaction, and Client(enforce_csrf_checks=True).
  • MDN: Cross-Origin Resource Sharing — fetched live on 2026-10-17. It supports separating cross-origin response reading from the browser’s ability to send a request, so CORS is not treated as a complete CSRF defense.

These are maintained primary technical references for an evergreen review task. The recommendations, route inventory, fixtures, and acceptance matrix below are engineering guidance derived from those references. They are not a report of a newly disclosed incident or a test of a reader’s application.

Contract details to verify

Map credentials before reviewing the handler

Write a small table for every changed endpoint:

Contract elementDecision to recordEvidence to retain
CredentialCookie, bearer header, mTLS, or a combinationAuthentication middleware and browser request trace
State changeWhich records, jobs, files, or credentials can changeHandler call graph and side-effect inventory
Request proofSynchronizer token, signed double-submit token, custom header, origin policy, or documented exemptionExact middleware, validation order, and failure code
Trusted originsCanonical scheme, host, and port for browser clientsDeployment configuration and proxy normalization rule
Cookie scopeSecure, HttpOnly, SameSite, Domain, Path, and prefixesSet-Cookie response and environment-specific settings
Client contractServer-rendered form, same-origin JavaScript, mobile client, or service callerVersioned client code and migration plan
Failure behaviorResponse class, body policy, and zero-side-effect ruleIntegration assertions and sanitized logs

The first review question is whether the browser automatically supplies an authority-bearing credential. A session cookie is ambient: a malicious site can cause a browser to send a request even though it cannot read the response. The request therefore needs a second proof that the application generated or authorized it. A bearer token in an Authorization header changes the browser threat model because a cross-origin form cannot set that header, but review JavaScript storage, refresh endpoints, service workers, and any proxy that converts a cookie into a header.

Do not let a submitted user_id, account number, or CSRF field select the authority it is supposed to prove. Authentication identifies the caller; the token and origin checks establish request intent; authorization still decides whether this caller may perform this action on this object. Pair this workflow with the site’s object-authorization review for the object-level decision.

Select one token pattern and keep its boundary clear

For a server-rendered form, the synchronizer-token pattern is direct: generate a token associated with the user’s authenticated session, place it in the form or a safe same-origin page, and compare the submitted value using a constant-time comparison before state changes. Keep the token out of URLs and third-party requests. Rotate or invalidate it according to the framework’s documented session behavior, and avoid writing it into ordinary logs.

For a cookie-based double-submit design, the cookie and request value must be bound so an attacker cannot set both values independently. OWASP recommends a signed double-submit pattern over a naive comparison of two attacker-influenced values. Use the framework implementation when one exists; a hand-written token check in an agent patch is a high-risk review target.

For AJAX, require the token in a custom header that a cross-origin HTML form cannot supply, or use the framework’s documented header convention. Django’s current documentation shows X-CSRFToken on a same-origin fetch() request, describes where to obtain the token depending on cookie settings, and notes that failures return 403 by default. The client must set the header only for the intended origin; a generic fetch wrapper that forwards a token to arbitrary URLs is itself a client-side CSRF risk.

Use Secure for cookies that carry authentication over HTTPS. Use HttpOnly when client JavaScript does not need to read the cookie, but do not describe it as CSRF protection. Set SameSite=Lax or Strict according to the product’s cross-site navigation and embedding needs; SameSite=None requires Secure and intentionally permits cross-site sending. A broad Domain can expose a cookie to more subdomains than the route requires, while a host-only cookie and an appropriate __Host- prefix can narrow scope when the deployment meets the prefix rules documented by MDN.

The right setting depends on redirects, federated login, embedded widgets, legacy browsers, and whether sibling subdomains are trusted. Record the product exception instead of changing SameSite to make a test pass. Keep token validation and origin checks in place when the cookie setting cannot provide the required invariant.

Do not confuse CORS with request integrity

CORS response headers tell a browser which scripts may read a response. They do not turn a cookie-authenticated state change into an intentional request. A patch that adds Access-Control-Allow-Origin and Access-Control-Allow-Credentials needs a strict, reviewed origin allowlist and still needs CSRF protection for cookie-authenticated mutations. Avoid reflecting the request’s Origin value into the response.

A custom header can be useful because a simple cross-origin form cannot set it, but preflight behavior is not a substitute for server-side validation. If the same patch also adds a browser-triggered server-side fetch, review its separate network boundary with the site’s SSRF guide . Validate the header or token at the route boundary, and make sure a proxy does not strip it or accept a weaker alternate spelling.

Happy-path operator workflow

  1. Freeze the agent revision and inventory every browser-reachable state change, including routes hidden behind GraphQL mutations, RPC names, form actions, upload handlers, and queue endpoints.
  2. Trace each route’s credentials through reverse proxies, session middleware, cookie settings, router, authorization, and side-effect code. Record any route that changes a state under GET.
  3. Write the request contract: accepted methods, token source, trusted origins, cookie attributes, allowed clients, and exact failure behavior.
  4. Use the maintained framework middleware or a narrowly scoped library integration. Reject missing, malformed, expired, or mismatched tokens before deserialization of sensitive fields or application-side effects.
  5. Add a same-origin browser test that obtains a legitimate token, submits a valid form or fetch request, and verifies the intended state change.
  6. Add proxy coverage for scheme and host normalization. The origin comparison must use the deployment’s canonical external origin, not an attacker-controlled forwarded header.
  7. Review the diff for alternate handlers, retries, background jobs, cache layers, and error paths that bypass the check.
  8. Retain a small evidence record: policy version, route identifier, client class, cookie policy, expected decision, response class, and side-effect counts.

A successful same-origin test proves only that the client can satisfy the contract. It does not prove a foreign origin cannot submit a valid request or that a second route does not bypass middleware.

Error-path operator workflow

If a request has no token, an invalid token, an unexpected Origin, a disallowed Sec-Fetch-Site, or a missing trusted-origin configuration, stop before the handler performs work. Return the documented failure class, omit protected response data, and record a stable reason code rather than the raw token or attacker-controlled origin.

If a reverse proxy rewrites Host, Origin, or scheme information, pause the rollout and reconcile the canonical-origin rule. Do not accept every X-Forwarded-Host value or disable origin checks because a staging proxy is misconfigured. Fix the trusted proxy boundary and rerun the tests.

If a token check fails after a side effect, classify the patch as failed even when the response is 403. Reconcile created records, queue messages, uploads, or credential rotations before retrying. A response status is not evidence that the mutation did not happen.

If the endpoint must support a cross-origin client, document the exact origin set, credentials mode, preflight behavior, token delivery, and revocation path. Do not turn the route into a broad cookie-authenticated endpoint to avoid client changes. Add a separate service credential or a narrowly scoped bridge when that is the actual product contract.

Prove the boundary with a browser-focused test matrix

Run these through the real router and middleware, using disposable users and records:

RequestExpected evidence
Same-origin form with a valid session cookie and tokenIntended mutation occurs once; response contains only approved data.
Same-origin JavaScript request with the documented custom headerIntended mutation occurs once; token is not sent to an unrelated origin.
Cross-origin form with the session cookie but no tokenRejected before mutation; zero queued work and unchanged records.
Cross-origin request with a guessed or stale tokenRejected; no useful token or protected fields in the body.
Cross-origin request with an Origin from an untrusted siteRejected according to the origin policy, even if a cookie is present.
Request with no Origin where the policy requires itControlled failure or the documented safe fallback; no permissive default.
CORS preflight from an untrusted originNo credentialed mutation path is opened; response does not reflect the origin.
SameSite=None cookie in a supported cross-site product flowOnly the explicitly allowed client succeeds and still supplies the required request proof.
GET route that changes stateRoute is converted to a safe method or receives the same request-integrity review.
Token failure after a retry or redirectNo duplicate mutation, credential replay, or queue submission.
Policy or session store unavailableFail closed with no protected side effect; evidence records the dependency failure.

For Django projects, instantiate the documented test client with enforce_csrf_checks=True; otherwise a default test client may relax middleware checks. For other frameworks, use the equivalent real middleware mode and a browser or HTTP client that honors cookies, redirects, and origin headers. Keep direct unit tests for token helpers, but do not treat them as proof that every route invokes the helper before effects.

Check response caching as well. Django documents that CSRF cookies and Vary: Cookie interact with caching, and that per-view cache decorators can run before middleware has added the correct variation. A cached page that omits or shares a token incorrectly can make an otherwise sound handler fail open or fail legitimate clients.

Failure modes

CORS is treated as CSRF protection. CORS controls response exposure. Keep token or equivalent request-integrity checks for cookie-authenticated mutations.

HttpOnly is treated as a CSRF fix. It limits script access to a cookie; the browser still attaches the cookie when its rules permit. Keep the request proof.

Only POST is protected. A state-changing GET, GraphQL mutation, upload route, or RPC method remains a CSRF surface. Inventory effects rather than method names.

A token is accepted from any place in the request. Do not accept a query-string token, arbitrary cookie/header combination, or a body field whose parser can be confused. Use one documented source and constant-time comparison.

The patch trusts forwarded origin data. Normalize through an explicitly trusted proxy and compare against a configured canonical origin. Never let a client choose the trust list.

SameSite=Strict is added without a product review. Login, federated identity, embedded features, and cross-site navigation can break. Treat cookie settings as a documented contract and keep independent request validation.

A generic client wrapper sends the token everywhere. Scope token injection to same-origin, known API paths. Client-side CSRF can turn attacker-controlled URLs or method choices into authenticated requests.

The failure response leaks the token or record. Return a stable error class, avoid echoing request values, and assert that denied responses contain no protected data.

The test uses a relaxed framework client. Enable real CSRF checks and include cookies, redirects, proxy headers, and the actual route stack.

An agent weakens the gate to make a fixture pass. Watch for csrf_exempt, a broad origin wildcard, unconditional token acceptance, a cookie-only check, or a catch block that continues after validation failure. Require a written reason for every exemption and test it separately.

FAQ

Is a bearer token in an Authorization header always safe from CSRF?

No. A normal cross-origin HTML form cannot set that header, which removes one browser delivery path, but JavaScript token storage, refresh cookies, service workers, browser extensions, and proxies can create other paths. Review how the token is acquired, attached, revoked, and converted at every boundary.

Is SameSite enough?

Usually treat it as defense in depth. Its behavior depends on the requested site, navigation mode, browser, cookie scope, redirects, and product exceptions. A synchronizer or signed token and a deliberate origin policy provide a clearer request-intent contract.

Can I exempt a webhook or mobile route?

Only when the route cannot receive browser ambient credentials under the deployment contract, or when it uses a separate authenticated protocol with its own replay and origin model. Record the exemption, bind it to a route and credential type, and test that a browser session cannot reach a side effect through it.

Should the server require both a token and Origin?

That can be a useful defense-in-depth policy when the deployment has reliable origin information. Define how absent origins are handled for non-browser clients and fail closed for the operations that require the check. Avoid adding an origin requirement without accounting for proxies and legitimate clients.

Does a 403 prove no mutation occurred?

No. Assert database, queue, upload, email, and credential state after the request. An error response after an earlier side effect is a failed boundary.

Reader next step

Choose one state-changing route touched by the next coding-agent patch. Map its browser credentials, write a caller-token-origin contract, and add a same-origin success test plus a cross-origin request with a known valid session cookie and no token. Run the tests through the real middleware with strict CSRF checks, assert zero side effects on every rejection, then review SameSite, CORS, cache variation, and proxy-origin handling as separate evidence. Approve the patch only when a legitimate browser client still works and an attacker-controlled page cannot satisfy the route’s request-intent proof.