fix(acl): let the role an agent runs as reach the room oracle - #1275
Merged
Conversation
The room presentation oracle exists for one consumer: an agent holding strictly less than its human, asking its principal's VTA to mint a scoped presentation and to open what it cannot decrypt. `application` is the role such an agent runs as - `vta-agent-memory` grants exactly it - and it held neither `RoomPresent` nor `RoomOpen`, so the one consumer the oracle was built for could not call it. The gates are role-derived (`role_has_capability` reads the role and nothing else), so there was no way to grant the capability to a particular entry either. The workaround an operator reaches for is worse than the grant: running the agent as `initiator`, which carries `KeyMint` and `DeviceAdmin` besides. Neither capability widens what this role can do. `RoomPresent` mints a leaf attenuated from the principal's own room authority - one action, one room, four hours, bound to the caller - and the role already holds `Sign`, which is the principal's key over arbitrary bytes and therefore strictly more. `RoomOpen` decrypts a room record under a group key the VTA already holds, and the same role can already read the credential vault. `Reader` and `Monitor` get neither, and a test pins that: minting a credential on a principal's behalf is not a read. Also corrects two doc comments that described enforcement that does not exist. `AclEntry::capabilities` said the auth layer falls back to the role-derived set when it is empty, which reads as "and uses this set when it is not" - but no gate consults the field, the authenticated claims carry no capability set, and nothing over the wire can set it. It is read in exactly one place, to describe a registered device's authority in a binding listing. A reader who believed otherwise would think an entry was least-privileged when it holds everything its role does, so the field and `role_has_capability` now say what is true. Making it real means carrying the set in the claims and giving the ACL surface a way to set it - a separate change with its own design question about whether an entry's set may widen beyond its role or only narrow within it. Signed-off-by: Glenn Gore <glenn.g@affinidi.com>
stormer78
added a commit
that referenced
this pull request
Sep 7, 2026
Two of the four findings the guide surfaced are closed by #1274 (registration is authorized: the signer must be the owner it names) and #1275 (the role an agent runs as carries the room capabilities). The guide described both as live gaps, so it goes stale the moment either merges. The room-creation caveat becomes what creation actually checks, and what it still does not: the request's own proof establishes the owner, and nothing yet governs which parties may create a room on a given host. The agent-capability catch becomes how to grant the oracle - by role, since the per-entry capability list is descriptive rather than enforced, which is worth saying where an operator would otherwise reach for it. Both the markdown and the HTML page carry the same correction, plus a refusal-table row for the registration error an operator will now meet. Signed-off-by: Glenn Gore <glenn.g@affinidi.com>
This was referenced Sep 7, 2026
Merged
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Second of two found while writing the data-rooms operator guide (#1273); the other is #1274.
The room presentation oracle exists for exactly one consumer — an agent holding strictly less than its human, asking its principal's VTA to mint a scoped presentation (
rooms/keys/present) and to open what it cannot decrypt (rooms/keys/open).applicationis the role such an agent runs as;vta-agent-memorygrants exactly it. It held neitherRoomPresentnorRoomOpen, so the one consumer the oracle was built for could not call it.There was no way around it per-entry either: the gates call
role_has_capability, which reads the role and nothing else. The workaround an operator reaches for is worse than the grant — running the agent asinitiator, which carriesKeyMintandDeviceAdminbesides.Why this is not a widening
RoomPresentSign: the principal's key over arbitrary bytes, and therefore strictly moreRoomOpenReaderandMonitorget neither, and a test pins it: minting a credential on a principal's behalf is not a read.Two doc comments that described enforcement that does not exist
AclEntry::capabilitiessaid the auth layer falls back to the role-derived set when it is empty — which reads as "and uses this set when it is not". It does not. No gate consults the field, the authenticated claims carry no capability set, and nothing over the wire can set it; it is read in exactly one place, to describe a registered device's authority in a binding listing. A reader who believed otherwise would think an entry was least-privileged when it holds everything its role does. The field androle_has_capabilitynow say what is true.Making it real — carrying the set in the claims, and giving the ACL surface a way to set it — is a separate change with its own design question: may an entry's capability set widen beyond its role, or only narrow within it? Worth doing, not worth smuggling in here.