{"record":{"id":"1cc4c5152a2bf535","repo":"bytedance/deer-flow","slug":"csrf-token-mismatch","errorCode":null,"errorMessage":"CSRF token mismatch.","messagePattern":"CSRF token mismatch\\.","errorType":"http","errorClass":"HTTPException","httpStatus":403,"severity":"error","filePath":"backend/app/gateway/langgraph_auth.py","lineNumber":55,"sourceCode":"    \"\"\"\n    method = getattr(request, \"method\", \"\") or \"\"\n    if method.upper() not in _CSRF_METHODS:\n        return\n\n    if is_auth_disabled():\n        return\n\n    cookie_token = request.cookies.get(\"csrf_token\")\n    header_token = request.headers.get(\"x-csrf-token\")\n\n    if not cookie_token or not header_token:\n        raise Auth.exceptions.HTTPException(\n            status_code=403,\n            detail=\"CSRF token missing. Include X-CSRF-Token header.\",\n        )\n\n    if not secrets.compare_digest(cookie_token, header_token):\n        raise Auth.exceptions.HTTPException(\n            status_code=403,\n            detail=\"CSRF token mismatch.\",\n        )\n\n\n@auth.authenticate\nasync def authenticate(request):\n    \"\"\"Validate the session cookie, decode JWT, and check token_version.\n\n    Same validation chain as Gateway's get_current_user_from_request:\n      cookie → decode JWT → DB lookup → token_version match\n    Also enforces CSRF on state-changing methods.\n    \"\"\"\n    # CSRF check before authentication so forged cross-site requests\n    # are rejected early, even if the cookie carries a valid JWT.\n    _check_csrf(request)\n\n    if is_auth_disabled():","sourceCodeStart":37,"sourceCodeEnd":73,"githubUrl":"https://github.com/bytedance/deer-flow/blob/1dd6ba1acb03700589994b0366c5d1c7d05e2eff/backend/app/gateway/langgraph_auth.py#L37-L73","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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"],"exampleFix":"// before: header cached at page load, goes stale after re-login\nconst CSRF = document.cookie.match(/csrf_token=([^;]+)/)[1]; // captured once\n// after: read per request\nfunction csrfHeader() {\n  return { 'X-CSRF-Token': document.cookie.match(/csrf_token=([^;]+)/?.[1] ?? '' };\n}\nawait fetch(url, { method: 'POST', credentials: 'include', headers: csrfHeader(), body });","handlingStrategy":"validation","validationCode":"function assertCsrfSync(): void {\n  const cookie = getCookie('csrf_token');\n  const header = lastSentCsrfHeader; // your client's cached value\n  if (cookie && header && cookie !== header) {\n    lastSentCsrfHeader = cookie; // resync before sending\n  }\n}","typeGuard":"null","tryCatchPattern":"catch (e) {\n  if (e.status === 403 && e.detail?.includes('CSRF token mismatch')) {\n    lastSentCsrfHeader = getCookie('csrf_token'); // re-read fresh cookie\n    return fetchOnce(url, opts);                   // single replay, no loop\n  }\n  throw e;\n}","preventionTips":["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"],"tags":["csrf","http-403","security","session-sync","cookies"],"backgroundTag":null,"analyzedSha":"1dd6ba1acb03700589994b0366c5d1c7d05e2eff","analyzedAt":"2026-08-14T21:20:34.804Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}