from0d284658037d700c9f020230cde14b104e0f82b2c96103f3c91141fbce494675 → 0d284658
to361b2026ff7e242af592ac80b0beb00ae226ab7805a022c4fbc4263d6b9464ac → 361b2026

+18 −0

11 # loot
22
33 A from-scratch source-control system.
44
55 **Thesis:** visibility and permissions belong to *content and changes*, not to
66 the *repository*. Commit your `.env`. Keep files private inside a shared repo.
77 Embargo a security fix: merge it, cut the release, reveal the source later.
88
99 This is the unsolved problem in modern version control. Ergonomics (jj already
1010 nails them) are a layer for later.
1111
12+## Security
13+
14+loot's cryptography is written from scratch and has never been independently
15+reviewed. What each reader — a relay, a forge, a collaborator, the anonymous
16+internet, the git mirror, and anyone holding your `.loot/` directory — can
17+actually read is stated tier by tier at
18+**<https://loot.millerbyte.com/trust>**. Read it before you put anything in a
19+loot repo whose disclosure would hurt you. Two things it says that the pitch
20+does not: the default tier is one a relay reads by design, and path names are
21+never encrypted at any tier.
22+
23+**Report a vulnerability to <security@millerbyte.com>** — not to the public
24+Discord. One maintainer, best effort, expect days rather than hours. ⚠ This
25+file is not the published route: the GitHub repo is permanently private
26+(ADR 0045/0064), so a `SECURITY.md` here and GitHub's private-advisory button
27+both reach nobody. `/trust` is the route; this section exists for whoever
28+already has repo access.
29+
1230 ## What works today
1331
1432 The full loop from first init to relay-based collaboration is functional. This
1533 block is the CLI's **full verb list** — all 61 verbs, regenerated from
1634 `loot --help` rather than curated (a guard test pins it to the dispatch table,
1735 so it cannot drift again; #1107):
1836
1937 ```text
2038 abandon absorb adopt apply archive attest bisect blame bundle buoy burn
2139 cherry-pick clone completions config conflicts describe diff doctor duplicate
2240 edit embargo-status evolog ferry gc grant grant-status grants grep id init
2341 keygen lane lanes log manifest maroon migrate new op peer pull pull-grants
2442 purges push rehome relay remote resolve revert serve shortlog split squash
2543 status surface tutorial undo verify view whoami
2644 ```
2745
2846 `loot <verb> --help` prints any verb's own usage block without running it.
2947
3048 ### Try it: private `.env` in a shared repo
3149
3250 ```bash
3351 cargo build --release
3452 export PATH="$PWD/target/release:$PATH"
3553
3654 cd $(mktemp -d)
3755 printf 'TOKEN=supersecret\n' > .env
3856 printf '# My Project\n' > README.md
3957 printf '.env restricted=alice\n*.md public\n' > .lootattributes
4058
4159 loot init --identity alice
4260 loot status -m "initial work"
4361 loot surface # alice: restores both README.md and .env
4462
4563 # switch to a non-keyholder to prove it
4664 printf mallory > .loot/identity
4765 rm -f .env README.md
4866 loot surface # mallory: README.md appears; .env stays sealed
4967 ```
5068
5169 The `.env` ciphertext lives in `.loot/` the whole time. Mallory cannot decrypt
5270 it, and if she snapshots and re-syncs, the sealed file is carried forward
5371 untouched — snapshot is visibility-aware.
5472
5573 ### Sync over a relay
5674
5775 A relay stores and forwards ciphertext it cannot read. Restricted keys never
5876 travel in a sync bundle (ADR 0003), so the relay's zero-knowledge property is
5977 enforced at the wire level, not by policy.
6078
6179 ```bash
6280 # Terminal 1: run a relay
6381 loot serve --dir /tmp/relay --addr 127.0.0.1:4000
6482
6583 # Terminal 2: alice pushes
6684 loot remote add origin http://127.0.0.1:4000
6785 loot push
6886
6987 # Terminal 3: bob pulls (bob only sees public content)
7088 loot clone http://127.0.0.1:4000 ./bob-repo --identity bob
7189 ```
7290
7391 ### Grants: sharing a content key
7492
7593 ```bash
7694 # alice knows bob's public key (from `loot whoami` on bob's machine)
7795 loot peer add bob "ssh-ed25519 AAAA..."
7896
7997 # deliver a sealed grant via the relay
8098 loot grant --relay origin .env bob
8199
82100 # bob fetches and applies it
83101 loot pull-grants # verifies alice's signature, checks peer registry
84102 loot surface # now bob can read .env
85103 ```
86104
87105 ### Embargo: timed reveals
88106
89107 ```bash
90108 # mark a file as embargoed until unix timestamp 1800000000
91109 echo "VULN_DETAILS=CVE-2025-XXXX" > security-fix.txt
92110 printf 'security-fix.txt embargoed=1800000000\n' >> .lootattributes
93111
94112 loot status -m "patch for CVE-2025-XXXX"
95113 loot push # relay holds the ciphertext; key withheld until reveal_at
96114 ```
97115
98116 At `reveal_at`, `flush_escrow` promotes the key so anyone who pulls can read it.
99117 The seam for a third-party key custodian (network escrow) is designed and ready.
100118
101119 ## Architecture
102120
103121 ```text
104122 crates/
105123 loot-core canonical engine: encrypted DAG, per-content visibility, convergence
106124 loot-identity ed25519 keypairs, x25519 ECIES, signed push envelopes, peer registry
107125 loot-net relay HTTP server + sync client (stow/negotiate/grant mailbox)
108126 loot-cli the `loot` binary — commands are thin verbs over Workspace
109127 loot-bench shared 50k-file benchmark workload
110128 spike-dag thin shim re-exporting loot-core (bake-off compat)
111129 spike-crdt non-canonical CRDT model (retained so the bake-off is reproducible)
112130 ```
113131
114132 ### Key modules
115133
116134 | Module | What it owns |
117135 | --- | --- |
118136 | `loot-core::sealed` | Per-content encryption, key custody, embargo, public-content compression (ADR 0003, 0007, 0020) |
119137 | `loot-core::converge` | Merger/relay convergence rule — decrypt-then-merge (ADR 0001) |
120138 | `loot-core::engine` | Encrypted content-addressed DAG: put/get/record/surface/bundle/apply |
121139 | `loot-core::manifest` | Grant audit trail: grantee, grantor pubkeys, timestamps |
122140 | `loot-identity` | ed25519 sign/verify, x25519 derive, ECIES seal/unseal, push envelope |
123141 | `loot-net::mailbox` | Relay grant mailbox: pubkey-addressed, content-addressed loose blobs |
124142 | `loot-cli::workspace` | Ambient repo: identity, clock, persistence, idempotent snapshot |
125143
126144 ### ADRs (docs/adr/)
127145
128146 | # | Decision |
129147 | --- | --- |
130148 | 0001 | Per-content decrypt-then-merge convergence |
131149 | 0002 | Encrypted DAG as the canonical foundation (bake-off winner) |
132150 | 0003 | Sealed content module + keyring custody (restricted keys never travel) |
133151 | 0004 | Drop plaintext dedup equality oracle |
134152 | 0005 | CLI slice, persistence, .lootattributes |
135153 | 0006 | JJ-style workspace auto-snapshot |
136154 | 0007 | Embargo escrow module |
137155 | 0008 | Grant log and targeted key bundles |
138156 | 0009 | Two-level revocation |
139157 | 0010 | Forward-maroon implementation |
140158 | 0011 | Relay stow append-only |
141159 | 0012 | Per-object loose storage |
142160 | 0013 | Named remotes and grant bundle delivery |
143161 | 0014 | Identity keypairs: ed25519 OpenSSH, signed push envelopes |
144162 | 0015 | Grant authentication and trust (grantor signs, peer-registry gate) |
145163 | 0016 | Identity portability: export/import with passphrase wrapping |
146164 | 0017 | RepoStore: one home for the `.loot/` layout |
147165 | 0018 | Signed changes: author in id + validity enforcement |
148166 | 0019 | Format versioning + compatibility gate (newer reads older) |
149167 | 0020 | Compress public content (Zstd); format major → 2 |
150168 | 0021 | Object-level "wants" negotiation on push/pull |
151169 | 0022 | Concurrent-agent model: docks, harbor, optimistic convergence |
152170 | 0023 | Agent-facing machine output: porcelain-first, reconciliation verbs |
153171 | 0024 | Resumable transfer via batched, negotiated sync |
154172
155173 See [CONTEXT.md](CONTEXT.md) for the full domain glossary.
156174
157175 ## Build & test
158176
159177 ```bash
160178 cargo build
161179 cargo test # ~25s — includes HTTP relay integration tests
162180 cargo test -p loot-core # fast, no I/O, 67 tests
163181 ```
164182
165183 ## Command reference
166184
167185 ```text
168186 loot init [--identity <name>] initialize a repo (identity from global config if omitted)
169187 loot clone <url> <dir> clone a relay into <dir>; ends with a materialized working tree
170188 loot config set <key> <val> set a global config value (~/.config/loot/config)
171189 loot status [-m <message>] snapshot the working tree into the working change (idempotent)
172190 loot describe -m <message> name the working change
173191 loot new finalize the working change; start a fresh one
174192 loot surface materialize what the current identity may see
175193 loot lane new [--name <n>] spawn a sealed lane (isolated tree + tip) over the shared store
176194 loot lanes list lanes with their tip, in-flight PR, and status
177195 loot log show change history with visibility hints
178196 loot gc [--dry-run] prune loose objects no change references
179197 loot verify [--accept-loss] integrity-check the object store (exits 1 on corrupt/missing; --accept-loss records unrecoverable losses)
180198 loot bundle <file> write a sync bundle (ciphertext, no keys)
181199 loot apply <file> merge a peer's bundle (idempotent)
182200 loot grant <path> <identity> <file> write a targeted grant bundle (file delivery)
183201 loot grant --relay <remote> <path> <id> seal and deliver a grant via relay mailbox
184202 loot grants [<url>] peek pending grant count (no download)
185203 loot pull-grants [<url>] fetch, verify, and apply sealed grants from relay
186204 loot maroon [--hard] <path> <identity> cut off <identity> from future access
187205 loot migrate <path> <vis-spec> change a path's visibility
188206 loot manifest show the grant audit trail
189207 loot conflicts list paths needing resolution
190208 loot resolve <path> <file> resolve a conflict
191209 loot remote add <name> <url> register a relay URL
192210 loot push [<url>] publish changes to a relay
193211 loot pull [<url>] fetch and merge changes from a relay
194212 loot serve [--addr <host:port>] run a relay
195213 loot keygen generate an identity keypair
196214 loot whoami show identity and public key
197215 loot id export <file> export keypair, passphrase-encrypted
198216 loot id import <file> import keypair from passphrase-encrypted file
199217 loot peer add <name> <pubkey> register a peer's public key
200218 loot peer list list known peers
201219 ```
202220