Blog

Oct 28, 2026

What a desktop AI app can leak, and how storage architecture changes the blast radius

Local storage shrinks the blast radius of a desktop AI app bug, it does not remove it. A fixed, disclosed Granola Electron bug, then our own audit.

A desktop AI app can leak more than a web app can, because it holds two things a browser tab does not: a privileged bridge to the operating system, and your data at rest on your own disk. Whether a bug in one of those becomes a bad afternoon or a bad quarter is decided mostly by storage architecture, meaning where the notes live, who holds the key, and how much a compromised window can reach without asking anyone.

This post is about that second question. It uses a real, fixed, publicly disclosed bug in a well-built Mac app as the worked example, and it ends with our own attack surface, which is not zero.

The worked example, with the fix in the same breath

On July 5, 2026, the security firm Strix published a write-up of a one-click account takeover it had found in Granola's Electron desktop app. The chain is worth reading in full, because it is elegant and because nothing in it required the app to be sloppy. Checked 15 September 2026 against that write-up and against Granola's own disclosure note.

  • The app had done the standard Electron hardening: contextIsolation: true, nodeIntegration: false, and native capability behind a scoped preload bridge on window.electron. That is the shape you are supposed to ship.
  • New windows were guarded. setWindowOpenHandler denied popups and pushed external links to the system browser.
  • The primary window had no deny-by-default guard on top-level navigation, so nothing caught will-navigate or will-redirect. In Electron the preload bridge is attached to the window and not re-granted per URL, so a trusted window that navigated to an attacker's page took the bridge with it. Granola's shipped fix blocks that navigation and hands external URLs to the system browser.
  • The trigger was mundane. An attacker sets their display name to a Markdown link, requests access to a private note, and waits for the victim to click that name in their own activity feed.

Then the part that belongs to this post. At the time of testing, the renderer stored the SQLite key-wrapping material in IndexedDB and exported the decrypted SQLCipher key into JavaScript. Anything that reached the trusted renderer could step straight through the at-rest encryption boundary: the local database was encrypted, but the key was reachable from the same place the attacker now was.

The response matters as much as the bug, and it is the reason this post can be written at all. The timeline, from the researchers' disclosure and from Granola's own note: reported on Tuesday, June 23, 2026 with a proof of concept; confirmed by the vendor the next morning; a server-side sanitization fix deployed June 24, which protected users without anyone updating the app; a bypass variant reported by the researchers on June 25 and fixed June 26; internal validation completed June 28; the patch confirmed effective by the researchers on Monday, June 29. Granola found no evidence of exploitation in the wild, published its own disclosure, and moved the key path so that the SQLCipher key no longer touches renderer JavaScript or IndexedDB. Six days from report to confirmed patch, with users covered on day two. Checked 15 September 2026.

If your takeaway is "that app is unsafe", you have read this wrong. Every desktop app that does real work has a version of this bug in its future. What varies is what the bug can reach, and what the vendor does in the week after the email arrives.

What varied: the blast radius

Strip the specifics and one variable is doing the work. Once untrusted code runs inside a privileged renderer, the damage is bounded by what that process can reach.

  1. Session and tokens. If auth tokens are reachable through a generic IPC pass-through, one navigation bug is an account takeover rather than a rendering glitch. In the Strix write-up, the effective gate was the handler list, not a renderer-side allowlist, so every registered channel was callable by name, and what closed that path was the navigation guard: the window now blocks navigation away from the trusted app origin. Checked 15 September 2026.
  2. The at-rest key. Encryption at rest is a boundary only if the key lives somewhere the renderer cannot go. A key exported into JavaScript turns "encrypted local database" into a label.
  3. The cloud copy. This is the one that rarely gets priced in. If the notes also live in a vendor's cloud, the ceiling on a credential compromise is not your laptop. It is every meeting the account ever recorded, reachable from any browser, without touching your Mac at all. Local-only data has a physical ceiling. Synced data does not.
  4. The capability surface. Screen capture, microphone, camera, a shell: anything behind the bridge is in scope the moment the bridge is.

Point 3 is the storage-architecture point, and it cuts both ways. Local storage does not make an app secure. It makes the worst case smaller. A stolen laptop is a different incident from a stolen session token, and only one of them scales to everyone.

Our own attack surface, stated plainly

