Zettel · security

What we hold, and what we can't.

Most privacy pages describe how carefully a company guards the things it collects. This one is mostly a list of things that never arrive.

the boundary

There is no column anywhere in our database for the content of a message.

Not empty. Not nullable. Not “we don’t populate it.” The column does not exist, and a test in our build fails if anyone adds one. You can read the schema yourself — it is the first file in server/schema.sql.

Your messages are read on your own machine, from your own archive, opened read-only. They are never uploaded, never indexed remotely, and never sent to a model unless you select a specific passage and confirm it twice — at which point the app shows you the exact payload before anything crosses.

what actually syncs

Only the Z layer: your marks, your ink, your chapters. An annotation is stored as four things.

columnwhat it iswhat we can learn from it
timeline An opaque handle. Your device computes it by HMAC-ing a conversation identifier with a salt that belongs to your account. That two marks belong to the same bucket. Not whose conversation it is, not who is in it.
t0, t1 The moment, or the stretch of time, the mark is anchored to. That something was marked then.
y A height between 0 and 1 — where on the sheet your pen was. Nothing.
body_ct Your own words, if the mark has any. Intended to be sealed on your device before it leaves. See not true yet, below.

The strongest sentence anyone could build from our database — including us, including someone with a subpoena — is “this account marked something in this opaque bucket at this moment.”

Rotating your account’s salt orphans every handle at once. That is what our delete button actually does, and it is one column.

why it is built this way

Because scope is defined by what enters the boundary.

A company that ingests your messages and encrypts them has to defend that decision forever — to an auditor, to a breach, to whoever buys them later. A company that never receives them has a much shorter conversation. We would rather have the shorter conversation, and we would rather you be able to verify it by reading a file than by trusting a paragraph.

It also means the interesting part had to be built without reading anything. Zettel proposes where to look in your archive using structure alone — a question mark, then a fortnight of silence — and returns who spoke and how long the quiet lasted, never a word of what was said. The reading happens in your browser, on your machine, over messages already on your screen.

where we actually are

Stated plainly, because a security page that reads like a finished product is the least trustworthy kind.

True today

Not true yet

yes Message content stays on your machine
yes Source public and independently checkable
not yet Annotation bodies sealed on-device
not yet Third-party penetration test
not yet SOC 2 Type II · ISO 27001

HIPAA, and being careful about it

People have asked whether Zettel can be used where protected health information is involved — a clinician annotating their own record of a conversation, for instance.

Our honest position: because message content never reaches our systems, we do not believe we receive PHI, and therefore do not believe we act as a Business Associate. We are stating that as our reading of our own architecture, not as legal advice, and we have not had it tested by counsel or by a regulator. If you are considering Zettel in a regulated setting, tell us and we will put the architecture in front of your compliance team in as much detail as they want — including the schema, which is the part that actually answers the question.

subprocessors

whowhat forwhat they see
CloudflareHosting, database, rate limiting Account identifiers, opaque timeline handles, annotation rows. No message content.
ResendSign-in emails Your email address and the sign-in link.
TwilioSign-in codes Your phone number and a six-digit code.
GitHubThe source and this page Nothing of yours.

If you sign in with Google or Apple, that provider learns you signed in to Zettel. If you would rather they didn’t, use email or phone.

reporting something

If you find a vulnerability, we want to hear about it before anyone else does. Open a private security advisory on the repository, or email security@zettel.ink.

We will acknowledge within three working days and tell you honestly whether we can fix it and when. We are a very small team; we would rather say “this will take a month” than go quiet. We will not threaten you, and we will credit you unless you ask us not to.