Security
The Clarence threat model
Every way we can think of that Clarence could be attacked or misused, ranked, with the design that answers each one. Published before launch, because an app that asks to read your messages owes you its attack surface, not just its promises.
Clarence is among the most sensitive software that can run on a Mac. It reads your iMessage history under Full Disk Access, builds a private map of your closest relationships, and can send real messages as you. A compromise here would not be a data leak; it would be impersonation of you to the people you love. So security is not a compliance layer bolted on at the end. It is the product promise, and this page is the working document behind it: the same one re-read before every stretch of building, lightly edited for reading outside the codebase. The plain-language summary lives on the privacy page; this is the long version, for readers who want to check our work.
What we protect
- Your message history. read in place from the Messages database, never copied, never uploaded
- The dossier Clarence builds. notes, memory, rhythm, and dispositions: a map of your closest relationships
- The ability to send as you. a real iMessage from Clarence is indistinguishable from one you typed
- The credentials for cloud drafting. keys that could be abused if extracted
- The promises themselves. “read then discarded”, “anonymized”, “never automated”: breaking one is an incident, not a bug
The trust boundaries
Four lines the architecture never lets anything cross casually:
- The window is untrusted. The part of the app you see displays attacker-influenced strings all day, so it is treated as hostile territory. Every privileged capability (reading history, storage, sending, AI calls) lives in a separate process; the bridge between them validates every message and exposes the narrowest possible surface.
- Message content is hostile input, everywhere. Any string that originated in a message or a contact card was authored by someone else. It is escaped when shown and parameterized when used, no exceptions, no “trusted” senders.
- The only cloud crossing is opt-in drafting. One boundary exists between the app and the network, and content crosses it only when you consent to a per-use read: an anonymized recent excerpt, transmitted once, retained by no one. Decline, and the draft is composed on-device with nothing transmitted, not even a name.
- Permissions are never assumed. Full Disk Access and the Automation grant are yours to give and yours to revoke. The app detects what it has and degrades gracefully, rather than presuming.
The risks, ranked
Ordered by how much damage each could do, not by how likely it is. The ranking is re-checked as the app changes; a stale threat model is a wrong one.
| The threat | The design that answers it |
|---|---|
| A malicious message attacks the app’s own window | Anything anyone texts you is treated as hostile input, because anyone who texts you authored it. Message and contact strings are rendered as text, never as markup or code, and the window that displays them is sandboxed away from everything that can read or send. |
| The send path gets abused: bulk sends, hidden sends, a worm | There is exactly one send function in the app. It demands a fresh human click each time, shows the recipient at the moment of sending, is rate-limited, and appends every send to a permanent log. No other code path can produce an outgoing message. |
| A message manipulates the drafting AI (prompt injection) | Model output is words on your screen and nothing else. It cannot trigger a send, call a tool, or take any action, and you review every draft before it goes anywhere. Links inside drafts are constrained. This is in the model precisely because everyone who texts you writes input to the drafting prompt. |
| “Discarded” quietly leaks into logs or crash reports | A standing rule with no exceptions: message content never enters logs, error reports, or crash dumps. The read log records counts and timestamps only. Cloud drafting runs under zero-retention, no-training terms. |
| A tampered update | Releases are signed and notarized; the updater verifies signatures over a pinned, encrypted channel before anything installs. |
| A hostile contact name injects into a script or a query | Everything is parameterized, always. Sending passes arguments as arguments, never by building script text from strings; database access uses prepared statements everywhere. A friend named “Robert’); DROP TABLE” is just a friend with a strange name. |
| The dossier is read off the disk | Clarence’s own database is encrypted (SQLCipher) with a key held in the macOS Keychain, and is excluded from Time Machine and iCloud backups by default, so your relationship notes never ride into copies you forgot about. |
| API credentials extracted from the app bundle | Keys live in the macOS Keychain, never in source or in the bundle. The plan on record: before Clarence has its second user, the cloud path moves behind a proxy with per-user tokens, making zero retention enforceable rather than configured. |
| A poisoned dependency (supply chain) | Committed lockfile, minimal and pinned dependencies, automated audits in CI, and a rule that every new dependency requires a written justification. An app with Full Disk Access does not get to install packages casually. |
| Anonymization that claims more than it does | Redaction is best-effort by nature, so the interface says “stripped where found” rather than pretending perfection, and reads are scoped to the recent thread, never the whole history. Overclaiming is listed here as a threat because trust dies from small lies first. |
The hardening baseline
For the technically fluent, the non-negotiable configuration of the app shell: context isolation on, no direct system access from the window, a sandboxed renderer, a strict content-security policy with no remote or inline code, navigation and popups denied, no remote content ever loaded, and an allowlisted bridge where every payload is schema-checked before it crosses. Privileged modules (database, filesystem, process control) exist only in the main process. None of this is tunable in settings; it is the floor the app is built on.
Standing rules
Rules every line of code is written under, forever:
- Message and contact strings are hostile: escaped on render, parameterized on use.
- No message content in logs, errors, or crash reports. Ever.
- One send path. A human gesture required, every send logged.
- AI output is display-only. It never drives an action.
- Every new dependency carries a written justification.
- Every design decision that touches data gets a written security-consequences section.
- No telemetry egress, ever. The app cannot phone home about you; the capability does not exist to be misconfigured.
How this stays alive
A threat model is only worth what its maintenance costs. This one is enforced at three gates: a security pass over the shell and its bridge before features are built on it, a dedicated adversarial review when mock data is replaced by real data, and a full pre-release pass (signing, notarization, update channel, key handling, dependency audit) before any build leaves the founder’s machine. Between gates, the document is re-read at the start of every build milestone and the ranking updated to match reality.
And one rule that belongs to us rather than to attackers: the promises on this page and the privacy page are part of the security model. If one is ever broken, that is a security incident, disclosed in the changelog, not a bug to patch quietly.
Read it like an attacker
This page is written to be checked, and the network story in particular does not require trust: watch Clarence with any outbound firewall and you will see one connection when you ask for a cloud draft, the updater checking its feed, and nothing else. If you find a hole in this model, an attack we have not ranked, or a claim that does not survive contact with the build, we genuinely want that email: [email protected]. Adversarial readers have already made this document better, and we would rather hear the next problem from you than from an incident.
Frequently asked questions
Why publish a threat model for an app that hasn’t launched?
Because order matters. A threat model written after launch documents what got built; one written before launch constrains what gets built. Clarence’s send path, logging rules, and storage design all exist in the shape this document demanded. Publishing it first also puts the promises on the record where they can be held against us.
Isn’t Electron risky for an app like this?
The risk in Electron is sloppy configuration, not the framework itself, and privacy is a property of data flow, not of bundle size. Clarence runs the display layer sandboxed with no ability to load remote code, keeps every privileged capability (reading history, storage, sending, AI calls) in a separate process, and allows the two to talk only through a narrow, validated bridge. The hardening checklist on this page is the contract.
What actually leaves my Mac?
Nothing, unless you use cloud drafting. If you do, an anonymized excerpt of the recent thread (names, numbers, and emails stripped where found) is transmitted once for that draft, under zero-retention, no-training terms, then discarded. You consent per use, any person can be marked local-only, and a fully on-device drafting mode exists. Voice transcription is on-device. There is no telemetry.
How would I know if a promise here was broken?
Two ways. First, network behavior is observable: point any outbound firewall at Clarence and you will see a connection when you ask for a cloud draft, the updater checking its feed, and nothing else. Second, policy: a broken promise (content that lingered, a log that captured a word it shouldn’t) is treated as a security incident and disclosed in the changelog, not quietly patched.
Clarence is a private Mac app that notices who you’re drifting from and drafts the message to bring them back. Your message history is read on your Mac and never uploaded; what the optional cloud drafting transmits, and under what terms, is exactly what this page says. The plain-language version is at how Clarence treats your messages.