Repository navigation
authenticate (and encrypt) index and cache store objects, fixes #9819, fixes #10235 - #10406
Merged
ThomasWaldmann merged 6 commits intoSep 23, 2026
Merged
Conversation
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## master #10406 +/- ##
==========================================
+ Coverage 88.28% 88.33% +0.04%
==========================================
Files 103 103
Lines 18904 18939 +35
Branches 2937 2938 +1
==========================================
+ Hits 16690 16729 +39
+ Misses 1540 1539 -1
+ Partials 674 671 -3 ☔ View full report in Codecov by Harness. |
ThomasWaldmann
force-pushed
the
encrypt-cache-index
branch
from
September 22, 2026 21:42
1e2131d to
f17f945
Compare
The index/ and cache/ store objects are to be protected by the repository key, like the objects in the packs. These methods wrap a store object in the key's envelope (encrypted and authenticated in the encrypting modes, authenticated only in the authenticated-* modes), binding the object name (or, for content-addressed objects, the namespace) into the envelope as AAD. Without a key, they fail with the new Repository.KeyRequired error (rc 28). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
key_factory, key_creator and Manifest set the key as the repository key, so the index/ and cache/ store objects can be protected by it. borg repo-create writes the empty chunk index after the key was created, Repository.create() does not write one any more. borg debug get-obj, put-obj and delete-obj load the key, as they access the chunk index; delete-obj without a key aborts now instead of keeping the superseded gap bytes. Tests: repositories opened by the tests get a key (a test key, or the real key via open_repository). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…orgbackup#9819 The index/ fragments are now protected like the objects in the packs: encrypted and authenticated in the encrypting modes, authenticated only in the authenticated-* modes. A fragment's name is the store hash of the stored envelope, so borg check keeps verifying index/ by name, also server-side. As the envelope is randomized in the encrypting modes, there is no "fragment with this content already exists" upload skip any more. A fragment that does not match its name or fails the authentication (e.g. a plaintext fragment written by an older borg 2 beta) is corrupt: the chunk index is rebuilt from the packs. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…y's envelope, fixes borgbackup#10235 cache/checked-packs and cache/referenced-by-archive.* are now protected like the objects in the packs (encrypted and authenticated in the encrypting modes, authenticated only in the authenticated-* modes), instead of having a store hash appended. The envelope binds each object to its name: a references cache copied to another archive's name is ignored and rebuilt by scanning that archive. PackTracker takes the repository instead of its store. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…tory key Describe the store object envelope and update what the store sees of the index/ and cache/ objects, see borgbackup#9819, borgbackup#10235. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- the envelope AAD also holds the repository id and a tag that tells a full object name and the namespace of a hashed-name object (index/) apart, so objects of another repository using the same key material, or envelopes made for the other kind of name, fail the authentication. - index fragments are not hashed on load to verify their names: the authentication already proves their content (borg check still verifies the names). The error message says "authentication failed". - a corrupt chunk index (a fragment that fails the authentication or does not deserialize) aborts every command that needs it, with the new CorruptChunkIndexFragment error (rc 94): run "borg check --repair". Only the commands that rewrite the whole index anyway, under an exclusive lock (borg check --repair, borg compact, borg repo-compress), rebuild it from the packs; borg compact --dry-run aborts. - borg check: such a fragment is an index error, --repair rebuilds the index from the packs. A check without --repair never rebuilds the index, and the index cross-check is skipped if the store hash check already found the index corrupt. Help text updated. - remember the plaintext hash of the fragments read in a session and do not store a fragment with known content again (unless forced), so e.g. a crashed repack converges again. - debug get-obj/put-obj/delete-obj: load the key via with_repository(), including the security checks. - tests: open_repository() always loads the real key, more tests; docs. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
ThomasWaldmann
force-pushed
the
encrypt-cache-index
branch
from
September 23, 2026 00:32
f17f945 to
cc10f61
Compare
Contributor
4 tasks done
ThomasWaldmann
pushed a commit
that referenced
this pull request
Sep 23, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
The
index/chunk index fragments,cache/checked-packsandcache/referenced-by-archive.*are now stored in the repository key's envelope, exactly like the metadata and data slots of the objects in the packs: encrypted and authenticated in the encrypting modes, authenticated only in theauthenticated-*modes. No new crypto, no mode-specific code in the repository layer.Fixes #9819, fixes #10235.
Changes
Repository.set_key(),store_encrypt_store(),store_load_decrypt(): wrap a store object inkey.encrypt(b"", value, aad=...). The AAD isb"borg-store-object\0", the repository id, a tag and the object name (tagn) or, for the content-addressed index fragments, the namespaceindex(tagh). An object copied to another name, or from another repository using the same key material, fails the authentication. Without a key, both fail closed with the newRepository.KeyRequired(rc 28), never falling back to plaintext.key_factory,key_creator,Manifest), including the source repository ofborg transfer.borg repo-createwrites the empty chunk index after creating the key (Repository.create()no longer does).borg checkkeeps verifyingindex/by name (server-side forrest://) without the key. Loading a fragment does not hash it to verify its name: the authentication already proves its content.CorruptChunkIndexFragmenterror (rc 94):Run "borg check --repair" to rebuild the chunk index.Only the commands that rewrite the whole index anyway, under an exclusive lock, rebuild it from the packs instead:borg check --repair,borg compactandborg repo-compress(borg compact --dry-runaborts). Normal commands never modifyindex/because of a corrupt fragment.borg check: the repository check reports such a fragment as an index error (rc 1 with--repository-only). The archives check needs the index, so a full check without--repairthen aborts (rc 94).--repairrebuilds the index from the packs and drops the fragment.cache/checked-packsand the archive reference caches: envelope instead of an appended store hash. A reference cache copied to another archive's name is ignored and rebuilt by scanning that archive (a hostile store can no longer make compact drop chunks an archive uses).borg debug get-obj/put-obj/delete-objload the key viawith_repository()now (they access the chunk index), including the usual security checks;delete-objwithout a key aborts instead of keeping the superseded gap bytes.PackTrackertakes the repository instead of its store.borg checkhelp.What stays visible to the store
Object names: the number and sizes of the index fragments (a rough chunk count), the archive ids that have a reference cache, that a check ran.
cache/chunkindex-invalid(an empty marker) andlocks/*stay plain.Existing beta repositories
No migration code (b25 already requires new repositories). On an older repo, the plaintext fragments fail the authentication, so the commands that need the chunk index abort with
CorruptChunkIndexFragment;borg check --repair(orborg compact) rebuilds the index from the packs. Covered bytest_plaintext_index_fragment_of_an_older_beta.Tests
store_objects_test.py): end-to-end inaes256-ocbandauthenticated-sha256with inspection of the raw store objects, a copied reference cache, the older-beta plaintext fragment (commands abort,borg check --repairfixes it), andborg compactrebuilding a corrupt index (--dry-runaborts).open_repository()loads the real key,repository_test.py/cache_test.pyuse a test key).🤖 Generated with Claude Code