sgit and Git
sgit is not a copy of Git with encryption on top. It has Git's verbs, so a Git user is at home in an hour. But the host reads nothing, and that makes possible a set of things Git cannot do at all. This page is the map, row by row, checked against sgit-ai 0.26.0:
- what only sgit does;
- what both do, and how they differ;
- where Git is ahead, and whether sgit can close the gap;
- what zero knowledge rules out for good.

In one paragraph. Use sgit when the files must stay unreadable to whoever stores them, when readers should need no account, or when many agents work in one large vault, one folder each. Use Git when you need line-level merges, blame and bisect today, a forge's review and CI, or twenty years of tooling. Almost everything on Git's side runs on the client in Git too, so sgit can build it. What sgit can never do is anything that needs the host to read your files.
The evidence:
- the sgit CLI team's claim-by-claim review of the newsroom's article What sgit does that Git does not, against the CLI's source and threat model;
- three behaviours re-run by this site against the PyPI release, the script;
- Git's and GitHub's own documentation.
1. What only sgit does
"No equivalent" means the mainstream Git toolchain and GitHub. Add-ons such as git-crypt and git-remote-gcrypt cover parts of the first row, with the trade-offs their own READMEs state.
| # | What sgit does | How | Git's nearest answer |
|---|---|---|---|
| 1 | The host reads nothing. Contents, file names, commit messages and branch names are encrypted on the client (AES-256-GCM) before they leave it | every command; objects stored under opaque ids | git-crypt encrypts contents only; its README says it "does not encrypt file names, commit messages" or other metadata |
| 2 | A read key is a capability, not a permission. It is derived one way from the vault key, so it can never become write access. It reads from any static host, with no account and no server code | clone --read-key, --transport static, sgit publish | read access is a role a server enforces (a collaborator, a deploy key), or the repository is public |
| 3 | Custody without access. Copy a published vault you cannot read, keyless, and verify it. A backup service or a second host holds and checks the vault and learns nothing | sgit vault mirror [--verify DIR] | a mirror of a Git repository can read all of it |
| 4 | Write without read. An append lane lets a sender put something into a vault and learn nothing, not even whether anyone else writes there: the answer is {"ok": true} | the server's append lanes; no sgit command yet | no Git or GitHub role can write without reading |
| 5 | One folder by byte ranges from plain storage. A scoped clone of a pack vault reads the packs' directories, then only that folder's byte ranges, verifying each object by its hash. On 76,000 files: 2.8 s and 12.5 MB, against 36 s and the whole 88 MB pack | clone --path on a pack vault (0.26.0) | Git's dumb HTTP takes whole packs; its partial clone needs a smart server that reads the content |
| 6 | Indexes the writer ships, encrypted and signed. Each push carries an index pack beside its data pack, so readers plan pulls and clones without walking trees. An index is a plan, never a proof: every object is still verified by its id | automatic on pack vaults (0.26.0); pack index build | verify | stats | Git's commit-graph and pack indexes are built by each reader, as local plaintext caches |
| 7 | Policy carried by the vault. The format gate (min_client, features) and signatures-required live in the encrypted branch index. Every sgit client enforces them before it writes anything | sgit vault format (0.19.0) | signature checks are each user's local config; a repository cannot require them of whoever clones it |
| 8 | A rewind is refused by name, on every clone. A remote head that does not descend from the last one this clone accepted is refused, even though the host took the forced push (it cannot read refs) | pull refuses; pull --accept-rewind takes it (0.19.0) | the default + refspec takes a forced update with one line of output; receive.denyNonFastForwards is a server setting |
| 9 | A secret guard at commit. A vault key in a zip under any name, a hard link to one of the clone's secrets, and .env files are refused | commit, write | nothing built in; GitHub's push protection runs on a server that reads the content |
| 10 | Rotate the key that encrypts the history. Move a vault to a new key, and optionally a new server | sgit vault rekey, vault move | a leaked credential is revoked on the server; the content was never encrypted |
| 11 | A computable cache. A hot path gets a fixed-cost read at a location only key holders can compute, so an edge function reads it in one request, without walking trees | sgit cache add | every read walks from a ref |
| 12 | Seal a file to a person. Encrypt a file to someone's public key, then commit the sealed file | sgit pki encrypt --recipient | Git signs commits; it has no "encrypt this to that person" |
Checked by this site against 0.26.0: rows 8 and 9, and write in section 2 (the script and its output). The other rows are from the CLI team's review of the code, and from the release notes of 0.24.0, 0.25.0 and 0.26.0.
2. What both have, and what differs
| Git | sgit | What differs |
|---|---|---|
| packs | pack mode (0.24.0) | sgit's packs are ciphertext on plain object storage; Git's are delta-compressed, sgit's are not |
kept packs above fetch.unpackLimit | clone --pack-store (0.26.0) | the same idea; sgit verifies each object on every read |
| partial clone plus sparse-checkout | clone --path, fetch <folder> to widen, clone --sparse (0.18.0) | against ciphertext on S3. A commit outside the scope is refused by name, and two scoped clones in different folders never conflict |
clone --no-checkout | clone --no-checkout, then sgit checkout <paths> (0.26.0) | the agent loop: pull --no-checkout, then checkout --since-pull writes only what changed |
shallow clone, fetch --unshallow | clone --depth N, fetch --unshallow (0.18.0) | combines with --path |
| bare clone | clone --bare, clone-headless, clone-branch, clone-range | |
git commit <paths> | sgit write <path> --file F [--also P:F …] [--push] [--json] | whole files, committed atomically to HEAD without scanning the working copy; other edits stay uncommitted. There is no staging area and no hunk selection |
| signed commits and tags | commits signed by each clone's key; verification and signatures-required (0.19.0); signed tags (0.21.0) | sgit's check is required by the vault (row 7). A signature proves a key holder, not a person; see section 5 |
log, diff, show, reflog | history log --grep --since --until --author --file --patch --stat --json, history diff A..B, history show, history reflog | history log --vault-key runs with no clone |
revert, reset, commit --amend | history revert, history reset, history undo, commit --amend (0.21.0) | |
stash | vault stash | |
merge and conflicts | branch merge, resolve --show, merge-abort | whole-file: a conflicting file is written as <path>.conflict; see section 3 |
remote | remote add / list / set-url | |
fsck | check fsck, check verify | checks encrypted objects and their signatures; can repair |
| reading over dumb HTTP | --transport static | one file from a normal vault is a ref, a commit, one tree per folder level and the blob, as plain GETs; one batch request for a path declared with sgit cache add |
The full Git-to-sgit command table is sgit for Git users.
3. Where Git is ahead, and whether sgit can close it
Git is a client-side program. Almost all of its lead runs on the client in Git too, on plaintext the client holds. sgit holds the same plaintext, so it can build the same things with no new server feature, unless the row says so. Size: S is days, M a week or two, L more, XL a design first.
| Git has | sgit today | How sgit would build it | Size |
|---|---|---|---|
| line-by-line three-way merge | whole-file, with <path>.conflict | a diff3 merge of base, ours and theirs for text files under a size cap; conflict markers inline; whole-file stays for binaries | M |
| rename detection | none | exact renames from the plaintext content hash the tree entries already carry; similar renames by Git's similarity score | S–M |
a staging area, commit -p | sgit write for whole files | commit <paths> reuses write; -p is a hunk picker over history diff; a real index is a design decision | S / M / L |
| blame | none | walk the file's commits (the index pack's file → commits member makes it cheap) and diff each step | M |
| bisect | none | state in .sg_vault/local/, the midpoint by generation number, which pack indexes already carry | S–M |
| grep over content | history log --grep over messages | sgit grep over HEAD or a commit, decrypting locally; optionally an encrypted local index | S / M |
| delta compression | each blob deflated, no deltas | delta against the previous version at push; the reader rebuilds and verifies by content address; a format feature and a gate | L |
| garbage collection | none; compaction moves old packs to archive/ | vault prune, driven by the client, which alone can read what is reachable, after a grace period as Git's gc.pruneExpire | M |
| read one file with no clone, from the CLI | the browser reader does it on normal vaults | sgit cat --vault-key; on pack vaults, Phase 4 of pack indexes: find the blob's offset in the index, read that range | S / L |
| a command for append lanes | the server feature only | sgit inbox send, sealed with pki encrypt; inbox read drains it into a commit | S–M |
| allowed signers, key expiry, revocation | any read-key holder can mint a signing key | an owner-signed signer registry in the branch index, pinned on first use (closes threat model TM-R01) | L |
| per-folder read access | one key per vault; a scoped clone is not a boundary | a folder key wrapped to each member's public key; others see the folder as the opaque entry scoped clones already carry | XL |
| who pushed, protected branches per person | one shared write key | per-member write tokens with an ACL by ref id, which the host enforces without learning branch names; the signer registry on the client | L, server and client |
| review, CI, every IDE | the vault web UI decrypts in the browser | review objects in the vault; CI runners that hold the read key, woken by a ref-change webhook; a git-remote-sgit helper so Git tooling reads and writes vaults | L each |
| 160-bit ids by default | format 1 is 48-bit; format 2 is 128-bit, opt-in | sgit vault format --set 2 (0.19.0) | shipped, opt-in |
| twenty years of maturity | four weeks of ten releases | time |
4. What zero knowledge rules out
These are limits of the model, not of effort. Each has a substitute on the client.
| Ruled out | Why | The substitute |
|---|---|---|
| anything the host must read to do: server-side search, diffs, a merge button, code scanning, language statistics | the host holds ciphertext; giving it the key ends zero knowledge | do it in the client, the browser, or a runner the key holder controls |
| taking back what a reader already has | no system can; a removed GitHub collaborator keeps their clone too | rotate forward (vault rekey, vault move), delete the old vault; per-folder keys limit what each reader ever got |
| per-folder permissions inside one key | the host cannot tell folders apart, and the key decrypts every object | per-folder keys (section 3) |
| hiding all metadata | sizes, timing, counts and access patterns are visible to any store | padding, fixed-size packs and decoy reads reduce it at a bandwidth cost; only an ORAM-style protocol removes it |
| the host optimising the store: repacking, deltas, dedupe across vaults | the host has only ciphertext; dedupe across vaults would need convergent encryption, which leaks equality | client-side deltas and prune (section 3) |
| a host that enforces fast-forward-only | the host cannot read a ref, so it cannot check ancestry | the client-side rewind guard (row 8), shipped |
5. The qualifiers
Said plainly, so that nobody builds on a stronger claim than the code makes:
- What the host still sees:
- sizes, timing and object counts;
- in pack mode, which commit each push carried;
- which trees are equal, because tree encryption is deterministic;
- with
blob-deflate, how compressible each file is; - the sizes of index packs;
- in path-ordered packs, which blobs share a folder;
- for a scoped clone, which byte ranges it read.
- Reading is a capability; writing is a permission. The write key is a bearer secret that the host checks, with no cryptographic role. A read-key holder who can write to the store can author history that
check verifycalls verified. A signature today proves "a key holder", not a particular clone or person. This is the threat model's top accepted risk, TM-R01, and the signer registry in section 3 closes it. - An index is checked under the vault's signature policy. An unsigned index is accepted unless the vault has
signatures-required. A signing teammate could mislead a fresh clone's working copy, as a commit from that key could.sgit pack index verifycatches it (TM-R42). - A read key cannot be revoked after the fact, and it is per vault, not per folder (TM-R12). The remedy is rotation, as above.
Sources: the CLI team's review of the newsroom's article, written against SGit-AI__CLI at 640069a; sgit-ai 0.26.0 from PyPI and its command reference; the check script, run on 28 October 2026; git-crypt's README; Git's documentation on git-clone and git-config. Where this page and the code disagree, the code wins and this page is wrong: say so on GitHub.
This page as markdown · its source, admin/content/docs/sgit-and-git.md