Where your meeting data lives, every path it can take off your Mac, and what we found when we read our own code line by line.
Architecture pages age badly. This one names the exact revision it describes, so you can tell when it has drifted.
Citations below are paths in that repository at that commit, with line numbers wherever a claim rests on a specific piece of code. A few point at a whole file or a line range instead, because the claim rests on the file as a whole — the entitlements list, the model catalogue — and a single line would be the less honest reference. The repository is private, so the references are for verification during a source review rather than public browsing — we grant read access to enterprise buyers and auditors under NDA. Ask at security@summitnotes.app. Everything else on this page you can verify from the outside with a packet capture: the egress table is the complete list, so any connection you observe that is not on it is a finding, and we want to hear about it.
The whole design collapses to one question: what crosses the edge of the machine? There are four boundaries, and three of the four are closed unless you personally open them.
Capture (ScreenCaptureKit + microphone), transcription (Whisper or Parakeet), diarization, embeddings, FTS5 and vector search, and local LLM inference through llama.cpp or MLX. This is the default path end to end. Nothing here touches a socket.
If you configure a hosted provider and paste a key, prompt text — which means transcript excerpts — leaves the machine for that endpoint. This is the only boundary that carries meeting content in a form the far side can read.
Content crosses as AES-256-GCM ciphertext under a key derived from your seed phrase and never uploaded. Apple stores bytes it cannot read; so can we. Structural metadata crosses in the clear — the specifics are listed below.
A loopback-bound HTTP listener that lets a local agent read your meetings. It never leaves 127.0.0.1, every client is either approved in a consent dialog or holds a token that exists only in memory, and it is off until you switch it on.
We enumerated every URLSession and URLRequest construction in the shipping targets. This is all of them. Four go
out, one only listens on loopback.
There is no analytics SDK, no crash reporter, no PostHog,
Mixpanel, Sentry, Firebase or Amplitude anywhere in the app
targets. There is no Summit-operated backend at all — we run no
server that your app talks to, which is why no amount of
compromise on our side reaches your recordings. Purchases are
verified on-device with StoreKit 2 signed transactions, so even
billing needs no server of ours. The AnalyticsService you will find in the source keeps at most 100 events in memory and
flushes them to the macOS unified log — the code path that would send
them somewhere does not exist.
| Data | Location | At rest | Leaves the device? |
|---|---|---|---|
| Audio recordings (.m4a) | Sandboxed container, or a folder the user picks | macOS file permissions + sandbox + FileVault if enabled. Not separately encrypted by the app on disk. | Only as an encrypted CKAsset, and only from the iOS companion when sync is on. |
| Transcripts, summaries, topics, action items | SQLite (GRDB) in Application Support | Plain SQLite in WAL mode. No SQLCipher passphrase — protection is the OS container, not app-level encryption. | Only as AES-256-GCM ciphertext via CloudKit, or as prompt text if a cloud LLM is configured. |
| Search index (vectors + FTS5) | VecturaKit store in Application Support; FTS5 tables in the same SQLite file | Same as the database. Embeddings are computed on-device. | No. The index is never synced or uploaded. |
| Model weights (Whisper, Parakeet, GGUF) | Application Support / Models | Public artefacts, no user content. | No. |
| LLM provider API keys | Keychain, generic password, service app.summitnotes.llm-provider-credentials | kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly — does not sync to iCloud, does not leave the Mac. | Only as an Authorization / x-api-key header to the endpoint the user configured. |
| Sync encryption key | Keychain, service app.summitnotes.encryption, kSecAttrSynchronizable = false | AES-256 key derived from a 12-word BIP-39 phrase the user holds. | Never to CloudKit and never to us — but pairing moves it. Re-typing the phrase on the phone carries it out of band; the QR option puts the raw key on screen for the camera. Treat both as handling the password to the whole library. |
| Usage analytics | In-memory queue, max 100 events, flushed to the unified log | Never persisted by the app; never transmitted. | No. There is no analytics backend in the client. |
| Calendar & Reminders | EventKit, read for meetings; written only when the user exports an action item | Owned by the OS, not copied wholesale into the app. | Summit contacts no calendar provider itself. But an action item you export lands in EventKit, and if the target list is backed by iCloud, Google or Exchange, that account syncs the text off the Mac — the OS does it, not us. A local-only list stays local. |
Once you have downloaded a local model and set the provider to Local Models, a recording goes from microphone to finished summary without a single byte crossing the sandbox. Be precise about what that means: a fresh install ships with no model on disk and no API key, so step 4 cannot run until you choose one or the other. Downloading a model is a one-off fetch from Hugging Face; entering a provider key is what opens the hosted path. Each step below names the code that performs it.
ScreenCaptureKit takes system audio, AVFoundation takes the microphone, and the two streams are mixed locally. Screen recording permission is requested for the sole purpose of capturing the other side of the call — the usage string says exactly that.
noted/Features/Recording/ScreenAudioRecorder.swift:140, 219, 245 · noted/Info.plist:5
whisper.cpp or FluidAudio/Parakeet runs in-process against a model file on disk. Both engines are initialised from a local path; there is no speech API call.
noted/Features/Transcription/WhisperTranscriber.swift:45–62 · FluidAudioAsrManager.swift:245
Speaker separation, echo de-duplication and silence trimming, all on-device.
noted/Features/Transcription/DiarizationService.swift:13–23
The pipeline chunks the transcript and calls the configured LLM. With Local Models that is llama.cpp or MLX loading a GGUF from Application Support — same machine, same process.
noted/Features/LLM/LlamaService.swift:103–160 · Summarization/Pipeline/SummarizationPipeline.swift:58–90
Embeddings are generated on-device and written to a VecturaKit store next to the models; keyword search uses SQLite FTS5 in the same database.
noted/Features/Search/SearchManager.swift:169–184
Everything lands in one SQLite file in Application Support, with audio alongside it or in a folder you nominate.
noted/Core/Database/DatabaseManager.swift:91–105
A 12-word BIP-39 phrase generated on the Mac, from which both
devices derive the same AES-256 key. The derived key is cached
in the local Keychain with synchronizable = false, so it never rides iCloud Keychain to anywhere. We hold no
copy and no escrow. Lose the phrase and the ciphertext in
CloudKit is unrecoverable — that is the trade we chose.
SummitCore/.../CloudKitEncryption.swift:147–174, 245–296
Stored as Keychain generic passwords under a fixed service name, accessible only after first unlock and marked this-device-only. They are never written to UserDefaults and never leave the Mac except as an auth header to the endpoint you configured.
noted/Utilities/Configuration/CredentialStore.swift:26–72
256 bits from SecRandomCopyBytes, held in memory for the process lifetime and deliberately
never persisted — a long-lived server would otherwise need an
at-rest secret it could read without authentication. The cost is
that you re-copy the token after a restart.
noted/Core/Services/MCPServerManager.swift:441–475
Read off the signed target’s build settings and entitlements. The denials matter as much as the grants: an app that cannot reach your camera, contacts or location cannot leak them however it is compromised. Every row comes from one of two files — the build settings in noted.xcodeproj/project.pbxproj:1250–1290 (and the matching Release block at 1322–1362), or the signed entitlements in noted/noted.entitlements:1–27.
| Capability | State | Source |
|---|---|---|
| App Sandbox | Enabled | ENABLE_APP_SANDBOX = YES |
| Outgoing network | Allowed | Required for the five egress paths above |
| Incoming network | Granted via entitlement | com.apple.security.network.server, used by the loopback MCP listener |
| Microphone | Allowed | ENABLE_RESOURCE_ACCESS_AUDIO_INPUT = YES |
| Calendars | Allowed | com.apple.security.personal-information.calendars |
| Camera | Denied | ENABLE_RESOURCE_ACCESS_CAMERA = NO |
| Contacts | Denied | ENABLE_RESOURCE_ACCESS_CONTACTS = NO |
| Location | Denied | ENABLE_RESOURCE_ACCESS_LOCATION = NO |
| Bluetooth | Denied | ENABLE_RESOURCE_ACCESS_BLUETOOTH = NO |
| USB | Denied | ENABLE_RESOURCE_ACCESS_USB = NO |
| Printing | Denied | ENABLE_RESOURCE_ACCESS_PRINTING = NO |
| User-selected files | Read/write | For the recordings folder and Markdown export target |
| App Transport Security | Default | No NSAllowsArbitraryLoads or other ATS exception anywhere in the project |
A privacy claim is only worth what the defaults are worth. Here is what a fresh install does before you change anything.
| Setting | Default | Source |
|---|---|---|
| iCloud sync | Off | AppSettings.swift:1217 · FeatureFlags.swift:19–20 |
| Local MCP server | Off | AppSettings.swift:736 |
| Markdown auto-export | Off | AppSettings.swift:807 |
| Local LLM engine | llama.cpp (on-device) | AppSettings.swift:394 |
| LLM provider | OpenAI-Compatible, pre-selected as OpenAI — but inert until the user supplies a key and model | AppSettings.swift:346, 353 · LLMAdapter.swift:347–353 |
| Analytics | On, but local-only — the flush path writes to the unified log and nowhere else | AnalyticsService.swift:39, 275–289 |
A security page that lists only strengths is marketing. These are the things we would raise if we were reviewing someone else’s code, so we raise them about our own.
The SQLite store is opened through GRDB with foreign keys, WAL and cache pragmas — there is no SQLCipher key. Recordings sit on disk as ordinary .m4a files. At rest, the protections are the sandbox container, macOS file permissions and FileVault. On a Mac without FileVault, an attacker with the disk can read both.
The catalogue carries a checksum field and the download manager will verify it when present — but every entry in the shipping catalogue sets it to nil, so verification never runs. Integrity currently rests on TLS plus the fact that 12 of the 34 model URLs are pinned to an immutable Hugging Face revision; the remaining 22 resolve against a mutable /main.
Field-level encryption covers titles, notes, transcript text, original text, topics and action-item content. Left in the clear so CloudKit can query and reconcile them: record IDs, created/modified timestamps, recording duration, word count, detected language, folder and template IDs, document state, and which device last touched the record. Someone with access to the CloudKit zone learns when you met, for how long, in what language and how much was said — but not a word of what was said.
The header comment in CloudKitRecordConversion.swift describes the encryption key as living in the iCloud Keychain with kSecAttrSynchronizable: true. The implementation does the opposite: the key is stored strictly locally with synchronizable set to false, and cross-device pairing is manual — a 12-word phrase or a QR transfer. The code is the stricter of the two; the comment is wrong and should be corrected.
exportKeyTransferPayload() encodes the raw 32-byte AES key into a payload rendered as a QR code for pairing a phone. Anyone who photographs that screen can decrypt the whole synced library. The code says as much in its own doc comment; the mitigation is procedural, not technical.
The target sets ENABLE_INCOMING_NETWORK_CONNECTIONS = NO while the entitlements file grants com.apple.security.network.server. The explicit file is what gets signed, and the loopback MCP listener needs it — but the two should be reconciled so the build settings tell the truth about the shipped posture.
When the provider is set to a hosted endpoint, transcript text is sent to that endpoint in the clear (inside TLS) and is thereafter governed by that provider’s retention and training policy, not by Summit. There is no redaction pass, no PII filter and no local proxy in front of it. Choosing Local Models — or pointing the OpenAI-compatible provider at Ollama or LM Studio on localhost — keeps everything on the machine.
You do not need our source to test the central claim. With Summit running on stock settings, put it behind a proxy or watch it with Little Snitch and record a meeting end to end. You should see the App Store version lookup and nothing else — no transcript, no audio, no telemetry.
With source access, these commands cover the direct HTTP half of the egress table — the paths the app builds itself:
Grepping for URLSession alone would not find everything, and we would rather say
so than let the recipe imply otherwise. CloudKit talks to Apple through
CKDatabase, the MCP listener uses NWListener, EventKit and StoreKit reach Apple’s services without the app
opening a socket at all, and a linked dependency could in
principle carry its own client — which is what the last command
and the resolved package list are for. The packet capture above is
the check that does not depend on knowing which API was used.
If any of these turn up something this page does not account for, the page is wrong and we want the report.
This is a first-party architecture review by the Summit team, not a third-party penetration test or a certification. It covers the macOS app, the iOS companion and the shared SummitCore package at commit 9b419e0. It reads the source as written; it does not prove the absence of memory-safety bugs, and it makes no claim about the security of third-party model weights, of the LLM providers you may choose to configure, or of Apple’s CloudKit and Keychain implementations beyond the guarantees Apple documents.
Development tooling in the repository — the evaluation CLI, which does call an Anthropic endpoint — is excluded: it is not part of any shipped target and never runs on a user’s machine.
Security reports, architecture questions and requests for source access under NDA all go to the same place. We answer them.