bytedance/deer-flow · error · HTTPException
CSRF token mismatch.
Error message
CSRF token mismatch.
What it means
HTTP 403 from the same CSRF guard: both the csrf_token cookie and X-CSRF-Token header are present, but secrets.compare_digest finds them unequal. This indicates the header was not derived from the current cookie — stale header, stale cookie, multiple tabs after cookie rotation, or a client minting its own value.
Source
Thrown at backend/app/gateway/langgraph_auth.py:55
"""
method = getattr(request, "method", "") or ""
if method.upper() not in _CSRF_METHODS:
return
if is_auth_disabled():
return
cookie_token = request.cookies.get("csrf_token")
header_token = request.headers.get("x-csrf-token")
if not cookie_token or not header_token:
raise Auth.exceptions.HTTPException(
status_code=403,
detail="CSRF token missing. Include X-CSRF-Token header.",
)
if not secrets.compare_digest(cookie_token, header_token):
raise Auth.exceptions.HTTPException(
status_code=403,
detail="CSRF token mismatch.",
)
@auth.authenticate
async def authenticate(request):
"""Validate the session cookie, decode JWT, and check token_version.
Same validation chain as Gateway's get_current_user_from_request:
cookie → decode JWT → DB lookup → token_version match
Also enforces CSRF on state-changing methods.
"""
# CSRF check before authentication so forged cross-site requests
# are rejected early, even if the cookie carries a valid JWT.
_check_csrf(request)
if is_auth_disabled():View on GitHub (pinned to 1dd6ba1acb)
Solutions
- Re-read the csrf_token cookie immediately before the mutating request and use that exact value as the header (don't cache it across logins)
- Clear the site's cookies, re-login, retry — this resyncs cookie and header
- Ensure a single session: one cookie jar in scripts (curl -c/-b same jar), one origin in browsers (go through the nginx proxy, not direct :8001/:3000)
- If it persists, dump both values client-side (cookie vs header) to confirm which side is stale
Example fix
// before: header cached at page load, goes stale after re-login
const CSRF = document.cookie.match(/csrf_token=([^;]+)/)[1]; // captured once
// after: read per request
function csrfHeader() {
return { 'X-CSRF-Token': document.cookie.match(/csrf_token=([^;]+)/?.[1] ?? '' };
}
await fetch(url, { method: 'POST', credentials: 'include', headers: csrfHeader(), body }); Defensive patterns
Strategy: validation
Validate before calling
function assertCsrfSync(): void {
const cookie = getCookie('csrf_token');
const header = lastSentCsrfHeader; // your client's cached value
if (cookie && header && cookie !== header) {
lastSentCsrfHeader = cookie; // resync before sending
}
} Type guard
null
Try / catch
catch (e) {
if (e.status === 403 && e.detail?.includes('CSRF token mismatch')) {
lastSentCsrfHeader = getCookie('csrf_token'); // re-read fresh cookie
return fetchOnce(url, opts); // single replay, no loop
}
throw e;
} Prevention
- Never cache the CSRF header value across logins — read it from the cookie per request
- After re-login or cookie rotation, invalidate every in-flight client's cached token
- Use exactly one cookie jar/session (single origin through the nginx proxy) for scripts and browsers alike
- On mismatch, replay at most once with the fresh cookie; a second mismatch means session corruption — re-login
When it happens
Trigger: POST/PUT/DELETE/PATCH to a LangGraph-runtime route where header token != cookie token: e.g. the server rotated csrf_token (new login) and the client still sends the old header value, or the client reads the header token from a different cookie jar/session than the one attached.
Common situations: Re-login in one tab while another tab keeps the pre-rotation X-CSRF-Token; cookie jar cleared but cached header value reused; load-balanced dev setup where two backends issued different csrf cookies and the client mixes them; browser extensions rewriting cookies.
Related errors
- CSRF token missing. Include X-CSRF-Token header.
- Failed to load agents: ${res.statusText}
- request_failed
- Your email could not be verified by the identity provider. P
- The identity provider did not provide an email address.
AI-assisted analysis of bytedance/deer-flow@1dd6ba1acb (2026-08-14).
Data as JSON: /api/errors/1cc4c5152a2bf535.
Report an issue: GitHub.