Skip to content

[SECURITY] wm-pro-key / wm-widget-key stored JS-readable in localStorage + cookies — XSS = full key compromise #3545

@koala73

Description

@koala73

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:

  1. 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.
  2. 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).
  3. 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).

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't workingsecuritySecurity-related

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions