assets/js/base.js (~lines 472-481, apiFetch) resyncs global.CSRF_TOKEN from a 403 response's csrf_token field, but then still throws — every caller sees a raw "Invalid CSRF token" error toast on the FIRST attempt, with no transparent retry.
Concrete trigger:CsrfMiddleware's token lifetime is 3600s (1h) while the session idle timeout is 18000s (5h) — confirmed in middleware/CsrfMiddleware.php. So the CSRF token silently expires roughly once per hour of page inactivity, meaning any user who leaves a ticket page open and idle for over an hour, then makes their first edit, will see a confusing "Invalid CSRF token" error even though the retry would have succeeded automatically. Same experience after a write in another tab rotates the shared token.
Impact: Not a security bug — the underlying rejection/resync logic is working correctly — but a real UX/robustness gap: a transparent one-shot retry (resync token, then automatically re-send the original request once) would eliminate this class of error entirely for end users.
Fix: In apiFetch's CSRF-rejection handling, after resyncing CSRF_TOKEN from the response, automatically retry the original request once with the new token before surfacing an error to the caller.
**Severity:** Medium
`assets/js/base.js` (~lines 472-481, `apiFetch`) resyncs `global.CSRF_TOKEN` from a 403 response's `csrf_token` field, but then still `throw`s — every caller sees a raw "Invalid CSRF token" error toast on the FIRST attempt, with no transparent retry.
**Concrete trigger:** `CsrfMiddleware`'s token lifetime is 3600s (1h) while the session idle timeout is 18000s (5h) — confirmed in `middleware/CsrfMiddleware.php`. So the CSRF token silently expires roughly once per hour of page inactivity, meaning any user who leaves a ticket page open and idle for over an hour, then makes their first edit, will see a confusing "Invalid CSRF token" error even though the retry would have succeeded automatically. Same experience after a write in another tab rotates the shared token.
**Impact:** Not a security bug — the underlying rejection/resync logic is working correctly — but a real UX/robustness gap: a transparent one-shot retry (resync token, then automatically re-send the original request once) would eliminate this class of error entirely for end users.
**Fix:** In `apiFetch`'s CSRF-rejection handling, after resyncing `CSRF_TOKEN` from the response, automatically retry the original request once with the new token before surfacing an error to the caller.
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Severity: Medium
assets/js/base.js(~lines 472-481,apiFetch) resyncsglobal.CSRF_TOKENfrom a 403 response'scsrf_tokenfield, but then stillthrows — every caller sees a raw "Invalid CSRF token" error toast on the FIRST attempt, with no transparent retry.Concrete trigger:
CsrfMiddleware's token lifetime is 3600s (1h) while the session idle timeout is 18000s (5h) — confirmed inmiddleware/CsrfMiddleware.php. So the CSRF token silently expires roughly once per hour of page inactivity, meaning any user who leaves a ticket page open and idle for over an hour, then makes their first edit, will see a confusing "Invalid CSRF token" error even though the retry would have succeeded automatically. Same experience after a write in another tab rotates the shared token.Impact: Not a security bug — the underlying rejection/resync logic is working correctly — but a real UX/robustness gap: a transparent one-shot retry (resync token, then automatically re-send the original request once) would eliminate this class of error entirely for end users.
Fix: In
apiFetch's CSRF-rejection handling, after resyncingCSRF_TOKENfrom the response, automatically retry the original request once with the new token before surfacing an error to the caller.