Skip to content

ARAP: the inline denial-binding form and the PEP's echo #660

Open

Description

@mcguinness

Problem

The inline denial-binding form is never specified. A self-contained binding_token must carry the denied Subject, Resource, Action, and authorization-relevant Context "inline or as a binding_hash", and the inline form is RECOMMENDED for interoperability because it is compared structurally and needs no agreed byte canonicalization. binding_hash, binding_context_members, and denial_expires_at are named. The inline claims are not. The hashed form is fully specified, so two independent implementations can interoperate today by using it; what is blocked is the portable use of the form the text recommends.

The claim list's stated force does not match the verification procedure. The list is introduced by "SHOULD include the following claims", yet aud inside it is REQUIRED and carries a verifier-side MUST-reject, the verification steps require the expiry, and both key selection and issuer trust depend on iss, which carries no keyword at all. The interoperable JWT contract is stricter than its own introduction says.

The PEP's echo does not compose with the service's requirement. denial.binding_token is REQUIRED when denial.evaluation_id is absent and OPTIONAL otherwise. A PEP holding both may therefore omit the token and remain conformant, while an Independent Access Request Service requires it and rejects the submission. Two conformant implementations fail to interoperate.

Recommendation

Name the inline binding claims, so the recommended form is usable across implementations.

Define a normative JWT subset and keep the general statement that binding_token is opaque and format-flexible:

  • REQUIRED: iss, aud, exp, the binding representation (inline claims or binding_hash), and binding_context_members when Context participates in the binding.
  • RECOMMENDED: iat, jti.
  • OPTIONAL: evaluation_id, and deployment-specific claims.

Make the PEP's echo unconditional: the PEP echoes every denial member the PDP supplied, unchanged. This changes the force of an existing rule, which is why it is a working-group decision rather than an editorial correction.

Related

Once the claims are named, the illustrative tokens need correcting. Every binding_token in the base draft and its companions decodes to correlation data alone, with no aud, whose absence a conformant service must reject, and no binding_context_members. The base draft's token carries an evaluation_id only, which the text says does not satisfy the independent-service requirement it is meant to illustrate. That is an editor task, not a decision for this issue.

Activity

  1. vatsalgupta commented on Oct 2, 2026

    @vatsalgupta
    Contributor

    On naming the inline claims, one option from building the example tokens: a single binding claim whose value is exactly the object that binding_hash hashes today.

    "binding": {
    "subject": {...}, (with properties.act removed)
    "resource": {...},
    "action": {...},
    "context": {...} (only the binding_context_members)
    }

    Bulk uses items in place of resource and action, as the bulk hash does.

    Then inline and hashed are the same object. The inline form is compared structurally, and the hashed form is the hash of the same thing. That means one construction to define instead of two, and no second set of claim names to keep in sync.

    +1 on making the echo unconditional. A PEP should not have to know whether the service it talks to is independent.

  2. mcguinness commented on Oct 13, 2026

    @mcguinness
    CollaboratorAuthor

    Two additions from reviewing #690 against this issue. The approval-side claim set belongs to #659, so only one observation for there: the family's two approval.state examples both sit in deployments where the PDP can resolve approval.id from trusted state, so nothing in the examples demonstrates the independent-PDP topology that member exists for.

    Removing subject.properties.act has no defined result when act is the only member. Hash Construction says <Subject> is "the bound Subject with subject.properties.act removed". Structural Comparison says "every properties member present in the bound values included, except that subject.properties.act is excluded". Neither says whether an emptied properties is kept or omitted, and the two readings produce different digests. From the Callback example's initial evaluation, whose Subject carries act as the sole properties member:

    yNM9XG-K4Bg6xk5KzQd4GU0mYM6gMuDm5AWz0q1YeeM   properties omitted
    F17OXm28QWX5pVv_Fc_dMxJy2Ut54GOfE-69KeOIgCM   properties kept as {}
    

    Omitting is the reading that works, because the exclusion exists so that a PEP which normalizes the actor to client.actor still compares equal. Keeping {} defeats that: the bound Subject normalizes to {type, id, properties: {}} while the submission carries {type, id}, and a conforming Access Request Service rejects with invalid_denial_binding. Proposed rule: after removing subject.properties.act, if properties is then empty, omit it.

    This is not only a hashed-form problem. Structural comparison requires objects to have "the same set of member names", so {properties: {}} and an absent properties are unequal inline as well. The inline form avoids agreeing on byte canonicalization; it still needs an agreed normalization.

    On the single binding claim proposed above: it holds up, and one construction instead of two is the right trade. It inherits the normalization question, so whichever way that is settled should be stated once and apply to both forms.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions