Home / SG/Vault / Git repos inside vaults

Git repos inside vaults

Engineering preview. A git repository is, in the end, just files — which means a vault can hold a complete repo, .git folder included, giving you an end-to-end-encrypted remote for your full history. The missing piece is legibility: making that history readable wherever the vault lands, without assuming a git binary. That piece now exists.

A pure-Python git reader

The sgit project has a working git reader — ~600 lines of implementation in the same Type_Safe style as sgit itself — that reads the .git object store directly: loose objects, packfiles (index v2, including large offsets), delta reconstruction (both OFS and REF deltas), symbolic refs and packed-refs. No git binary, no gitpython, no dulwich — its only dependency is the same type-safety framework sgit already uses.

Verified against sgit's own development repository (900+ commits) during review:

>>> svc = Git_Repo__Reader__Service(repo_path='…/SGit-AI__CLI')
>>> [b.name for b in svc.branches()]
['dev', 'claude/sgit-positioning-proposal…']
>>> svc.head_commit()
HEAD 2e2dd4c · 1 file changed · author + message parsed
>>> len(svc.object_parser.flatten_tree(head.tree_sha))
1161   # every file in HEAD, resolved through packfiles and deltas

Why this matters for vaults

Run git and sgit side by side

The integration runs the other way too: the same folder can be a git repository and an sgit vault at once — git's ecosystem (remotes, pull requests, CI, GitHub Pages) over the working files, sgit's zero-knowledge sync over the same content, and a git remote that doubles as an encrypted mirror of the vault itself.

Why this is safe by construction: the bare/ store is byte-parity with what the sync server holds, and the server is untrusted by design — everything in bare/ is protected by the vault key, not by where it sits. Git exposure of bare/ is therefore equivalent to server exposure, which the zero-knowledge model already accepts. A git remote is just a second untrusted server — one that also gives you distribution, replication and history for free.

The boundary reduces to three rules:

# .gitignore — the whole of it
.sg_vault/local/   # plaintext tier: vault key, push token, unwrapped private keys
.sg_vault/work/    # scratch area — may hold plaintext transiently
*.pem              # defence in depth: no unwrapped key material anywhere, ever

Pair it with a .gitattributes that marks .sg_vault/** as binary -diff -merge: ciphertext has no meaningful line diff, and a textual merge would corrupt it. Everything else is committed — including the plaintext working tree, which is the point: the vault's contents readable, searchable and reviewable on GitHub, with blame, PRs and CI on top. That makes repo visibility a content decision, not a crypto one: bare/ is safe either way; your documents are what you are choosing to expose.

The round trip: edit on GitHub, pull back into the vault

Because the working tree is real files, the flow runs both ways. Edit a file in the GitHub web editor, merge a colleague's pull request, let a CI job rewrite a document — then git pull into the local clone, and sgit sees exactly what changed, the same way it sees your own edits: sgit status reports them, sgit commit records them in the vault's history, sgit push seals them to the server. Two provenance trails, both kept: git records who proposed and reviewed a change; sgit records it landing in the encrypted vault.

Before first publication: audit, then keep the discipline

The vault serving this site ships the full .gitignore / .gitattributes pair — clone it for a working example. And once the vault is on GitHub, the same repo can serve the app itself: see static hosting on GitHub Pages.

What a key rotation does to the mirror

Rotating a vault key (delete-on-remoterekeypush) re-encrypts every object under a new key, so every content-addressed ID changes and the entire store is replaced. In the git mirror that lands as one large commit — for this site, 336 objects deleted and a fresh set added. Nothing breaks (the .gitattributes binary rule means no diff churn), but two consequences are worth knowing before you rotate:

This section exists because it happened here: the passphrase for this site's own vault was written into a tracked build file (an anti-leak regex that became the leak), reached three public commits, and was caught by the audit above. The key was rotated the same hour and the old vault deleted from the server; the exposed content was this public website. Recorded rather than quietly fixed — see the release history.

Status — honestly