Skip to content

feat: reject update calls for delegations with queries-only permissions#10449

Draft
aterga wants to merge 3 commits into
masterfrom
claude/fervent-mccarthy-tmfzpw-delegation-permissions
Draft

feat: reject update calls for delegations with queries-only permissions#10449
aterga wants to merge 3 commits into
masterfrom
claude/fervent-mccarthy-tmfzpw-delegation-permissions

Conversation

@aterga

@aterga aterga commented Jun 11, 2026

Copy link
Copy Markdown
Contributor

Queries-only permissions on delegations

This is the strictly-enforced counterpart to the sender_info-attribute approach in #10447: instead of (or in addition to) reading permissions out of certified session attributes, the restriction is carried by the delegation itself, so the protocol enforces it regardless of the client's cooperation.

What it does

Request delegations gain an optional permissions field, mirroring the existing targets field:

  • permissions absent or "updates" → unrestricted (today's semantics, byte-for-byte identical hash for existing delegations).
  • permissions = "queries" → the sender authenticated through this chain may make query and read_state requests, but update calls are rejected during ingress validation with the new RequestValidationError::UpdateCallNotPermittedByDelegation.
  • Any other value → the delegation is rejected as AuthenticationError::UnsupportedDelegationPermissions (fail-closed: no pre-existing delegations carry the field, so unknown values need no legacy leniency — unlike the sender_info variant, which must fail open on unparseable legacy blobs).

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

  • The field is part of the delegation's representation-independent hash, i.e. covered by the delegation signature. A dapp holding the session key cannot strip the field — doing so changes the hash and invalidates the signature.
  • Old replicas fail closed: they parse the delegation without the unknown field (serde ignores it), recompute the hash without it, and signature verification fails. A restricted delegation is unusable on un-upgraded replicas rather than silently unrestricted. Rollout: replicas first, issuers (Internet Identity) after.
  • Delegations without the field are completely unaffected, so existing agents, dapps, and issuers need no changes. Agents only need to round-trip the new field once they want to carry restricted delegations; an agent that drops it fails closed.
  • The enforcement point is 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). In the companion design, II sets this field from the same "Read-only mode" checkbox that drives the implicit:permissions certified attribute of #10447 — the delegation field is the enforcement bit, the attribute remains the canister-visible metadata (ic0.msg_caller_info_data_*).

Changes

  • ic-types: Delegation.permissions (optional), new_with_permissions, accessor, inclusion in the signed bytes (hash_of_map key "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 in ic-validator-ingress-message.
  • ic-validator-http-request-test-utils: delegate_to_with_permissions chain-builder support.
  • Tests: signed-bytes golden test for the new field; CBOR encoding tests; e2e tests covering update-rejected / query-and-read_state-permitted / "updates"-permitted / unknown-value-rejected / restriction-in-the-middle-of-a-chain.

Caveats / follow-ups

  • Same conservative behavior as feat: reject update calls when sender_info restricts permissions to queries #10447: the whole /call path is restricted, including replicated queries (the validator cannot know method kinds).
  • Interface-spec update (delegation map field + verify_delegations formal conditions) to follow in dfinity/portal, and II-side issuance + agent (@dfinity/agent) round-tripping of the field.
  • A system test in 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-message passes and clippy is clean; downstream consumers (ic-http-endpoints-public, ic-ingress-manager, ic-canister-client, ic-state-machine-tests) compile. Bazel was not available in the development container, so CI should confirm the Bazel build.

https://claude.ai/code/session_01BQNgPJKgxWohrJ6owwJBzz


Generated by Claude Code

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

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.permissions to ic-types, include it in the delegation’s signed bytes, and expose constructors/accessors.
  • Thread delegation permission restrictions through ic-validator delegation-chain validation and reject update calls when any delegation in the chain has permissions = "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.

Comment thread rs/validator/src/ingress_validation/tests.rs Outdated
Comment thread rs/validator/src/ingress_validation/tests.rs Outdated
Comment thread rs/validator/ingress_message/src/lib.rs
Comment thread rs/validator/src/ingress_validation.rs Outdated
claude added 2 commits June 11, 2026 21:05
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
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants