Skip to content

Deep-link auth callback accepts an injected token with no state nonce (login CSRF) #40

Description

@taskmasterpeace

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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    auditFiled from the July 2026 auditsecuritySecurity vulnerability or hardening

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions