1. Shape of the system
Cullapse is a native SwiftUI application with three runtime surfaces: the macOS app, the iOS app, and a bundled command-line tool. There are no Cullapse-operated servers in the picture. The application makes outbound connections only to:
- your IMAP mail servers, over TLS, using credentials you supply;
- Apple system services (Foundation Models, iCloud Keychain, iCloud KV-Store, StoreKit) when you opt in;
- a third-party AI provider's API, if and only if you configure one and supply a key.
There is no inbound listener. There is no analytics endpoint. There is no crash-reporter operated by Cullapse — crashes are routed through Apple's system reporter, which you control.
2. IMAP behavior
Cullapse speaks IMAP4rev1 (RFC 3501) over TLS, with selective use of the IDLE, MOVE, CONDSTORE, and LIST-EXTENDED extensions where the server advertises them. The client is built on a vendored SwiftMail implementation. The relevant guarantees:
- Only
FETCHrequests forENVELOPE,FLAGS,BODYSTRUCTURE,RFC822.SIZE,UID, and the named header set (List-Unsubscribe,List-Id,Message-ID) are issued. - No
BODY[], noBODY[1], noRFC822fetch is ever issued. The client has no code path that would. - External URLs found in headers are not dereferenced. Tracking pixels and remote images do not load — Cullapse never has the body that would reference them.
- Reconnection backs off on transient errors and respects the server's stated rate limits.
- UID validity is re-checked on every reconnect; if the server's
UIDVALIDITYchanges, the local cache for that mailbox is invalidated.
3. Credential storage
IMAP passwords, app-specific passwords, and AI provider API keys
are stored in the system Keychain. Every keychain call site is
routed through a single internal store (KeychainStore)
so that the iCloud-sync flag is applied uniformly across items.
By default, items use
kSecAttrAccessibleWhenUnlockedThisDeviceOnly and do
not leave the device. Enabling “Sync passwords with iCloud
Keychain” in Settings → General triggers a one-time migration:
existing items are deleted and re-added with
kSecAttrAccessibleWhenUnlocked, because
kSecAttrSynchronizable is part of an item's primary
key and cannot be flipped via SecItemUpdate. Reads
and deletes always use kSecAttrSynchronizableAny so
stale items in either mode are handled cleanly.
4. OAuth — scaffolding, no providers wired
The codebase contains XOAUTH2 routing, an OAuth2 client, token refresh, and an iCloud-Keychain token migrator, but no provider is currently enabled. The reasons are practical:
- Gmail's
mail.google.com/scope requires an annual CASA security audit. - Microsoft's Publisher Verification requires a paid work/school tenant; basic-auth IMAP for personal outlook.com / hotmail.com / live.com was retired in September 2024.
- iCloud Mail has no IMAP OAuth at all and requires app-specific passwords.
- Yahoo / AOL's third-party OAuth program is effectively closed.
Every account today authenticates with a password or app-specific password. The OAuth scaffolding is kept so the switch can be flipped when a provider becomes viable for direct distribution.
5. Local cache
Cached headers are persisted with SwiftData inside the
application's sandboxed container. The store is configured with
ModelConfiguration(url:) — the local-only form.
ModelConfiguration(... cloudKitDatabase:) is not
used. Cached headers therefore do not sync via CloudKit and are
not accessible to any other app.
The cache holds the same fields described in §3 of the privacy policy and nothing else. Bodies are not present because they are never fetched.
6. iCloud sync paths
Cullapse uses Apple's iCloud Key-Value Store (NSUbiquitousKeyValueStore)
to sync a narrow allowlist of preferences across your devices,
only when you turn it on. The allowlist is enforced in
code in a single module. Anything not on the allowlist will not
sync, regardless of where it is stored.
On the allowlist:
- Accounts list — addresses, hosts, ports, display names, without passwords.
- Rules, AI preferences (no API keys), sender categories, VIPs, suggestion dismissals, replied-message IDs, UI preferences.
Explicitly off the allowlist:
- Cached message headers (
MessageCache). - IMAP UID validity, fetch progress.
- All Keychain items, including passwords and API keys.
iCloud Keychain sync is a separate, per-device preference covered in §3.
7. AI request boundary
For Apple Foundation Models — the default — requests are dispatched through Apple's framework, which routes either to the on-device model or to Private Cloud Compute. No data crosses Apple's privacy boundary.
For an external provider you configure (Anthropic, OpenAI, Google, Perplexity), the request goes from your device directly to the provider's API over TLS, authenticated with the key you entered. The payload is constructed from the metadata fields described in §3 of the privacy policy. The provider's privacy policy governs what they do with the request. Cullapse does not log, mirror, or sample these requests.
8. Destructive-action safety
Bulk delete, archive, and move are optimistic and buffered. When you confirm an action, Cullapse mutates the local headers view immediately and schedules a task to commit the change to the server after a configurable undo window (default 5 seconds, adjustable in Settings). Before that window expires, the ⌘Z undo rolls back from a snapshot stored on the pending action.
- Both VIP protection and transactional-mail protection are evaluated inside the rules engine, not inside the matcher. Manual selections are not filtered — you are explicit.
- Both protections are settings-toggle gated and visible in the bulk-action confirmation.
- Suggestion dismissal is fingerprint-based, so the same suggestion does not reappear after you have rejected it.
9. Automation safeguards
Rules and CLI commands operate on the same buffered pipeline.
Every destructive CLI verb supports --dry-run and
prints the affected message count before the buffer is committed.
Rule conditions are header-only; rules cannot read bodies because
no caller in the system has bodies to give them.
10. Network egress
The application's outbound destinations are:
- Your IMAP servers (port 993, TLS, or 143 with STARTTLS where you require it).
generativelanguage.googleapis.com,api.anthropic.com,api.openai.com,api.perplexity.ai— only if you have configured the corresponding provider.- Apple endpoints used by the system frameworks (StoreKit, iCloud).
No content is sent to any domain operated by Cullapse, because Cullapse operates none.
11. Logging
The application uses Apple's unified logging system (os_log).
Logs are at the info level or below and contain
structure (IMAP command names, durations, error codes) but
not envelope content. Logs stay on your device
and are visible to you in Console.app. Cullapse does not ship
logs anywhere.
12. Sandbox and entitlements
The macOS application runs under the App Sandbox. Its
entitlements are limited to outbound network access
(com.apple.security.network.client), an iCloud
KV-Store identifier, an application group used to share license
state with the CLI, and a keychain access group used to share
credentials with the CLI. The application has no entitlement to
read the user's files outside its container, no microphone, no
camera, no contacts, no location.
13. CLI signing and entitlements
The cullapse binary ships inside
Cullapse.app/Contents/Resources/cullapse. It is
built from a separate Swift package by a Run-Script build phase
and re-signed with the application's identity for notarization.
The CLI's entitlements file deliberately differs from the GUI's:
- No
app-sandboxentitlement — the CLI runs from your shell. - No
network.cliententitlement — unsandboxed processes have network access by default. - No
ubiquity-kvstore-identifier— the CLI does not touch the iCloud KV-Store. - Same
application-groupsandkeychain-access-groupsas the GUI — so accounts and credentials are shared between the two surfaces without an additional setup step.
14. Security reports
Cullapse does not operate a coordinated-disclosure program. There is no security inbox, no bug-bounty arrangement, no PGP key, no acknowledgement SLA, and no committed remediation timeline. The app is built and maintained by one person and provided as is, with no warranty (see EULA §12).
If you find what looks like a security issue, you are welcome — but not required — to send a note to dev@snxt.ai. Please understand that:
- I read messages when I can; I make no commitment to reply or to act on a particular schedule.
- I cannot promise that a reported issue will be fixed, mitigated, or acknowledged in any release.
- I do not coordinate embargoes, assign CVEs, or run a vulnerability rewards program. Treat any disclosure timing as your own decision.
- I do not accept reports that require signing an NDA, an indemnity, or any other agreement as a precondition to reading them.
If you intend to publish a finding, do so on whatever timeline seems right to you. Cullapse is small, open about its architecture (this page exists for a reason), and you can inspect what it does without my permission.