Security model
A host you don’t have to trust.
SealedGit is designed on the assumption that the server is untrusted. It stores what it is given, serves what it is asked for, and never holds a key. This page sets out exactly what that protects, and what it doesn’t.
- The host
- Untrusted. Stores ciphertext only.
- Keys
- On members’ devices, none on the host
- Each repository
- MLS_
256_ MLKEM1024_ AES256GCM_ SHA512_ MLDSA87 - Security level
- NIST Category 5, post-quantum
- Pushes
- Committing AEAD, with history windows by dual key regression
01
Threat model: the host is untrusted
Assume the server is run by someone you don’t know, broken into by someone you don’t like, or compelled by someone you can’t refuse. SealedGit is built so that none of them learns what is in your repositories, because the host is never given anything it could read.
The host can
- Store ciphertext and serve it back
- See handles, repository names, who is a member, and the size and timing of pushes
- Refuse to serve you, or delete what it stores
The host cannot
- Read code, commit messages, issues, pull requests, reviews, release notes or repository descriptions
- Admit anyone to a repository: that takes an MLS Add from a member’s device
- Substitute its own key during admission without the safety number giving it away
- Let a removed member decrypt anything pushed after the removal
Encryption protects confidentiality, not availability. A host you don’t trust can still refuse service, so keep your own clones. And a member’s device holds that member’s keys: if the device is compromised, so is everything it can read.
02
What is encrypted
What your team writes into a repository is encrypted on a member’s device before the host receives it, and it is decrypted only on members’ devices.
- Code and historyPushed as encrypted git bundles.
- Commit messagesInside the encrypted bundles, with the code they describe.
- Issues, pull requests and reviewsEncrypted to the repository’s members and decrypted on their devices.
- Releases and release notesEncrypted to the repository’s members.
- Repository descriptionsEncrypted. The repository’s name is not; see below.
- Large filesKept in SealedGit’s own sealed large-file store,
shub lfs. - Ref updatesStored as hash-chained RefHeads, each one committing to the one before it.
A committing AEAD for every push
Pushes are sealed with a committing AEAD. A ciphertext is bound to the key that produced it, so it cannot be made to open, under some other key, into different contents.
Dual key regression for history windows
History windows use dual key regression, so a grant can cover a bounded stretch of a repository’s history rather than all of it or none of it. See history grants.
03
What the host can still see
Encryption hides contents. It cannot hide that you use a host, so some metadata stays visible. This is all of it.
- HandlesYour username, and the usernames of the people you work with.
- Repository namesThe name of each repository. Descriptions are encrypted; names are not.
- MembershipWho is a member of which repository.
- Push size and timingHow much is pushed, and when.
- Your email addressHeld by the accounts service, so it can send confirmation links, password resets and invitations.
Name repositories with this in mind. If a name would itself give something away, choose another.
04
Keys live on members’ devices
Every key that can decrypt a repository is held on a member’s device. The host holds no key that would let it read your repositories, and nothing in SealedGit hands it one.
That is why the SealedGit app runs on your own computer, on macOS or Linux, and opens in your browser at a local address: it decrypts where your keys are. Signing in to the app enrols your device, which is what lets a member admit it to a repository. Start it with shub app.
05
Each repository is an MLS group
SealedGit uses Messaging Layer Security (MLS) to agree keys among the member devices of a repository. Every repository is its own group, on a post-quantum ciphersuite at NIST security category 5.
MLS_
- Key encapsulation
- ML-KEM-1024
- Signatures
- ML-DSA-87
- AEAD
- AES-256-GCM
- Hash
- SHA-512
NIST security category 5
NIST rates post-quantum parameter sets in five security categories. Category 5, the highest, means an attack should cost at least as much as a key search on AES-256. ML-KEM-1024 (FIPS 203) and ML-DSA-87 (FIPS 204) are the category 5 sets of NIST’s standard key-encapsulation and signature schemes, and AES-256-GCM, which encrypts the group’s messages, is the yardstick itself. Where each algorithm is used
These are the algorithms NIST standardised. The implementations SealedGit links are open-source Rust libraries, not a FIPS 140-validated module.
Epochs
An MLS group moves forward in epochs. Each change of membership, whether a member is admitted or removed, starts a new epoch with new keys. The host carries the MLS framing that makes this happen, but the framing is opaque to it: the host can neither read it nor take part.
Here is one repository across four epochs, and who can decrypt what in each.
| Member | Epoch 1you and dee | Epoch 2ben admitted | Epoch 3dee removed | Epoch 4cy admitted |
|---|---|---|---|---|
| youthroughout | reads | reads | reads | reads |
| benfull history | history | reads | reads | reads |
| deeremoved in 3 | reads | reads | none | none |
| cyforward-only | none | none | none | snapshot |
- reads: decrypts what is pushed in that epoch
- history: earlier epochs, opened by a full-history grant
- none: cannot decrypt
For cy, “snapshot” means the tree as it is at the join, then every change afterwards.
06
An invitation is not access
Getting someone into a repository takes two separate acts. The host can help with the first. Only a member’s device can do the second.
-
Invite a member
Inviting an email address records an invitation and sends a link. No keys change hands, and the invitee can read nothing yet.
shub repo invite-email you/ledger --email teammate@example.com -
Accept the invitee
The invitee signs up, or signs in, and accepts the invitation.
-
Compare both of you
Compare the safety number for the invitee’s device key with them, over a channel you already trust: in person, or on a call.
-
Admit a member’s device
A member’s device admits the invitee with an MLS Add, which the host cannot perform. Admission pins the invitee’s device key, so the host cannot substitute a key of its own.
shub repo admissions --repo you/ledger --user <their handle> --key-package-digest <their digest>
Why the comparison matters: admission pins the exact device key you compared. A key substituted by the host would show a different safety number, and a mismatch is your signal not to admit.
07
Removing a member rotates the epoch
Removal starts a new epoch whose keys the removed device never receives. From then on it cannot decrypt anything that is pushed, as dee’s row in the example shows.
Removal looks forward, not back. It cannot reach into a device and delete what that device already fetched and decrypted. If a removed member could read secrets in the repository, rotate those secrets.
08
Full-history and forward-only grants
A member’s access to the past depends on their grant. There are two kinds.
Full history
The whole repository
The member can read the repository’s entire history, from the first push. That is ben in the example.
Forward-only
From the join onwards
A snapshot of the tree as it is at the join, and every change afterwards, but none of the earlier history. That is cy.
History windows use dual key regression, which lets a grant open exactly the stretch of history it covers, and nothing outside it.
09
What SealedGit deliberately does not do
Each of these would require the host to read plaintext, so SealedGit does not offer them.
- WebhooksA webhook reports what changed in a repository. The host can’t report on what it can’t read.
- Hosted CI runnersRunning your build on the host’s machines would mean giving them your plaintext.
- Hosted codespacesAn editor on the host is your source code, decrypted, on the host.
- The Git LFS wire protocolLFS hands file contents to the server as they are. Use SealedGit’s sealed large-file store,
shub lfs. - Shallow or partial clonesCutting history down on the server takes a server that can read the history.
- Host-side code search, diffs and mergesAll three need the plaintext. Do them on your machine, with git.
Try it
Read the model. Then push something sealed.
Create an account, open the SealedGit app on your computer, and make your first repository.