July 5th, 2026

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

TL;DR: Strix found a one-click account takeover in Granola's Electron desktop app. A user-controlled link in the activity feed could navigate a trusted Granola window to an attacker-controlled page, and that page kept access to Granola's privileged Electron bridge. Granola confirmed the issue quickly, shipped a server-side fix that protected users without an app update, patched a bypass we sent later, and found no evidence of exploitation in the wild.

Granola's disclosure: Report: Desktop App Navigation Vulnerability

  1. 01Activity request

    Attacker-controlled display name

  2. 02One click

    Trusted window navigates off-origin

  3. 03Attacker page

    Electron bridge still present

Redacted POC showing the activity-feed sequence. One click navigates the trusted renderer to attacker-controlled content while the Electron bridge remains available. Recorded June 25 during bypass retesting; Granola deployed the bypass fix June 26.

We love using Granola (an AI notetaker) at Strix. Part of the reason is that Granola has handled security well in the past. And it of course deals with extremely sensitive data -- internal company notes, customer meetings etc. That made it a good target for a new thing we have been working on: asking Strix to reason about desktop apps, not just web apps.

So we handed Strix the Granola desktop app with no exploit hypothesis and asked it to go deep. It came back with a direct chain: one click on a notification could move a trusted Granola renderer to a page we controlled, while keeping Granola's privileged IPC bridge, SQLite control port, and session access within reach. Screen-capture paths and webcam-adjacent controls were also reachable in principle behind the bridge.

Let's get into it!

Introduction

Electron apps are very interesting targets. They look like desktop apps, but much of the UI is still web content. That makes them powerful, and it makes their security model depend on one question: which web content is allowed to talk to the native side?

Granola had done the standard Electron hardening work. Its main windows used contextIsolation: true and nodeIntegration: false. The renderer could not just reach into Node.js. Instead, a preload script exposed a scoped bridge on window.electron, and native capabilities lived behind IPC channels.

That shape is all good. But, the danger is that the bridge follows the window. If a privileged window that started on trusted app content can be navigated to attacker-controlled web content, the attacker does not need Node integration. They can inherit the bridge that was already there!

Electron trust boundary

Navigating off-origin kept the privileged bridge

At time of testing

Trusted renderer
app://ui/window.electron

Granola content starts inside a preload-bearing window.

same Browser Window
Attacker origin
https://attacker.examplewindow.electron

A new origin inherits the bridge already attached to the window.

privileged IPC
Final impact
One-click account takeover

Session access, plus a foothold into local desktop capabilities.

Fixed now

Trusted renderer
app://ui/window.electron

will-navigate and will-redirect enforce the boundary.

navigation blocked
System browser
shell.openExternal()window.electron

External pages open outside the privileged renderer.

no privileged IPC
Final outcome
The attack chain ends

Attacker-controlled code never enters Granola's trusted renderer.

At the time of testing, one click could navigate the trusted window to an attacker page without dropping window.electron, turning the navigation flaw into account takeover. Granola now blocks that transition.

What Strix tested

Strix is the open-source autonomous hacking agent we build. For this run there was no live URL to crawl, so we gave it the extracted Granola app bundle and told it to find what it could. It spun up 56 agents.

One of those was focused on that Electron Trust Boundary.

Within minutes, the trust-boundary lead had mapped the native surface:

Electron Trust Boundary Agent
Thinking

Current evidence confirms a broad preload bridge: window.electron.ipcInvoke/ipcSend/ipcOn is available to renderer code, and renderer bundles actively use it for powerful channels like get-session, sqlite:port, and screenshot/system actions.

Then, it asked the million-dollar-question:

Electron Trust Boundary Agent
Thinking

The critical next question is reachability from any remote or less-trusted renderer, because that would convert the generic IPC bridge into a high-impact trust-boundary break.

This is the key: can an attacker make untrusted JavaScript run in the renderer that had the privileged bridge?

Granola vulnerability overview

Strix confirmed two important primitives by running the packaged app under a local Electron runtime.

First, the preload bridge was a generic IPC pass-through. The renderer could call ipcInvoke, ipcSend, and ipcOn with channel names of its choosing. Unknown channels returned "no handler registered", which meant the effective gate was the handler list, not a renderer-side allowlist. Sensitive channels like get-session, get-refreshed-access-token, set-tokens, and get-stored-accounts were reachable behind that bridge.

Second, at the time of testing, the local database encryption boundary was weaker than it looked. The renderer stored SQLite key-wrapping material in IndexedDB and exported the decrypted SQLCipher key into JavaScript. An attacker who reached the trusted renderer could therefore bypass the at-rest encryption boundary. Granola has since fixed this: the SQLCipher key no longer touches renderer JavaScript or IndexedDB, and the key path has been moved out of the renderer.

Still, these findings were not enough on their own. They only became critical if attacker-controlled JavaScript could run inside the trusted renderer. So Strix went looking for a way to move a preload-bearing Granola window off trusted content.

The crack: top-level navigation

The External Navigation lead agent found the missing guard. Granola protected new windows: setWindowOpenHandler denied popups and pushed external links to the system browser. But the primary window itself did not have the same deny-by-default guard for top-level navigation. Nothing caught will-navigate or will-redirect.

That meant a trusted Granola window could be navigated to https://attacker.example. Because the preload bridge was attached to the window, not freshly granted per URL, window.electron came along for the ride.

