How Satchel works
A plain technical overview of Satchel 0.3.0, a Firefox and LibreWolf add-on. Everything runs inside the add-on (JavaScript and WebAssembly). The only other piece is Session Router on the same computer.
1. Keys and identity
Your identity is a key pair, not an account. When you first open My Satchel you choose a passphrase (at least 8 characters) and Satchel makes a 32-byte Ed25519 seed. Two keys come from that one seed:
- an Ed25519 signing key, used for posts, records, mail signatures and signing in to your mail inbox;
- an X25519 encryption key, derived from the seed with HKDF-SHA256, used for mail and file drops.
You can keep several profiles, each with its own key, name, mail and contacts, and pick one per site. Nothing in Satchel links one profile to another. There is also a throwaway key that lives only in memory and is gone when you end it.
2. Reaching .sesh through Session Router
.sesh names belong to the Session network. Session Router runs on your computer and wires .sesh DNS into the system, so a name resolves to a local address starting with fd2e:7365:7368:. Satchel doesn't carry any traffic itself; it uses the browser's own requests, and the router carries them.
.sesh sites are plain http://, because Session Router already encrypts the traffic end to end. With HTTPS-Only mode on, the browser rewrites those requests to https://. When Satchel sees that, it loads the page by the router address instead of by its name. LibreWolf also rewrites local addresses, so there it needs dom.security.https_only_mode.upgrade_local set to false.
If the browser uses DNS over HTTPS, .sesh names go to an outside server that can't answer them and learns which sites you open. Satchel's setup check warns about this, and the fix is to exclude sesh from DNS over HTTPS.
3. Signed records and collections
A place is one collection on one host: a chat room, board, wiki, People list, directory, file drop or app collection. Its address looks like http://<host>.sesh/<kind>/<name>, and a join link carries the collection's 64-hex root key after the #.
- Every post and record is signed with Ed25519 by its author. Each kind of record is signed under its own domain string, so a signature for one kind can't be reused as another.
- Satchel pins the collection's root key from the join link and syncs the host's log, checking each entry's signature, its place in the log and any moderation records against the keys it expects. A batch with a bad or repeated entry is refused before anything is stored.
- Posts carry a small proof of work, which makes flooding a place cost something.
- The built-in views (room, board, wiki, profile, directory, file drop and others) are templates listed in a signed registry. Every file must match its hash in the signed entry, and each template runs in a sandboxed frame whose policy blocks all network access.
- On a place tab, a strip on top shows whether the host key checks out, which view you're using and which profile you post as.
- Posts, connect requests and app changes are confirmed in a sheet opened from the toolbar button. The sheet's text comes from Satchel, not from the site, and it sits outside every site frame so a page can't cover it.
Anyone can run their own collection on their own host. On My Satchel, places are grouped by the collection they come from, and Remove host leaves a collection's places and stops syncing with it.
4. Mail
A mail address is a signed card holding your Ed25519 key, your X25519 key and a list of one to three mail hosts. It is shared as a link of the form srnet://mail/<key>…, and People cards and Contacts show it so others can write to you.
- The message is signed by the sender, padded to a power-of-two size bucket (1 to 64 KiB) and sealed to the recipient's X25519 key with an ephemeral X25519 exchange, HKDF-SHA256 and ChaCha20-Poly1305.
- The mail host sees the recipient, a message id, the padded size and the time. It never sees the sender or the content.
- Nothing is registered on a mail host until you open your inbox. You can change hosts, or run your own.
- Voice clips are recorded in a small Satchel window opened from the mic button in Chat or in a new mail. Clips are up to 30 seconds. A clip to a contact is sealed mail; a clip to a room can be played by anyone who can read that room, and room hosts delete clip files after 7 days.
5. File drops
A file is encrypted in the browser before upload with ChaCha20-Poly1305 in 64 KiB chunks, one random key per file version, padded to a power-of-two size bucket so the host learns only the bucket. The file key is wrapped to each recipient's X25519 key. A share link carries the key after the #, which browsers don't send to the host.
6. The app sandbox
A host can offer small apps for a collection. An app is one JavaScript file (at most 256 KiB), published as a record signed by the collection's root key. Satchel fetches it and checks its size and hash before it runs.
- It runs in the QuickJS engine (WebAssembly) inside a dedicated worker. Before the engine starts, the worker removes network and storage interfaces such as
fetch,XMLHttpRequest,WebSocketandindexedDB, and QuickJS never gets bindings to them. - An app can read its own collection, draw a small panel and propose records. It can't sign; a proposed record becomes a post only after you confirm it in the sheet.
- The worker sends only plain data. The tab checks it against a schema and builds the panel itself as text. Calls have time, size and rate limits, and a stuck app is stopped.
- Satchel asks before an app runs the first time, and asks again whenever the host serves a different version.
7. Permissions and why
| Permission | Why |
|---|---|
webRequest, webRequestBlocking | To notice requests for .sesh names (and HTTPS-Only rewrites of them) and load them by their router address. The listeners match only .sesh names, a list of search engines' result pages and the exact router addresses Satchel fetches itself. |
dns | To check that .sesh names resolve through Session Router, and to find a site's router address. |
storage, unlimitedStorage | To keep your encrypted key, mail, contacts and joined places in the browser, without the browser clearing them when space runs low. Satchel also asks once for persistent storage. |
Access to http:// sites ("Access your data for all websites") | Router addresses are plain http:// local addresses, and Firefox has no narrower way to allow them. Satchel uses this only for .sesh. |
*.sesh, 127.0.0.1 | .sesh places, and a local control endpoint on this computer that some Session Router builds offer, used only after you pair with it. |
| Search engine result pages | When you type name.sesh in the address bar, the browser may send it to a search engine. Satchel catches that and opens the site instead. |
Satchel has no content scripts and doesn't read or change other sites. The microphone isn't an install-time permission: Firefox asks for it only when you press record in a voice clip window, and only Satchel's own page calls for it. The manifest declares that no data is collected.
8. Storage and backup
Your key is stored encrypted: PBKDF2-SHA256 with 600,000 rounds and a random 16-byte salt derives an AES-256-GCM key from your passphrase. The public key is stored in the clear next to it so Satchel can show what's yours while locked. The seed is held in memory only while the key is open, and it is dropped after 15 minutes without use, or when you press Lock. Background work, like the automatic mail check, doesn't count as use. Synced collections are kept in the browser's IndexedDB.
Right after a key is made, Satchel asks you to save a backup file, and a reminder stays on My Satchel until you do. Export key saves the same encrypted file at any time. It opens only with your passphrase. Restore from a backup file, on the first screen, brings it back on the same or a new install. Without the passphrase or a backup file there is no way to recover the key.
Copyright the Satchel authors. MPL-2.0.