onionwire/docs/THREAT_MODEL.md
Sirius DevOps 6323619d80
All checks were successful
ci / test (pull_request) Successful in 3m13s
feat(store): encrypt chat bodies at rest; Ctrl-Q CLEAR/QUIT
Wrap a random 32-byte message key with Argon2id+ChaCha20-Poly1305.
sqlite messages.plaintext is nonce||ciphertext; identity keys stay
plaintext. Empty/wrong passphrase fails closed. Ctrl-Q types CLEAR or
QUIT then Enter (Esc cancels).
2026-09-10 17:19:50 -04:00

3.3 KiB
Raw Permalink Blame History

OnionWire threat model (v1)

OnionWire is a two-party chat over Tor onion services. There is no chat server.

Tor relays are not a chat server

Each install hosts its own v3 onion and dials friends onions through in-process Arti. Relays move cells; they never see plaintext, never store messages, and never hold a roster. Compromising a relay is not compromising OnionWires message store. The store is the sqlite file on the endpoint.

Location updates are not a missing-person finder

A signed loc frame (onion, ts, sig) rewrites a friends locator only if that friend is already in the roster, the signature verifies against the pinned pubkey, and ts is newer. There is no directory, DHT, or introduction server. If you rotated while they were offline, they cannot find you until they F3-paste your new invite. OnionWire will not hunt for a missing person.

Rotating the onion does not revoke a friend

F4 changes the locator only. The identity key is unchanged. Anyone who already has your pubkey can still prove they are talking to you, and a later invite paste with the same k updates their row. To become a new person, /wipe-all (new identity key). There is no in-band unfriend/revoke in v1.

Message bodies are encrypted at rest; keys are not

Chat bodies in sqlite are ChaCha20-Poly1305 (nonce || ciphertext in the messages.plaintext column). A random 32-byte data key is wrapped with Argon2id (same params as identity backup) from a non-empty passphrase. Salt + wrapped key live in store_meta. Unlock is fail-closed: wrong or empty passphrase does not open chat.

Identity secret key, friend public keys, and locators remain plaintext in the same db. The message key is not wrapped with identity_sk (that key is already on disk). A seized laptop still yields who you talk to and your identity unless you add OS/FDE. sqlcipher is out of v1. /wipe overwrites message bodies and vacuums; /wipe-all deletes the data dir.

Fail closed

Peer onion down → send fails. Fingerprint mismatch vs the pinned key → hard fail, no send. Arti HS experimental: if it cannot publish, OnionWire stops; it does not fall back to C-tor.

Profile is friend-visible, not a directory

A signed prf frame is shown to people who already have a session with you. Apply only for existing friends. There is still no name lookup, DHT, or public profile server. Anyone who already has a Noise session can see the profile you send them; that is not confidentiality against that friend.

Monero sidecar is not a wallet

OnionWire never holds spend keys. Optional ONIONWIRE_WALLET_RPC talks HTTP to a user-hosted monero-wallet-rpc. Never trust a rcp frame without RPC confirmation (verified stays 0). Subaddress reuse is the users wallet policy.

Backup file is the identity

/backup writes the identity and Noise static secrets. Treat the file like onionwire.db. Restore overwrites self keys and does not rewrite the friends table.

Rate limit is availability, not anonymity

Incoming rendezvous accepts are token-bucket limited (30/60s, burst 10). Excess is dropped without handshake. That is a DoS/availability control. It does not hide that you run an onion, and it is not a traffic-analysis defense.

Out of v1

Prosody, XMPP, s2s, MAM, carbons, outbox, multi-device, DHT / name server, sqlcipher.