Grant and revoke access¶
Granting access enrolls an identity and issues the attributes that let them satisfy a policy, so they can read its protected files. Revoking removes them — which, because read access is cryptographic, means rotating the key so everything from that point on is closed to them. This guide covers both, and is honest about what revocation can and cannot do for content someone has already read.
Phase 1 — built today
Grant and revoke are part of the Phase 1 permission layer, which is done: tovio access grant
and tovio access revoke are built and enforced today, offline and over TLS. Read access
enforcement is the Team (Tier 1) model, where a Key Authority decides who is in a policy's
recipient set.
flowchart LR
G["grant"] --> GA["enroll + issue claims;<br/>wrap DEK on the next commit"] --> GO([✓ additive, prospective])
R["revoke"] --> RR["rotate DEK now:<br/>re-seal HEAD + re-wrap for remaining"] --> RO([✓ current + future content closed])
More diagrams for the full permission and review flow: Permission & review workflows.
How grant and revoke work, in one breath¶
A protected file is encrypted under a per-file key (a DEK). For each authorized identity, a copy of that key is sealed ("wrapped") to their public key and stored alongside the ciphertext.
- Granting is additive and prospective: it enrolls the recipient and issues their attribute claims now, and the wrapped key copy is added the next time a matching protected path is committed. Nothing already committed is rewritten.
- Revoking is re-encryption and takes effect immediately: it drops the recipient from the roster, generates a fresh key, re-encrypts the committed protected content, wraps the new key only for the identities that remain, and records the result in a new commit. The revoked identity's old key copy still exists, but it only ever unlocked the superseded ciphertext.
This is why the two operations feel asymmetric — and why revocation is forward-looking, not retroactive.
Grant read access to an identity¶
In Team mode your Key Authority is the authorization oracle: it confirms an identity satisfies a policy and enrolls their public key. The grant itself is run by someone who already holds the key.
A grant starts with the recipient's public identity, not with a DID you type. Alice shares her
.tovio/identity.pub — the 64-byte public identity tovio identity show points at — and you pass the
file:
Have Alice's pairing code to hand before you start — at a terminal the command blocks on it:
$ tovio access grant --identity ./alice-identity.pub --attr role=senior --attr team=payments
⚠ `did:key:69923b5c…`'s identity file pairs a signing key with a key-agreement key, and nothing in
the file shows the holder of the signing key ever saw that second key. Certifying the pair
wraps every future content key for this DID to it.
Pairing code (SAS) computed from the file's two public keys:
05029 57808 78165 20398 51039 38602
Re-enter the code the holder is showing (empty to abort):
Enter a code that does not match, or abort, and the grant fails before any claim is issued. Enter the matching one and it completes:
✓ Granted access to did:key:69923b5cc15f6c2cea3df9a1db6d9fe7937ca8deadfd86efde547b8bf79b073e
• role=senior
• team=payments
Takes effect on the next commit of a matching protected path.
--attr is repeatable and is the only way to name attributes — there are no per-attribute flags. Two
things here are easy to miss:
- The grant is prospective. It enrolls Alice and issues her claims now; her wrapped key copy appears on the next commit of a matching protected path. Already-committed objects are not rewritten.
- The pairing code is a real gate, not a courtesy. An
identity.pubis an unauthenticated key pair, so a crafted file can pair a victim's signing key with an attacker's wrap key. Confirming the SAS over a channel you trust is what closes that.
Unattended — in a script, or under --json/--quiet — there is nothing to prompt, so pass --code
<SAS> and the command refuses a mismatch instead. Without it, a first enrollment still proceeds and
merely prints the code as a notice; but re-binding an already-enrolled DID to a different wrap key is
refused outright, because there the substituted key displaces one the repository already trusts and an
after-the-fact notice is exactly what a script ignores.
If you are on the receiving end and lack an attribute, open a request:
$ tovio access request "config/production/**" --attr clearance=secrets
✓ Access request opened: 19f58bd8dcfd1231…
Path: config/production/**
Access: read
Requester: did:key:69923b5c…
Attributes: clearance=secrets
Portable request: .tovio/access/requests/19f58bd8dcfd1231….vex
The Key Authority must still approve it with `tovio access grant`.
The request is a portable file, not a network call — nothing is transmitted. Hand it to whoever holds
the Key Authority (they can list what has arrived with tovio access requests), and they approve it by
running tovio access grant. Confirm the result with tovio access check — it names
exactly which predicates you now satisfy.
Revoke access (key rotation)¶
Revoking is the mirror of granting. Where a grant adds a wrapped key, a revoke rotates it: TOVIO generates a fresh key for the affected path, re-encrypts the content under it, and re-wraps the new key only for the identities that remain. The revoked identity's old key copy still exists, but it only ever unlocked the now-superseded ciphertext. Granting them back later is a fresh grant.
Revoke takes the recipient's DID — the did:key:<hex> string a grant printed, and the one
tovio access list shows:
$ tovio access revoke did:key:69923b5cc15f6c2cea3df9a1db6d9fe7937ca8deadfd86efde547b8bf79b073e
✓ Revoked did:key:69923b5cc15f6c2cea3df9a1db6d9fe7937ca8deadfd86efde547b8bf79b073e
Rotated keys: re-sealed 1 protected file(s) under a fresh DEK in blake3:bc29b8cbc9….
Revoke rotates now, in a new commit
Unlike a grant, tovio access revoke does not wait for your next commit. It drops the recipient from
the roster and immediately re-seals the committed protected content under fresh DEKs, publishing
the result as its own commit — so the revoked key cannot read even the current version. The whole
operation is all-or-nothing: if the re-seal fails, the roster change is rolled back, so nobody is
left half-removed. tovio undo will reverse the rotation commit, which puts the current version back
within the revoked recipient's reach; the command warns you when it does.
What revocation does — and does not — do
It closes the current and all future content. The re-seal covers what is already committed at HEAD, so from the rotation onward the revoked identity cannot read anything encrypted under the new key — not even the version that existed when they were revoked.
It cannot un-read the past. Anyone who legitimately decrypted a file while they had access already holds that plaintext outside TOVIO's control. Rotation protects new content; it cannot reach back and erase a copy someone already made. If a specific secret was exposed, rotate the secret itself (change the password, reissue the credential), not just the policy.
There is no instantaneous, retroactive revocation in this model — that is an accepted, documented property of envelope encryption. See security for the full residual-risk list.
Past vs future content, at a glance¶
| Content committed before revocation | Content committed after revocation | |
|---|---|---|
| Revoked identity can read it? | Only the plaintext they already decrypted while authorized | No — encrypted under the new key |
| Stored ciphertext re-encrypted? | Yes — the protected content at HEAD is re-sealed under a fresh key in the revoke's own commit | New objects use the new key from the start |
| The real fix for a leaked secret | Rotate the secret (credential) itself, then revoke | Revocation alone is sufficient going forward |
Historical objects deeper than HEAD keep their old wraps — content addressing means they cannot be rewritten — so a revoked identity that already cloned that history can still open those older versions with the key copy they hold. That is the accepted boundary of envelope encryption, not a bug.
A note on write access¶
Everything above is about reading. Write access to a write-policy path is enforced differently: when
you push, the relay checks a proof that you hold satisfying attributes, and rejects the push with
TVO-PERM-002 if you do not. There is no ciphertext to hide behind for writes, so the check happens at
sync time rather than through encryption. Granting or revoking the attributes in a write policy is the
same tovio access flow; the enforcement just lands on push.
Verify the result¶
After any grant or revoke, confirm it. Both tovio access check and tovio policy test evaluate a
path's policy offline and print the same per-predicate verdict; pass --identity <file> to either one to
ask on somebody else's behalf, using the identity.pub they shared:
$ tovio access check config/production/api-keys.env --identity ./alice-identity.pub
config/production/api-keys.env (read policy: role=senior & clearance=secrets)
subject: did:key:69923b5c…
[✓] role=senior
[✓] clearance=secrets
verdict: AUTHORIZED ✓
Omit --identity and it answers for you — covered in detail on check access. Grants
and revokes are recorded in the audit log as PolicyChange entries carrying the affected
DID.
Next steps¶
- Manage your keys — the keys these grants wrap content to.
- Read the audit log — see the record of who was granted or revoked, and when.
Last reviewed September 9, 2026
Suggest an improvement to this page Not for security reports — see disclosure