Summary
src/services/widget-store.ts:133-139:
setDomainCookie('wm-widget-key', key);
try { localStorage.setItem('wm-widget-key', key); } catch { /* ignore */ }
...
setDomainCookie('wm-pro-key', key);
try { localStorage.setItem('wm-pro-key', key); } catch { /* ignore */ }
Both wm-widget-key and wm-pro-key (and wm-access-token, user-identity.ts) are stored in localStorage AND in domain cookies. Both are JS-readable. Any XSS or malicious browser extension on *.worldmonitor.app extracts them in a single localStorage.getItem call.
Why the constraint exists
These are client-set keys. There's no httpOnly option for cookies that JS itself creates — httpOnly requires the server to issue them via Set-Cookie. So as long as the key is created/stored entirely client-side, JS must be able to read it.
Likely fix (architectural)
Migrate to server-issued session tokens:
- Key exchange flow: client POSTs the long-lived
wm-pro-key ONCE to a server endpoint (e.g. /api/session/exchange). Server validates, issues a short-lived (15-min) session JWT as an httpOnly; SameSite=Strict; Secure cookie + a refresh token.
- Frontend forgets the key after exchange — it's stored only on the server side (or not at all, if the server can derive it from a stable user record).
- Refresh via a separate
/api/session/refresh endpoint that reads the refresh token (also httpOnly).
This brings worldmonitor in line with how every modern auth provider (Clerk, Auth0, Stytch) handles bearer tokens.
For users who need to copy/share the key (legitimately), keep a "show key" flow in settings that re-derives it from the user's account on the server side.
Severity
HIGH (architectural). The XSS amplification factor is real — any successful XSS becomes a permanent account takeover, not just a session compromise.
Source
Manual code review (deepseek security audit, validated 2026-05-01).
Summary
src/services/widget-store.ts:133-139:Both
wm-widget-keyandwm-pro-key(andwm-access-token,user-identity.ts) are stored in localStorage AND in domain cookies. Both are JS-readable. Any XSS or malicious browser extension on*.worldmonitor.appextracts them in a singlelocalStorage.getItemcall.Why the constraint exists
These are client-set keys. There's no
httpOnlyoption for cookies that JS itself creates —httpOnlyrequires the server to issue them viaSet-Cookie. So as long as the key is created/stored entirely client-side, JS must be able to read it.Likely fix (architectural)
Migrate to server-issued session tokens:
wm-pro-keyONCE to a server endpoint (e.g./api/session/exchange). Server validates, issues a short-lived (15-min) session JWT as anhttpOnly; SameSite=Strict; Securecookie + a refresh token./api/session/refreshendpoint that reads the refresh token (also httpOnly).This brings worldmonitor in line with how every modern auth provider (Clerk, Auth0, Stytch) handles bearer tokens.
For users who need to copy/share the key (legitimately), keep a "show key" flow in settings that re-derives it from the user's account on the server side.
Severity
HIGH (architectural). The XSS amplification factor is real — any successful XSS becomes a permanent account takeover, not just a session compromise.
Source
Manual code review (deepseek security audit, validated 2026-05-01).