Correct the security claims in docs and comments, and record the accepted risks (closes #171)
Docs and comments only; no behaviour change. Corrects ten overclaims the security review found: snapshot names are hashed but the hash uses no secret, so a guessed hostname and name can be confirmed; a blob is named by hex(SHA256(SHA256(uncompressed contents))), stated once in docs/REPOSTRUCTURE.md and referenced elsewhere; double hashing does not hide known content (blob packing does); age uses ChaCha20-Poly1305, not XChaCha20; encryption is required, not optional; a snapshot is marked complete before its metadata is uploaded; the export comment now matches its only caller; deep verify detects corruption, not authorship; adding a recipient does not reach existing data; restore examples target a user-owned directory. Adds an Accepted Risks subsection under Security Considerations with the seven documented risks, cross-referenced from the README. Model: opus-4-8
This commit was merged in pull request #199.
This commit is contained in:
@@ -38,7 +38,7 @@ vaultik snapshot list
|
||||
|
||||
Features:
|
||||
|
||||
* modern encryption ([age](https://age-encryption.org/), X25519 + XChaCha20-Poly1305)
|
||||
* modern encryption ([age](https://age-encryption.org/), X25519 + ChaCha20-Poly1305)
|
||||
* content-defined chunking with deduplication (FastCDC)
|
||||
* incremental backups (only changed files are re-chunked)
|
||||
* multithreaded zstd compression at configurable levels
|
||||
@@ -79,11 +79,13 @@ vaultik snapshot verify <snapshot-id>
|
||||
# with one or more identities, is accepted
|
||||
export VAULTIK_AGE_SECRET_KEY="$(cat vaultik_backup_private_key.txt)"
|
||||
|
||||
# deep verify (downloads and cryptographically verifies every blob)
|
||||
# deep verify (downloads every blob, decrypts it, and re-hashes it to
|
||||
# detect corruption — this checks integrity, not who wrote the blob)
|
||||
vaultik snapshot verify --deep <snapshot-id>
|
||||
|
||||
# restore (requires the private key)
|
||||
vaultik snapshot restore <snapshot-id> /tmp/restored
|
||||
# restore (requires the private key). Restore into a new directory you own,
|
||||
# writable only by you — not a shared location like /tmp
|
||||
vaultik snapshot restore <snapshot-id> ~/vaultik-restore
|
||||
|
||||
# daily cron job: back up, keep a 4-week rolling window of snapshots
|
||||
# 0 3 * * * vaultik snapshot create --cron --prune --keep-newer-than 4w
|
||||
@@ -128,12 +130,13 @@ full `remote_key` from `snapshot list --json` — to restore and verify:
|
||||
# keeps the key out of your shell history)
|
||||
export VAULTIK_AGE_SECRET_KEY="$(cat vaultik_backup_private_key.txt)"
|
||||
|
||||
# restore everything to /tmp/restored, then check every restored file's
|
||||
# chunk hashes
|
||||
vaultik snapshot restore --verify <remote-key> /tmp/restored
|
||||
# restore everything to a new directory you own (writable only by you, not a
|
||||
# shared location like /tmp), then check every restored file's chunk hashes
|
||||
vaultik snapshot restore --verify <remote-key> ~/vaultik-restore
|
||||
|
||||
# optionally, deep-verify the snapshot against the store (downloads and
|
||||
# cryptographically checks every blob)
|
||||
# optionally, deep-verify the snapshot against the store (downloads every
|
||||
# blob, decrypts it, and re-hashes it to detect corruption — this checks
|
||||
# integrity, not who wrote the blob)
|
||||
vaultik snapshot verify --deep <remote-key>
|
||||
```
|
||||
|
||||
@@ -449,14 +452,19 @@ Snapshot IDs follow the human-readable format
|
||||
`<hostname>_<snapshot-name>_<RFC3339-timestamp>` (e.g.
|
||||
`server1_home_2025-06-01T12:00:00Z`), but this ID is never written to the
|
||||
destination store in plaintext. Each snapshot's metadata directory is named
|
||||
with its `<remote-key>`, a one-way double SHA-256 hash of the ID, so a listing
|
||||
of the store reveals no hostname or snapshot name. The backup time is not
|
||||
hidden: manifest.json.zst carries a plaintext timestamp, and object
|
||||
with its `<remote-key>`, a one-way double SHA-256 hash of the ID, so a plain
|
||||
listing of the store shows no hostname or snapshot name. The hash uses no
|
||||
secret, though, so an observer who guesses a candidate hostname and snapshot
|
||||
name can hash it and confirm the snapshot is present; the remote key keeps
|
||||
names out of a listing but does not hide them from a guess. The backup time is
|
||||
not hidden either: manifest.json.zst carries a plaintext timestamp, and object
|
||||
modification times are visible at the storage layer regardless. For example,
|
||||
`server1_home_2025-06-01T12:00:00Z` is stored under
|
||||
`metadata/17f97bcde958748af076b926af59823943db59e80ce7170b40f124dfa28f64aa/`.
|
||||
See [docs/REPOSTRUCTURE.md](docs/REPOSTRUCTURE.md#remote-key-derivation) for the
|
||||
derivation.
|
||||
derivation, and [Security Considerations](docs/REPOSTRUCTURE.md#security-considerations)
|
||||
(including [Accepted Risks](docs/REPOSTRUCTURE.md#accepted-risks)) for what the
|
||||
format does and does not protect.
|
||||
|
||||
### data flow
|
||||
|
||||
@@ -497,7 +505,7 @@ derivation.
|
||||
|
||||
### encryption
|
||||
|
||||
* Asymmetric encryption using age (X25519 + XChaCha20-Poly1305)
|
||||
* Asymmetric encryption using age (X25519 + ChaCha20-Poly1305)
|
||||
* Only the public key is needed on the source host
|
||||
* Each blob and each metadata database is encrypted independently
|
||||
* Multiple recipients supported (encrypt to multiple keys)
|
||||
|
||||
Reference in New Issue
Block a user