You can see the shape of the fix Granola later shipped. The window now blocks navigation away from the trusted app origin and hands normal external URLs to the OS browser instead:

1webContents.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'.

In Strix's words:

External Navigation Lead Agent
Thinking

A preload-bearing primary renderer can be navigated from trusted application content to an arbitrary external http(s) origin without dropping its privileged Electron bridge. Once attacker-controlled JavaScript runs in that trusted renderer, the external page inherits IPC access and a forwarded SQLite message port.

The attack flow

A navigation bug is only useful if an attacker can trigger it. Granola had already thought about obvious routes. You could not just paste a link into a note and hijack the window; note bodies were guarded.

The interesting path was the activity feed. The activity feed is designed to take users to notes and meetings, so it naturally contains clickable notification content. Some of that content includes another user's display name.

Strix found that an attacker could set their own display name to a Markdown link, request access to a victim's private note, and then appear in the victim's activity feed. The victim clicks the name, which is a normal action in that interface. The trusted Granola renderer navigates to the attacker's page with the bridge still intact.

Granola activity feed showing the attacker-controlled display-name request used to trigger the proof of concept
The highlighted request is the click that triggers the off-origin navigation; the surrounding entries are bypass tests from the same retest session.

The chain is short:

  1. The attacker sets their display name to a link pointing at an attacker-controlled page.
  2. The attacker triggers a notification by requesting access to a private note.
  3. The victim sees the request in their activity feed.
  4. The victim clicks the display name.
  5. The trusted renderer navigates to the attacker page while retaining window.electron.
  6. The attacker page can now talk to privileged IPC channels and reach data that should only belong to the app.

Skadoosh! One click and we have account takeover, with a foothold into local desktop capabilities behind it.

Proof of concept

  1. 01Activity request

    Attacker-controlled display name

  2. 02One click

    Trusted window navigates off-origin

  3. 03Attacker page

    Electron bridge still present

Redacted POC showing the activity-feed sequence. One click navigates the trusted renderer to attacker-controlled content while the Electron bridge remains available. Recorded June 25 during bypass retesting; Granola deployed the bypass fix June 26.

The working proof of concept demonstrated that the attacker-controlled page could speak Granola's privileged IPC dialect after the navigation. From there, the page could reach session-related channels, request the SQLite port, and recover the then-renderer-exposed SQLCipher key through the path Strix had identified earlier. Capture-related paths, including screenshot and webcam-adjacent controls, were also reachable in principle behind the bridge.

The first patch blocked the original notification-link route server-side, so users were protected without needing to update the desktop app. We then sent a bypass as raw URLs were defanged, but a display name carrying Markdown with an HTML-entity-encoded scheme colon still rendered as a live link. Granola deployed the bypass fix on June 26 and asked us to re-test after its internal validation.

Why it matters

First, we still love Granola (even more so now after they handled this so professionally). But this bug is not really about Granola. It is about a class of Electron failures where the app has a reasonable bridge design, but the window carrying that bridge can wander away from trusted content.

Electron's security model is positional. Capability follows the window, and the model only holds while that window stays on trusted content. In this case, the assumption that activity-feed notifications were trusted turned a latent navigation bug into a one-click account takeover.

For desktop apps, that difference matters. Once an attacker reaches the privileged renderer, "web bug" starts to look like "local app bug": session tokens, account state, local storage, database access, screenshot paths, and other native-adjacent features can all become part of the blast radius.

Recommendations for Electron teams

  1. Guard top-level navigation, not just new windows. setWindowOpenHandler is not enough. Deny by default on will-navigate and will-redirect for every preload-bearing window, and allowlist only your own trusted origins.
  2. Treat internal surfaces as attacker-controlled if users can write into them. Notifications, activity feeds, meeting titles, note titles, avatars, and display names are all untrusted input, even when they appear inside first-party UI.
  3. Avoid generic IPC pass-through bridges. A compromised renderer should not be able to name arbitrary IPC channels. Expose a narrow, explicit, typed API and enforce authorization on the main-process side.
  4. Keep encryption keys out of renderer reach. If the renderer can recover the SQLCipher key, local database encryption does not survive renderer compromise.
  5. Test the chain, not just the finding. "A bridge exists" is not the same as "an attacker can reach it." The severity came from proving the whole path: notification click, external navigation, retained bridge, privileged access.

Responsible disclosure

  • Tue, June 23 — we reported the one-click ATO with a PoC video and the full IPC surface.
  • Wed, June 24 (early AM) — Granola replied: "pretty sure this is valid. Great find!" and started reproducing. They told us they knew the renderer could be redirected and had guards, but had not expected an attacker-controlled URL to reach notifications, which they had treated as trusted.
  • Wed, June 24 — the sanitization fix shipped. Notification links could no longer produce an attacker-controlled clickable URL, deployed server-side so users were protected with no update required. Granola also broadened will-navigate protections and started work to lock the bridge to authorized pages.
  • Thu, June 25 — we sent the bypass described above.
  • Fri, June 26 — Granola shipped the bypass fix targeting that vector.
  • Sun, June 28 — Granola completed its internal validation and asked us to re-test.
  • Mon, June 29 — we confirmed it was patched.

Granola found no evidence of exploitation in the wild and offered a reward despite having no formal bounty program. Users were protected by the next day, with a real conversation the whole way. This is how disclosure should go.

Try Strix for free →