feat: reject update calls for delegations with queries-only permissions#10449
Draft
aterga wants to merge 4 commits into
Draft
feat: reject update calls for delegations with queries-only permissions#10449aterga wants to merge 4 commits into
aterga wants to merge 4 commits into
Conversation
Adds an optional permissions field to request delegations, mirroring the existing targets field. When a delegation in a request's delegation chain carries permissions = "queries", the ingress validator rejects update calls authenticated through that chain with the new UpdateCallNotPermittedByDelegation error, while query and read_state requests remain permitted. Delegations with permissions = "updates" or without the field are unrestricted, and any other value is rejected as unsupported (fail-closed, since no pre-existing delegations carry the field). The field is covered by the delegation signature (it is part of the representation-independent hash), so a client cannot strip it to lift the restriction: removing the field changes the delegation hash and invalidates the signature. For the same reason, replicas that predate this change fail closed on restricted delegations. This enables issuers like Internet Identity to sign read-only delegations that can read on the user's behalf (perform query calls) but cannot change state (perform update calls), enforced by the protocol regardless of the client's cooperation. It is the strictly-enforced counterpart to the sender_info-based attribute approach (#10447). https://claude.ai/code/session_01BQNgPJKgxWohrJ6owwJBzz
Contributor
There was a problem hiding this comment.
Pull request overview
This PR extends IC request delegations with an optional permissions field to support queries-only delegations that are enforced during ingress validation (rejecting /call update requests), while remaining backward compatible for existing delegations and failing closed on unsupported permission values.
Changes:
- Add
Delegation.permissionstoic-types, include it in the delegation’s signed bytes, and expose constructors/accessors. - Thread delegation permission restrictions through
ic-validatordelegation-chain validation and reject update calls when any delegation in the chain haspermissions = "queries". - Add test utilities and end-to-end tests covering acceptance/rejection behavior across request types and invalid permission values.
Reviewed changes
Copilot reviewed 9 out of 9 changed files in this pull request and generated 4 comments.
Show a summary per file
| File | Description |
|---|---|
| rs/validator/tests/ingress_validation.rs | Adds a golden signed-bytes test covering the new permissions field hashing. |
| rs/validator/src/ingress_validation/tests.rs | Updates unit tests to match the new validate_signature return type (restrictions vs targets). |
| rs/validator/src/ingress_validation.rs | Implements delegation permissions parsing, restriction accumulation, and update-call rejection. |
| rs/validator/ingress_message/tests/validate_request.rs | Adds e2e tests verifying update rejection and query/read_state acceptance for queries-only delegations. |
| rs/validator/ingress_message/src/lib.rs | Exposes the new validation/authentication error variants in the standalone validator API. |
| rs/validator/ingress_message/src/internal/mod.rs | Maps new ic_validator error variants into ic_validator_ingress_message equivalents. |
| rs/validator/http_request_test_utils/src/lib.rs | Extends delegation-chain builder utilities to create delegations with permissions. |
| rs/types/types/src/messages/http/tests.rs | Updates CBOR serialization tests to include the new permissions field (null or string). |
| rs/types/types/src/messages/http.rs | Adds Delegation.permissions, constructor/accessor, and includes it in signed-bytes hashing when present. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Short-circuit the queries-only delegation rejection before sender_info canister signature verification on the call path, make the error message byte-identical across the validator crates, and strengthen test assertions to also check queries_only. https://claude.ai/code/session_01BQNgPJKgxWohrJ6owwJBzz
This was referenced Jun 12, 2026
The supported values of a delegation's permissions field are now: - "queries": the sender can only execute queries; requests to /call endpoints (updates and replicated queries) are rejected. - "all": the sender can execute all functions (queries, replicated queries, and updates); same as an absent permissions field. Any other value, including the previous "updates", is rejected as unsupported. https://claude.ai/code/session_01BQNgPJKgxWohrJ6owwJBzz
eichhorl
reviewed
Jun 12, 2026
| use ic_validator_ingress_message::AuthenticationError::UnsupportedDelegationPermissions; | ||
|
|
||
| #[test] | ||
| fn should_reject_update_call_when_delegation_restricts_to_queries() { |
Contributor
There was a problem hiding this comment.
Maybe we could similarly extend system tests around ingress validation
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.
Queries-only permissions on delegations
This is now the consolidated read-only-sessions POC (the previously stacked alternatives #10447/#10452, which carried the restriction in certified
sender_infoattributes, were closed in favor of this more lightweight approach).What it does
Request delegations gain an optional
permissionsfield, mirroring the existingtargetsfield. Supported values:permissionsabsent or"all"→ the sender can execute all functions — queries, replicated queries, and updates (today's semantics; byte-for-byte identical hash for existing delegations without the field)."queries"→ the sender can only execute queries: requests to/callendpoints (updates and replicated queries) authenticated through such a chain are rejected during ingress validation with the newRequestValidationError::UpdateCallNotPermittedByDelegation; query and read_state requests remain permitted.AuthenticationError::UnsupportedDelegationPermissions(fail-closed: no pre-existing delegations carry the field, so unknown values need no legacy leniency).A restriction anywhere in the chain applies to the whole chain (like
targets, restrictions only accumulate).Why this can't be bypassed, and why it's backward compatible
HttpRequestVerifier<SignedIngressContent>— the same verifier used by the ingress manager for block validation, so a malicious boundary node or replica cannot smuggle a restricted update call into a block.Rationale
Enables issuers like Internet Identity to sign read-only delegations: sessions that can read on the user's behalf (query calls) but cannot change state (update calls), enforced by the protocol regardless of the client's cooperation. II sets this field from a "Read-only mode" checkbox in the authorize flow (II-side changes prepared separately).
Historical precedent: the interface spec already extended the delegation map with an optional restriction field once — the
sendersfield (added Dec 2021, never implemented by the replica, removed in interface-spec#246). This PR follows the same formal pattern (verify_delegationsaccumulation), but implementation-first.Changes
ic-types:Delegation.permissions(optional),new_with_permissions, accessor, inclusion in the signed bytes (hash_of_mapkey"permissions").ic-validator:DelegationRestrictions(targets + queries-only) threaded through delegation-chain validation; update-call rejection; fail-closed handling of unsupported values; new error variants mirrored inic-validator-ingress-message.ic-validator-http-request-test-utils:delegate_to_with_permissionschain-builder support."all"-permitted / unsupported-values-rejected (incl."updates"and the empty string) / restriction-in-the-middle-of-a-chain.Caveats / follow-ups
/callpath (replicated queries) are rejected for"queries"delegations by design — the"all"vocabulary makes this explicit.verify_delegationsformal conditions) prepared for dfinity/portal; II-side issuance and agent (@icp-sdk/core) round-tripping of the field prepared/pending.rs/tests/crypto/ingress_verification_test.rs(mirroring the delegation tests there) is a natural follow-up; the validator-level e2e tests in this PR cover the enforcement semantics.cargo test -p ic-types -p ic-validator -p ic-validator-ingress-messagepasses and clippy is clean; downstream consumers (ic-http-endpoints-public,ic-ingress-manager,ic-canister-client,ic-state-machine-tests) compile. CI is fully green.https://claude.ai/code/session_01BQNgPJKgxWohrJ6owwJBzz