# One Click Account Takeover in Granola: How a Notification Link Broke Out of Electron

Attacker-controlled display name rendered as Markdown link in Granola's activity feed navigated a trusted Electron window off-origin, retaining the privileged IPC bridge for one-click account takeover.

- Payout: Research
- Severity: critical
- Program: Granola
- Write-up by: Strix
- Disclosed: 2026-07-05
- Write-up quality: 8/10
- Original: https://www.strix.ai/blog/granola
- Library page: https://bugbountylibrary.com/r/blog%3Astrix-ai-blog-granola
- Tags: area:os domain:desktop impact:ato impact:data-leak kind:research platform:research sev:critical tech:electron technique:chained technique:source-review vuln:access-control vuln:account-takeover vuln:misconfiguration

**Target:** Electron desktop app renderer / activity feed notification
**Root cause:** Primary window lacked will-navigate/will-redirect guards (only setWindowOpenHandler was set), so a trusted preload-bearing renderer could be navigated to an attacker origin while keeping window.electron. Activity-feed display names were treated as trusted and rendered as clickable Markdown links.
**Steps:**
1. Set own display name to a Markdown link pointing at attacker-controlled page
2. Request access to a victim's private note to trigger a notification
3. Victim sees request in activity feed and clicks the display name
4. Trusted renderer navigates to attacker page; window.electron bridge persists
5. Attacker page calls `ipcInvoke`/`ipcSend`/`ipcOn` on channels like `get-session`, `get-refreshed-access-token`, `set-tokens`, `get-stored-accounts`, `sqlite:port`
6. Recover SQLCipher key exported into renderer JS via IndexedDB key-wrapping material
**How found:** Strix ran 56 agents on the extracted app bundle; the Electron trust-boundary agent mapped the preload bridge (ipcInvoke/ipcSend/ipcOn with get-session, sqlite:port, screenshot channels) and asked whether untrusted JS could reach a bridge-bearing renderer. The external-navigation agent then noticed setWindowOpenHandler guarded popups but no will-navigate/will-redirect guard existed on the primary window, and the activity feed was found to render user display names as links.
**Exploitation:** One click in the victim's activity feed navigates the privileged Granola renderer to attacker content; the page then speaks the privileged IPC dialect to pull session tokens, request the SQLite port, and recover the renderer-exposed SQLCipher key, yielding account takeover plus local desktop capability foothold.
**Escalation:** Chained navigation bug with generic IPC pass-through bridge and renderer-exposed SQLCipher key to escalate from a click to full session/database compromise; screenshot and webcam-adjacent controls reachable behind the bridge.
**Insight:** When auditing Electron apps, check whether setWindowOpenHandler is the only navigation guard: top-level will-navigate/will-redirect on preload-bearing windows is a separate, often-missed boundary. Then hunt for any first-party UI surface (activity feeds, notifications, display names, titles) that renders user-controlled strings as clickable links, since that is the trigger for off-origin navigation.

## Proof of concept (copied exactly from the original report)

**will-navigate guard fix code**

```
webContents.on('will-navigate', (event, url) => {
2 if (!url.startsWith('app://ui/')) {
3 event.preventDefault();
4 if (url.startsWith('https:') || url.startsWith('mailto:'))
5 shell.openExternal(url);
6 }
7});
8// Same idea for 'will-redirect'.
```

