multica-ai/multica · error
CSRF validation failed
Error message
CSRF validation failed
What it means
HTTP 403 from the auth middleware when the token came from the auth cookie (stateful browser session) and CSRF validation failed for a state-changing method. Cookie-based auth is vulnerable to cross-site request forgery, so mutating requests authenticated via cookie must present the CSRF token/header pair validated by auth.ValidateCSRF; a missing or mismatched CSRF token is rejected with 403 (not 401, because the session itself is valid).
Source
Thrown at server/internal/middleware/auth.go:59
// the client is untrusted and discarded before the auth
// branches run. Only the mat_ branch below re-sets it. This
// is what prevents a client from sending a normal mul_ PAT
// plus a forged `X-Actor-Source: member` (or anything else)
// to convince a downstream handler that its request came
// from a non-task-token path.
r.Header.Del("X-Actor-Source")
tokenString, fromCookie := extractToken(r)
if tokenString == "" {
slog.Debug("auth: no token found", "path", r.URL.Path)
http.Error(w, `{"error":"missing authorization"}`, http.StatusUnauthorized)
return
}
// Cookie-based auth requires CSRF validation for state-changing methods.
if fromCookie && !auth.ValidateCSRF(r) {
slog.Debug("auth: CSRF validation failed", "path", r.URL.Path)
http.Error(w, `{"error":"CSRF validation failed"}`, http.StatusForbidden)
return
}
// Agent task token: "mat_" prefix. Minted by the server at
// task-claim time and injected by the daemon into the agent
// process. Authoritative for actor identity — the bound
// (user_id, agent_id, task_id, workspace_id) triple is
// written into request headers here, OVERRIDING whatever the
// client sent, so a downstream actor-resolver cannot be
// tricked by a client that strips or forges X-Agent-ID /
// X-Task-ID. Human-only endpoints (e.g. agent env
// management) reject requests authenticated this way; see
// `actorSourceFromRequest`. MUL-2600.
if strings.HasPrefix(tokenString, "mat_") {
if queries == nil {
http.Error(w, `{"error":"invalid token"}`, http.StatusUnauthorized)
return
}View on GitHub (pinned to 2c0912b6ec)
Solutions
- Have the frontend read the CSRF cookie and send it in the expected header on every mutating request.
- If the session was refreshed, re-read the CSRF token — tokens from the old session will not validate.
- For non-browser clients, switch to Authorization: Bearer <PAT>, which bypasses CSRF checks entirely.
- Confirm the request includes credentials (cookies) so the session cookie accompanies the CSRF token.
Example fix
// before: mutating request with cookie but no CSRF header
await fetch('/api/issues', {method:'POST', credentials:'include', body})
// → 403 CSRF validation failed
// after: attach CSRF header from cookie
const csrf = document.cookie.match(/csrf_token=(\w+)/)?.[1] ?? ''
await fetch('/api/issues', {method:'POST', credentials:'include',
headers:{'X-CSRF-Token':csrf}, body}) Defensive patterns
Strategy: validation
Validate before calling
function csrfToken(): string {
return document.cookie.match(/(?:^|; )csrf_token=([^;]+)/)?.[1] ?? ''
}
if (method !== 'GET' && !csrfToken()) throw new Error('missing CSRF token; refresh session') Type guard
function hasCsrf(): boolean { return csrfToken() !== '' } Try / catch
const resp = await fetch(url, init)
if (resp.status === 403) {
const body = await resp.text()
if (body.includes('CSRF')) { await refreshSession(); retryOnce() } // stale CSRF, not a permission problem
} Prevention
- Attach the CSRF header automatically in a shared fetch wrapper for mutating methods.
- Re-read the CSRF cookie after any session refresh or login.
- Prefer bearer-token auth for non-browser clients to bypass CSRF entirely.
When it happens
Trigger: Browser sends a state-changing request (POST/PUT/PATCH/DELETE) authenticated by the session cookie without the CSRF header, with a stale CSRF token, or with a token bound to a different session; a cross-site form/fetch attempting an authenticated action.
Common situations: Frontend fetch omits the CSRF header after a session refresh/rotation; CSRF cookie not read correctly (wrong cookie name or SameSite blocks reading); a third-party page attempting CSRF (the rejection working as designed); API clients mistakenly using cookie auth instead of bearer auth.
Related errors
- Invalid desktop runtime config JSON: ${err instanceof Error
- errMsg
- fixed_args entries cannot contain NUL bytes
- command_name cannot contain NUL bytes
- vcs: token unauthorized
AI-assisted analysis of multica-ai/multica@2c0912b6ec (2026-08-15).
Data as JSON: /api/errors/92a132052302381b.
Report an issue: GitHub.