registrystack / registrystack/registry-stack
Evidence: audit selector values sent for a grant-bound or context-bound subject
- Dominant language
- Rust
- Stars
- 2
- Forks
- 0
- Avg merge
- 2h 57m
- Merged PRs (30d)
- 128
Description
## Use Case
An agent acting under a task grant, or a caller whose subject comes from its authenticated session, does not choose the subject: Evidence derives it from the grant or context claims. When such a caller sends `selector.values` anyway, `resolve_grant_subjects` in `crates/registry-evidence/src/selector.rs` returns `AuthorizationError::Selector`. The runtime charges the selector failure budget, answers `400 invalid-selector`, and writes nothing to the audit chain.
The contracts keep invalid-selector failures operational-only (`pre_authentication_and_validation_failure` in `products/evidence/contracts/audit-event.schema.yaml`, `products/evidence/OPERATOR-CONTRACT.md`, invariant 25 in `products/evidence/CONCEPT.md`). The stated reason is that the core has no authenticated requester plus authorization decision to build the refusal event from. That reason does not hold for this case: the caller is authenticated and an entitlement has already matched (`match_entitlement` runs before `resolve_selectors`), so the requester, actor, client and grant pseudonyms are all available. Today an auditor cannot see an agent trying to name a subject for itself; only the rate limiter notices.
The check fires on any supplied value, including one equal to the granted subject. The event therefore records a contract violation by an authorized caller, not proof of an attempt outside the grant.
## Proposed Behavior
For a subject whose value origin is `authenticated-context` or `authenticated-grant`, when the caller supplied selector values:
- Durably append one minimal denial event before the response, with the fields the authorization refusal event carries today: operation and event identifiers, assurance profile, bundle revision, requester, actor, client and grant pseudonyms, actor kind, timestamp and duration.
- Keep the `400 invalid-selector` response and the selector failure budget charge.
- If the audit sink cannot accept the event, answer `503`, as an authorization refusal does.
- Apply this on both evaluation paths in `crates/registry-evidence/src/runtime.rs` (single evaluation and request batch), which share the same error handling today.
Other invalid-selector failures, such as malformed values or profile validation on a `request`-origin subject, stay operational-only.
## Boundaries
- Never record the supplied selector values, subject fields, requirement, purpose, or any other material the refusal event omits today.
- The refusal event's `decision` and `safeErrorCategory` are closed to `not-authorized`, while this response is `invalid-selector`. Choose one shape: a closed `invalid-selector` decision and category on `registry.evidence.audit.authorization-refusal/v1` with its own `reason`, or a separate discriminator. Semantic audit readers and the service change together either way.
- Update the contract text with the code: `audit-event.schema.yaml`, `OPERATOR-CONTRACT.md`, `CONCEPT.md` invariant 25, `IMPLEMENTATION.md`, the security invariant matrix, and the traceability entries.
- Tests: a grant-bound and a context-bound request with supplied values each append exactly one event with no selector material and still answer `400`; an audit sink failure answers `503`; a malformed selector on a `request`-origin subject appends nothing.
Open question: `resolve_grant_subjects` also returns `Selector` when the authenticated context lacks the claim a grant-bound subject reads. That caller is authorized too, but the cause is usually a token or profile defect rather than caller behavior. Should it be audited the same way?
Contributor guide
Research direction
Start in crates/registry-evidence/src/selector.rs and trace resolve_grant_subjects through both evaluation paths in crates/registry-evidence/src/runtime.rs, then read the audit contracts and invariant 25. Define the compatible refusal-event shape with the semantic audit readers, and add the listed grant-bound, context-bound, sink-failure, and malformed-selector tests. Done means the correct events are appended without selector material, responses remain 400 or become 503 on sink failure, and all named contract and traceability documents agree.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- rust
- Domain
- api, backend, observability, security
- Issue type
- Feature
- Difficulty
- 5/5
- Estimated time
- Over a week
- Activity status
- Active
- Clarity
- Mostly clear
- Newbie friendliness
- 45/100