Public Access
Take a rotated vault's contents onto the new key as well
Rotating a vault re-keyed the vault and not its contents, which was the deal struck last time: everything already stored stayed sealed under the generation it was written with, every remaining member kept the older keys, and the guarantee was narrowed to "nothing written from now on". That left one gap worth closing — somebody who walked off with the old key could still open old ciphertext they later got hold of — and the reason it was safe to defer is the reason it was cheap to add. A vault at mixed generations reads perfectly well, so the pass that moves items across can stop half way and be run again. VaultResealer walks the vault and rewrites each item as an ordinary upsert against the version the server holds. It never decodes the plaintext: an item is opened and the same bytes are sealed again under a fresh data key, so an item written by a newer client crosses a rotation untouched rather than being re-encoded through this build's codec and quietly losing the fields this build has no concept of. It also means nothing in the pass knows what an item is, which is why one loop covers every type including the ones added after it. A conflict is counted and skipped rather than merged — there is nothing to merge, since no content changes — and the next pass picks the item up at the version the other client left. The half that a pass over stored items cannot see is a change queued before the rotation and pushed after it, which would put a brand-new item into the vault under the key the person who just left still holds. So the push path re-seals a stale payload as it dispatches it, writing the revision back to the outbox first so that a retry sends the same bytes rather than a fresh envelope. Between the two, nothing reaches the server under a superseded generation at all. Queued items are therefore deliberately left alone by the pass: rewriting one there would overwrite the user's unpushed work with the version the server holds, which is the one thing a re-keying pass must never do. Removal runs it last, after a sync — a mirror that is behind produces a batch of conflicts instead of a re-sealed vault — and the status line distinguishes the two guarantees, because they are not the same: a vault fully re-sealed is closed to the person who left, and one with items outstanding is closed only to what happens next. Six tests, and three mutations run against them: making the re-seal return the payload unchanged fails five of the six, making the push path skip re-sealing fails the queued-edit test and only that one, and counting conflicts as applied fails the write-elsewhere test. One of the six was wrong before it was right — it modelled a third-party write by re-pushing an existing payload at a bumped version, which no real client would do, and it took reading the AAD to see that the test was lying rather than the code.
This commit is contained in:
@@ -303,11 +303,13 @@ Four limits, stated rather than discovered:
|
||||
can open, and hands each new key to the members who are left — so nothing written from that point on is
|
||||
readable to them. Everything they already pulled is still on their machine. Rotate the SSH credentials
|
||||
that matter — that is the actual remediation, and it is why there is no button labelled anything stronger.
|
||||
- **A rotation re-keys the vault, not what is already in it.** Existing items stay sealed under the
|
||||
generation they were written with, and everybody still in the team keeps those keys as well as the new
|
||||
one — which is what stops a rotation making the vault's own history unreadable. It also means somebody
|
||||
who left with a copy of the old key could still open old ciphertext they later got hold of. Re-sealing
|
||||
stored items under the new key is the remaining half; see [ADR 0010](docs/adr/0010-vault-key-rotation.md).
|
||||
- **A rotation moves the vault's contents too, and says so when it could not.** The key changes first, in
|
||||
one server transaction; then every item already stored is re-sealed under it, so the key somebody left
|
||||
with opens nothing that is still here. Existing items keep working throughout — everybody still in the
|
||||
team holds the older keys as well as the new one, which is what stops a half-finished rotation making a
|
||||
vault unreadable, and what makes the pass safe to interrupt and run again. An item somebody else was
|
||||
editing at that moment is left for the next pass, and the message tells you which of the two you got.
|
||||
See [ADR 0010](docs/adr/0010-vault-key-rotation.md).
|
||||
- **Adding a member shares the vaults you can open, including their history.** Membership is still one act
|
||||
and a key is still another — nothing changed about that — but the client now performs the second one for
|
||||
you, wrapping every generation it holds so the new member can read the vault back to its first item. A
|
||||
@@ -595,9 +597,10 @@ keychain plus a terminal — and the spike that gates all of it.
|
||||
adding machine can open to them — every generation of each, so they can read the vault's history and not
|
||||
only what happens next. Removing somebody revokes their grants, advances each vault it can open to a
|
||||
fresh key generation in one server transaction, and wraps that key to the members who remain. What a
|
||||
rotation buys is exact: everything written from then on is unreadable to the person who left. Items
|
||||
already stored keep the generation they were sealed under and are not re-encrypted — that half is still
|
||||
outstanding, and it is safe to add later precisely because a vault at mixed generations stays readable.
|
||||
rotation buys is exact: everything written from then on is unreadable to the person who left. The items
|
||||
already stored are then re-sealed under the new key as well, item by item and resumably — which is safe
|
||||
to do incrementally precisely because a vault at mixed generations stays readable. A change queued before
|
||||
the rotation is re-sealed as it is pushed, so nothing reaches the server under a superseded key at all.
|
||||
See [ADR 0010](docs/adr/0010-vault-key-rotation.md).
|
||||
|
||||
**Ownership transfer is here, and it is one write rather than two.** The member you name becomes owner
|
||||
@@ -607,8 +610,7 @@ keychain plus a terminal — and the spike that gates all of it.
|
||||
key grants, because removing the outgoing owner would revoke them and rotate every team vault, which is a
|
||||
much larger act than the one being asked for.
|
||||
- **M4 — hardening and ops**, packaging, self-hosting guide.
|
||||
- **M5 — multi-provider OIDC**, identity key rotation, re-sealing a rotated vault's stored items,
|
||||
per-item content keys.
|
||||
- **M5 — multi-provider OIDC**, identity key rotation, per-item content keys.
|
||||
|
||||
## Licence
|
||||
|
||||
|
||||
Reference in New Issue
Block a user