Validate blob hashes, offsets and lengths from the destination (closes #155)
check / check (pull_request) Successful in 1m22s
check / check (push) Successful in 3m30s

A blob hash read back from the downloaded snapshot database or the store listing was trusted unchecked. A hostile remote could set a hash such as "aa/../../etc" and have a decrypted blob written outside the cache directory, or feed a short or negative value that panicked a command.

blobDiskCache.path now refuses any key with a path separator, and ReadAt rejects a negative offset or length, bounding so a sum cannot overflow past the check. A new isBlobHash helper gates FetchBlob, shallow and deep verify, and restore: buildBlobIndexes rejects every hash from the snapshot database before any fetch. The blobs/ and metadata/ listings skip a non-conforming name, and short-hash prefixes in log and error text go through a panic-safe shortHash helper.

Model: opus-4-8
This commit was merged in pull request #195.
This commit is contained in:
2026-09-22 16:11:28 +02:00
parent d88ed64489
commit 82c51a5337
10 changed files with 549 additions and 53 deletions
+12 -2
View File
@@ -425,12 +425,12 @@ func (s *restoreSession) downloadNextBlobSet(plan *restorePlan) (bool, error) {
blob, ok := s.blobByHash[hash]
if !ok {
return false, fmt.Errorf("%w: %s", errBlobMissingFromIndex, hash[:16])
return false, fmt.Errorf("%w: %s", errBlobMissingFromIndex, shortHash(hash))
}
err := s.downloadBlobToCache(hash, blob.CompressedSize)
if err != nil {
return false, fmt.Errorf("downloading blob %s: %w", hash[:16], err)
return false, fmt.Errorf("downloading blob %s: %w", shortHash(hash), err)
}
s.result.BlobsDownloaded++
@@ -474,6 +474,16 @@ func (v *Vaultik) buildBlobIndexes(
blobByHash := make(map[string]*database.Blob, len(blobsByID))
for id, blob := range blobsByID {
hash := blob.Hash.String()
// The snapshot database is untrusted. A hash that is not 64
// lowercase hex characters could steer a later fetch to a path
// outside the cache directory, so reject it here, before any
// blob is downloaded.
if !isBlobHash(hash) {
return nil, nil, fmt.Errorf(
"%w: %s", errInvalidBlobHash, shortHash(hash))
}
blobIDToHash[id] = hash
blobByHash[hash] = blob
}