Skip to content

End-to-end encryption

E2E is per channel and opt-in: the human flips it on in the app, and a 32-byte key is generated on their device — the server never sees it. The agent receives it as PIDGE_SECRET (base64url), stored next to PIDGE_TOKEN: same storage, same precedence (env var wins, else the same config file that holds the token — project-, agent- or machine-scoped; pidge doctor prints the path in use). The secret deliberately does not ride the setup prompt — chat logs sync to the agent provider’s cloud — so the app hands the human a terminal one-liner that writes it straight into that config file. Prefer generating the key yourself, outside Pidge’s code entirely? Bring your own key.

The {TOKEN, SECRET} pair always travels from one source — mixing a token from one channel with a secret from another is exactly the mixup the kf key fingerprint catches.

Sealing is per-field and documented — the manifest’s encrypted_fields/clear_fields lists are the source of truth. In short:

  • Sealed on-device: titles, bodies (body/body_markdown), subtitles, replies, the human’s messages to you, custom-action labels, tap-to-copy values, and deep links. The server stores only v1: AES-256-GCM envelopes for those fields.
  • Sealed once the media gate is open (e2e_media_ready — see below): media bytes (photos and files, both directions) and filenames. While the gate is closed they ride clear — the manifest and whoami state which applies to your channel right now.
  • Clear by necessity: action ids, profile/type, urgency, correlation_id, timestamps, delivery metadata (e.g. file sizes), Live Activity status, and the advisory provenance fields (sent_note, ack summaries, runtime labels — keep secrets out of notes) — the server needs them to route, schedule, attribute and degrade honestly.
  • Group channels are encrypted in transit, not end-to-end.
  • Sends seal themselves. With the secret set and the channel E2E, content fields leave your machine as envelopes with enc: "v1" + kf alongside. The 201 echo prints decrypted for display.
  • Attachments seal only when the media gate is open (e2e_media_ready — the human’s devices can open sealed blobs). Until then an attachment rides clear even though the text is sealed — don’t attach something you wouldn’t send unsealed. PIDGE_E2E_MEDIA=on/off overrides per machine, and a channel pinned sealed-media locally refuses to send clear bytes rather than downgrade.
  • listen/wait/ask decrypt the human’s sealed messages and answers before printing. Rows without enc are clear history and render as before.
  • Errors are precise, never garbage. A sealed row the CLI can’t open is blanked with an e2e_error naming the reason — sealed with another key (kf mismatch), unknown version, missing secret — raw base64 never reaches your terminal.
  • No secret ⇒ nothing breaks. Sends go clear and the app marks them “⚠️ not encrypted”; pidge doctor validates the secret (32 bytes, shows the kf), warns on an orphan secret, and exits 2 when an E2E channel’s secret is invalid.

The sealing/opening code is in the open CLI repo (MIT), with committed test vectors any implementation can check against.

This protects content against the server — infrastructure, database dumps, the operator. It does not protect against a compromised agent machine: PIDGE_SECRET sits in your env/config in the clear. The “ends” are the human’s device and your agent.