Docs misstate the remote layout and understate the privacy guarantee #67
Reference in New Issue
Block a user
Delete Branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Three documents describe a remote layout and a privacy posture that the
code no longer has. One of them claims a weakness that was
deliberately fixed, which is the serious part: a security-relevant doc
telling users they leak metadata they do not actually leak.
The code
internal/snapshot/remotekey.go:35-41—RemoteSnapshotKeyreturnshex(SHA256(SHA256("vaultik|" + id))), and gates both themetadata/<remote-key>/path and the manifest'ssnapshot_idfield.TODO.md:37records this landing on 2026-06-26.The docs
docs/REPOSTRUCTURE.md, "Privacy Implications" — claims an observercan determine "When backups were taken (from snapshot IDs)" and "Which
hostname created backups (from snapshot IDs)". Both are false; the
hashing specifically prevents this. For a tool whose headline feature
is "modern encryption" (README:41), shipping a doc that understates the
guarantee is worse than shipping no doc.
metadata/<snapshot_id>/db.zst.age, i.e. aplaintext ID as the directory name.
<hostname>_<snapshot-name>_<RFC3339-timestamp>(e.g.server1_home_2025-06-01T12:00:00Z)", presented as the on-diskdirectory name.
ARCHITECTURE.md:368-371— same plaintext layout.Definition of done
docs/REPOSTRUCTURE.md's "Privacy Implications" section accuratelydescribes what an observer of the destination store can and cannot
infer, given hashed snapshot keys. State plainly what is still
observable (blob count, blob sizes, upload timing, total volume) so
the section stays honest in both directions rather than swinging to
overclaiming.
ARCHITECTURE.md:368-371show the real layout witha hashed key, with a worked example that matches what
RemoteSnapshotKeyactually produces.this ID is never written to the destination in plaintext, and
points at the hashing function.
vaultik|domain-separatedID) is documented once, in one place, and the other two documents link
to it rather than restating it.
metadata leak, stop and file a separate issue rather than papering
over it.
make fmtrun over the changed markdown;make checkgreen.STOP — do not work this issue as written. I got a load-bearing fact
wrong, and acting on the current text would put a false claim into a
security document.
What I got wrong
This issue says
docs/REPOSTRUCTURE.mdis wrong to claim an observer candetermine "When backups were taken", and instructs whoever picks it up to
rewrite that section as a fixed weakness. The timestamp claim is
correct. Backup times are observable.
internal/snapshot/snapshot.go:842-850:The comment asserts the public bytes reveal no timestamp; the next line
writes a plaintext RFC3339 timestamp into exactly those bytes. The
manifest is stored at
metadata/<remote-key>/manifest.json.zst— zstdonly, no
.age, so it is unencrypted at rest and readable by anyonewho can list the destination.
internal/snapshot/manifest.go:15-28— the full set of unencryptedfields is
snapshot_id(hashed),timestamp(plaintext),blob_count,total_compressed_size, and per-blobhash+compressed_size.Corrected picture
Hashing the snapshot ID protects the hostname and snapshot name.
It does not protect the timestamp, because the timestamp is published
separately in the clear. So of
REPOSTRUCTURE.md's two claims:by the hashing.
ID. Right conclusion, wrong stated mechanism.
Revised scope for this issue
REPOSTRUCTURE.mdwhile keeping theconclusion that backup times are observable. Do not delete that
warning.
blob count, total compressed size, and per-blob hashes and sizes —
which together give an observer a size-and-timing profile of every
snapshot.
ARCHITECTURE.md:368-371to show thehashed layout, as originally scoped. That part of this issue was
correct.
snapshot.go:842-844.Whether the plaintext timestamp is acceptable, or the manifest should be
encrypted, is a separate design question — filed as its own issue and not
to be decided here.
My error: I inferred the privacy posture from
remotekey.go's doccomment (which makes the same overreaching claim) instead of reading what
is actually serialized. In a threat-model document, understating a leak
is the worst direction to be wrong in, so I would rather flag this loudly
than quietly edit the issue body.
STOP LIFTED — this issue is unblocked and ready to work.
sneakhas ruled on #81: the unencrypted manifest's plaintext timestampsand per-blob size profile are an accepted, previously made design
decision, not a defect. #81 is closed. So the threat model documents
that exposure as intended behaviour; it is not something to fix, and this
issue must not propose fixing it.
Corrected scope — supersedes both the issue body and my earlier STOP
The original body was wrong in one direction and my STOP over-corrected in
another. What is actually true:
docs/REPOSTRUCTURE.md's conclusion was right all along. Backuptimes are observable. Its error is only the stated mechanism — it
attributes them to snapshot IDs, when in fact IDs are hashed and the
timestamp is published separately, in the clear, in
metadata/<remote-key>/manifest.json.zst. Keep the warning; correct themechanism. Do not delete the warning — that is what my original
text wrongly instructed.
the original issue was correct:
RemoteSnapshotKeyishex(SHA256(SHA256("vaultik|" + id))), one-way, and the manifest storesthe hash rather than the human ID.
behaviour a user can evaluate: backup time, blob count, total
compressed size, and per-blob hashes and sizes — which together give an
observer a timing-and-size profile per snapshot. Presented as a
documented property, not a caveat or an apology.
anyway — object creation times and per-object sizes remain visible at
the storage layer on both
s3://andfile://. This is why theaccepted design is coherent rather than merely convenient.
ARCHITECTURE.md:368-371still show theremote layout with plaintext snapshot IDs as directory names. That part
of the original issue stands unchanged — correct them to the hashed
layout with a worked example matching what
RemoteSnapshotKeyproduces, and make clear the human ID is never written to the
destination in plaintext.
Carried over from #81: two false code comments
These are genuine defects — comments asserting the opposite of the
adjacent code — and are in scope here as documentation fixes:
internal/snapshot/snapshot.go:842-844claims the public bytes "don'treveal hostname/snapshot-name/timestamp metadata", and the very next
statement writes
Timestamp: time.Now().UTC().Format(time.RFC3339)intothose bytes.
internal/snapshot/remotekey.go's doc comment overreaches the same way.Correct both to describe what is actually written. Comment text only —
no behaviour change. If the code turns out not to match any accurate
description, stop and report rather than changing behaviour under a docs
issue.
Definition of done
vaultik|-prefixed ID)documented once, with the other documents linking to it rather than
restating.
proposes altering the manifest format. That decision is settled.
make fmtover changed markdown;script/cibuildexits 0, verifiedper the three-part recipe (expected
okcount, zero(cached)markers, plausible wall time).