We publish an audit of ourselves, so this section is not a courtesy. It is the same ledger, applied to us. Every fact below is checked against that live page as of 28 October 2026.

  • Routines is a Mac app with an account behind it. On the default path, a chat or routine prompt goes from your Mac to getroutines.ai and on to Anthropic under our key. We store the token count and the cost, never the prompt or the answer. Add your own API key, sign in with ChatGPT, or connect Claude Code, and there is no Routines server in the path.
  • Meeting audio is not an exception we get to skip. Our meeting transcription streams audio to Deepgram, and dictation is a second audio path that streams to Deepgram on every plan while you speak. "Nothing leaves your Mac" is a sentence we do not write, and the reason we do not write it is in the audit.
  • Where our own connector tokens sit, since this post is about exactly that question. From our published audit, verbatim: "OAuth tokens for Gmail, Calendar, Microsoft 365 (Outlook, read only) and Slack, and the bot tokens for Telegram and Slack apps, are stored in the app’s local database on your Mac, not in the macOS Keychain; the AI provider keys and other API keys you paste do live in the Keychain. Connector calls go from your Mac to the provider directly, and bots connect out from your Mac, so no Routines server sits in the middle." Two honest readings of that, and we owe you both. The good one: there is no server-side copy of your tokens to breach, disconnecting the connector in settings is the off switch our audit names, and Cloud Sync strips tokens out of connector definitions before anything syncs. The uncomfortable one: a local database is a weaker store than the Keychain, and by the logic of this very post, that is our version of point 2 above. It is written on our own page because a directory that grades other apps has to publish its own worst row.
  • What stays is the storage. Your meeting notes are markdown files on your disk, and transcripts sit in a local database on the machine. Cancel your subscription and the notes still open, because they are files. Cloud Sync and Backup is off by default. Turn it on and your vault, your meeting transcripts and your notes are copied to your Routines account so your devices share them, which is point 3 above, entered by your own choice. Share an item by link, a meeting for example, and that item is uploaded to getroutines.ai so the link can show it; a shared meeting carries its title, attendees, notes and transcript. That is the claim this whole post is really about, and it is a claim about format and location, not about being un-hackable.
  • We run an assistant that can act. It can fetch a web page it is pointed at, reach services through gateways when you add keys, hand a task to Claude Code if you have it installed, and, if you have granted computer control, hand an instruction to Claude in a supported browser, which acts on pages under your own logins. Every one of those is capability surface. Tools run only inside a chat or routine turn you started, and nothing runs unprompted: if you do not start a chat or an AI routine, no prompt is sent. But "nothing runs unprompted" is a design choice, not a guarantee about bugs.

The part a reader can use

If you evaluate desktop AI apps, four questions separate the architecture from the marketing. None of them require you to trust an answer you cannot check.

  1. After I cancel, what file am I left holding, and what opens it?
  2. If someone steals my session token today, what can they read without ever touching my laptop?
  3. Where is the at-rest key, and can the part of the app that renders web content reach it?
  4. What has the vendor published about a security issue they had, with dates? An app with a public, dated, fixed disclosure is telling you more than an app with a perfect record and no history.

The last one matters more than it looks. The Strix write-up exists because Granola published its side of it, and when we built our own privacy audit of Granola from its documents, that incident was already among the vendor's own security write-ups, dated, with what happened, how it was fixed and who was affected. Checked 15 September 2026. The apps worth worrying about are the ones where the equivalent post has never been written by anyone.

FAQ

Was Granola hacked?

Not as far as anyone has published. The security firm Strix reported a one-click account takeover in Granola's Electron desktop app on June 23, 2026. Granola confirmed it the next morning, deployed a server-side fix on June 24 that protected users without an app update, fixed a bypass reported on June 25 by June 26, and the researchers confirmed the patch on June 29. Granola's own disclosure note says it found no evidence of exploitation and that no users were affected. Strix published the write-up on July 5, 2026.

Does keeping notes on my own disk make a desktop AI app secure?

No. Local storage makes the worst case smaller, it does not remove it. A bug that reaches a privileged window on your Mac can still read whatever that window can reach, including an at-rest encryption key if the key is exported into the app's JavaScript. What local-only storage changes is the ceiling: a stolen laptop is one incident, while a stolen session token for a synced account can expose every meeting the account ever recorded, from any browser.

What does Routines send off my Mac?

On the default path, a chat or routine prompt goes from your Mac to getroutines.ai and on to Anthropic under our key, and we store the token count and the cost, never the prompt or the answer. Meeting audio streams to Deepgram for transcription, and dictation is a second audio path that streams to Deepgram while you speak, on every plan. Add your own API key, sign in with ChatGPT, or connect Claude Code, and no Routines server is in the path. No prompt is sent unless you start a chat or an AI routine, and a signed-in app still re-checks its entitlements with our servers about every ten minutes and fetches a public release file from GitHub shortly after launch and every six hours to look for updates. The full list, including where connector tokens are stored, is on our transparency page.

What should I ask before trusting a desktop AI app with my notes?

Four questions. After you cancel, what file are you left holding and what opens it? If someone steals your session token, what can they read without touching your laptop? Where is the at-rest key, and can the part of the app that renders web content reach it? And what has the vendor published, with dates, about a security issue it had? A dated, fixed, public disclosure tells you more than a clean record with no history.

Related

Related posts

Try it on your Mac.

Routines keeps your notes, transcripts, and routine outputs as markdown and SQLite on your machine, where they stay unless you turn on Cloud Sync or share one by link.

Download