Severity: High
Location: crates/librefang-api/dashboard/src/api.ts:811, 833-840, 843-848, 2824
Problem
The dashboard stores its bearer token in localStorage (XSS-readable; any future dangerouslySetInnerHTML slip exfiltrates it — currently safe because react-markdown is used safely, but fragile). For WebSockets, buildAuthenticatedWebSocketUrl puts the token in ?token=... — query strings land in server access logs, browser history, and the HTTP Referer header if the WS URL ever leaks. dashboardLogin writes the token straight to localStorage with no expiry tracking, no refresh-token rotation, no idle timeout. Token lives forever until manual logout or 401.
Attack
- XSS via any future markdown-rendering regression → full token theft. 2) nginx access log compromise → all WS tokens harvested. 3) Browser history sync to a different device → token leaks across sessions.
Suggested fix
Prefer httpOnly cookie auth (the codebase already uses credentials: "same-origin" in dashboardLogout). For WebSockets, use the Sec-WebSocket-Protocol subprotocol header instead of query string. At minimum, document why localStorage is acceptable here and add an idle-timeout on the SPA side.
Filed from cross-cutting bug/design audit (2026-04-28). Line numbers may drift across commits — verify before fixing.
Severity: High
Location:
crates/librefang-api/dashboard/src/api.ts:811, 833-840, 843-848, 2824Problem
The dashboard stores its bearer token in
localStorage(XSS-readable; any futuredangerouslySetInnerHTMLslip exfiltrates it — currently safe because react-markdown is used safely, but fragile). For WebSockets,buildAuthenticatedWebSocketUrlputs the token in?token=...— query strings land in server access logs, browser history, and the HTTPRefererheader if the WS URL ever leaks.dashboardLoginwrites the token straight to localStorage with no expiry tracking, no refresh-token rotation, no idle timeout. Token lives forever until manual logout or 401.Attack
Suggested fix
Prefer
httpOnlycookie auth (the codebase already usescredentials: "same-origin"indashboardLogout). For WebSockets, use theSec-WebSocket-Protocolsubprotocol header instead of query string. At minimum, document why localStorage is acceptable here and add an idle-timeout on the SPA side.Filed from cross-cutting bug/design audit (2026-04-28). Line numbers may drift across commits — verify before fixing.