Severity: Medium
electron/main.ts (handleDeepLink, ~L43-65), reached via second-instance/open-url, forwarded to frontend/App.tsx (~L91-105).
The directorsdesktop://auth/callback?token=…&refresh=… handler extracts the token and forwards it straight to the renderer, which POSTs to /api/sync/connect. Unlike the loopback flow (palette-auth-server.ts, which verifies a random state nonce), the custom-protocol path performs no state/origin validation. Any web page the user visits can do location.href = "directorsdesktop://auth/callback?token=<attacker's Palette JWT>&refresh=<attacker refresh>"; the OS focuses the app and it silently connects to the attacker's Palette account. Classic login-CSRF: the victim's subsequent work/uploads/credit-spends are attributed to the attacker's account, and the attacker's refresh token is persisted to settings.json.
Fix
Carry the same state nonce through the directorsdesktop:// redirect that the loopback server already uses, and reject callbacks whose state doesn't match a pending sign-in the app initiated.
Severity: Medium
electron/main.ts(handleDeepLink, ~L43-65), reached viasecond-instance/open-url, forwarded tofrontend/App.tsx(~L91-105).The
directorsdesktop://auth/callback?token=…&refresh=…handler extracts the token and forwards it straight to the renderer, which POSTs to/api/sync/connect. Unlike the loopback flow (palette-auth-server.ts, which verifies a randomstatenonce), the custom-protocol path performs nostate/origin validation. Any web page the user visits can dolocation.href = "directorsdesktop://auth/callback?token=<attacker's Palette JWT>&refresh=<attacker refresh>"; the OS focuses the app and it silently connects to the attacker's Palette account. Classic login-CSRF: the victim's subsequent work/uploads/credit-spends are attributed to the attacker's account, and the attacker's refresh token is persisted tosettings.json.Fix
Carry the same
statenonce through thedirectorsdesktop://redirect that the loopback server already uses, and reject callbacks whosestatedoesn't match a pending sign-in the app initiated.