Desktop app architecture
The desktop app is a thin window over the same Rust core every Pigeon app uses. The window draws the screens; the core does the cryptography; the operating system's keystore holds the key to everything stored on disk.
One core Built
All protocol and crypto logic lives in pigeon-core. On top of it, pigeon-app is a small facade that holds the app's state and runs the network work in the background. The desktop shell may call pigeon-app and nothing else of Pigeon's: a build check fails if it reaches into the core directly. The facade has no protocol logic of its own, so there's one implementation to review, not one per app.
Every Pigeon crate, the shell included, forbids unsafe Rust, and the desktop has no foreign-function layer.
A thin shell Built
The shell is Tauri 2, pinned to an exact version, drawing with the system's own web view: WebKitGTK on Linux and WebView2 on Windows. One window, a tray icon with Show and Quit, one copy running per user. Quitting lets the background worker finish and save first.
Keys in the OS keystore Built
Everything the app keeps on disk (your keys, chats and contacts) is in one SQLCipher database, encrypted page by page with AES-256 and authenticated with HMAC-SHA512. The app refuses to start against plain, unencrypted SQLite.
- The database key is 32 random bytes made when the store is created.
- It lives in the operating system's keystore: the Secret Service (GNOME Keyring or KWallet) on Linux, the Credential Manager on Windows.
- Pigeon never writes it to disk, a log or an error message.
A computer with no keystore (a bare window manager, say) falls back to a passphrase stretched with Argon2id (64 MiB, 3 passes, 4 lanes). The logic is built; the passphrase screen is Planned, as is wrapping the key with the computer's TPM.
The hardened web layer Built
The screens are HTML, so the web layer is treated as the most exposed part of the app.
- Nothing remote. The window shows only the pages bundled inside the app. Any other navigation is refused and new windows are denied. There's no development server address in a release.
- A strict Content Security Policy.
default-src 'none', scripts and styles only from the app itself, no fonts, no frames, and connections only to the app's own bridge. - An allowlist of commands. The page may call Pigeon's own commands and listen for its events, nothing else. The list is checked against a golden copy in the tests, so a new permission can't slip in.
- No secrets on the bridge. No command takes key material. The one exception is the seed phrase: its 24 words cross once at sign-up, and again only when you rotate them.
- No clipboard or file access from the page. Copy and save are done by the Rust side.
- Text, never HTML. Names and messages are inserted as plain text, tested against a corpus of injection attempts, and invisible direction-override characters are dropped.
- Careful links. Only
https://links with plain ASCII host names open, in your normal browser. - No inspector. Developer tools are off in every build, and remote-debugging switches are cleared at start.
What isn't claimed: the app doesn't use Tauri's separate "isolation" mode. The defences are the policy, the allowlist and the text-only rendering above.
Platforms and updates
- Linux: a
.debfor Ubuntu 22.04 and later and Linux Mint 21 and later Built. Updates come through the package manager. - Windows 10: an installer builds Built, but it isn't signed yet and hasn't been tested on real Windows machines Planned. Its updater accepts only signed updates and never downgrades; it stays off until the signing key exists.
- macOS and phones come later Planned.
Sources: ADR-0014 (M2 clients, the facade and the desktop), ADR-0008 amendment 1 (the encrypted device store and the keystore), crates/pigeon-desktop/BIRTH.md, crates/pigeon-desktop/tauri.conf.json, crates/pigeon-desktop/capabilities/default.json, crates/pigeon-app/src/keystore.rs.
Related: architecture overview, the seed phrase.