Internet-Draft AADP Bound Permits September 2026
Saha Expires 2 April 2027 [Page]
Workgroup:
Network Working Group
Internet-Draft:
draft-saha-aadp-bound-permit-00
Published:
Intended Status:
Standards Track
Expires:
Author:
S. Saha
Independent

Action-Bound Permits for the Agent Action Decision Protocol (AADP): Carrying a Per-Action Decision Across a Trust Boundary

Abstract

The Agent Action Decision Protocol (AADP) decides, per action, whether an agent's concrete request may proceed now, and assumes that the decision is consumed by an enforcement point on the same secured channel that issued it. AADP identifies, as a planned extension, a permit that must be honoured across a trust boundary, and defines the "present_bound" obligation as its hook. This document specifies that extension. An action-bound permit is a permit signed by the decision point, bound to one recipient, one presenter key, one HTTP request and one decided action instance, and short-lived. It composes with mandate formats such as the Agent Authorization Envelope, which carry what a recipient can evaluate for itself; the bound permit carries a decision the recipient cannot recompute, because it depends on state held by the issuer: cumulative budgets, live reservations, approval lifecycle and escalation. The document adds scoped trust in issuers, a declared currentness mode, a mandate reference, a verification order with registered refusal reasons, and a confirmation the recipient signs. It builds on existing specifications for every mechanism it can, and defines no new signature format, token format or policy language.

Status of This Memo

This Internet-Draft is submitted in full conformance with the provisions of BCP 78 and BCP 79.

Internet-Drafts are working documents of the Internet Engineering Task Force (IETF). Note that other groups may also distribute working documents as Internet-Drafts. The list of current Internet-Drafts is at https://datatracker.ietf.org/drafts/current/.

Internet-Drafts are draft documents valid for a maximum of six months and may be updated, replaced, or obsoleted by other documents at any time. It is inappropriate to use Internet-Drafts as reference material or to cite them other than as "work in progress."

This Internet-Draft will expire on 2 April 2027.

▲

Table of Contents

1. Introduction

1.1. What Already Exists, and What Does Not

A consequential request from an agent that crosses into another organization's system raises three questions, and existing work answers two of them.

Table 1: Three Questions at a Trust Boundary
Question Answered by
Who is acting, and what may it reach? Workload identity and access tokens (for example SPIFFE, mutual TLS, OAuth 2.0)
What has the agent been mandated to do, within what constraints, until when? Mandate formats such as [I-D.kroehl-agentic-trust-aae], which the relying party evaluates itself
Was this exact request decided, under the sender's current authorization state, and is what arrived the request that was decided? Inside one domain, [AADP]. Across a boundary, nothing.

The third question is the gap. A mandate evaluation of the kind AAE specifies is stateless by design: AAE lists state carried across presentations and cumulative budgets as future work. An AADP evaluation is stateful by design: a verdict depends on cumulative spend, reservations, approvals that expire, prior executions and a kill switch. A recipient in another domain holds none of that state and cannot recompute the decision. It can only verify that a decision was made, by whom, about what, and that the request it received is the one decided.

That is all a bound permit does. It is an envelope for one decision, not a system for cross-domain authorization, and Section 1.3 says what it leaves to others.

1.2. The Composition Rule

A recipient that supports this profile acts on a request only when all three of the following hold:

  1. The decision verifies: a bound permit from an issuer the recipient trusts for this class of action (Section 6) verifies, and binds this request (Section 3 and Section 4).
  2. The mandate holds, where one is referenced: if the permit carries a mandate reference (Section 5), the recipient evaluates that mandate against the same request, and its verdict permits the action.
  3. The recipient's own policy permits.

This is a conjunction. Each layer can refuse; none can overrule another. The issuer's decision never widens what the mandate allows, and the mandate never substitutes for a decision the issuer did not make.

1.3. Not in Scope

  • Establishing trust between organizations: discovery, vetting, onboarding and federation. This document assumes a recipient has a configured table of issuers (Section 6) and says nothing about how it was built; [OIDFED] and [SPIFFE] address this.
  • Mandates and their delegation. This document references a mandate; it does not define one.
  • A policy language, for the same reason [AADP] declines to define one.
  • Exactly-once external effect. As in [AADP] Section 7, a protocol cannot guarantee it; the recipient's idempotency store is what collapses retries (Section 12).
  • Multi-hop chains of per-action decisions. This version refuses them (Section 11).
  • Prompt injection. A bound permit faithfully carries a decision that policy should not have made. Its claim is narrower: a decided request cannot be altered, redirected, reused or presented by another party without detection.

1.4. Requirements Language and Terminology

The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "NOT RECOMMENDED", "MAY", and "OPTIONAL" in this document are to be interpreted as described in BCP 14 [RFC2119] [RFC8174] when, and only when, they appear in all capitals, as shown here.

Issuer:
the AADP Policy Decision Point (PDP), or a signing service acting for it, that signs the bound permit.
Presenter:
the AADP Policy Enforcement Point (PEP) that sends the request and holds the key named in the permit.
Recipient:
the party that receives the request and performs the effect. Its enforcement point is the Recipient Enforcement Point (REP).
Action object (A):
the JSON object, derived from the request by the rules of its action type, that the decision was taken about (Section 4.3).
Bound permit:
the action-bound permit of [AADP] Section 1.2, in the form defined in Section 3.

2. Roles and Flow

 Agent --proposal--> PEP --decide--> PDP (issuer)
                      |  <--permit + bound permit--+
                      |
                      |  request + Content-Digest + Signature
                      |  (key in cnf) + bound permit
                      |  + Idempotency-Key
                      v
          +--------- trust boundary ----------+
                      v
                     REP -- verify permit, binding, scope,
                      |     currentness
                      |  -- evaluate referenced mandate (if any)
                      |  -- apply local policy
                      |  -- consume (iss, jti) atomically
                      v
                  Recipient system -- effect
                      |
                      v
          signed recipient confirmation --> PEP --report--> PDP
Figure 1: Bound Permit Flow

The PEP never signs the decision and the PDP never signs the request. Each signature says one thing: the issuer's says this was decided; the presenter's says this is the request, and I am the party the decision named. A compromised PEP can still substitute a request, but not silently: the substituted request will not match the decided action, and the mismatch lands in the recipient's signed record as evidence (Section 15.3).

3. The Bound Permit

3.1. Envelope

A bound permit is a JSON Web Token [RFC7519] in JWS compact serialization [RFC7515], explicitly typed as [RFC8725] recommends:

{ "alg": "EdDSA", "kid": "<issuer key id>",
  "typ": "aadp-permit+jwt" }

EdDSA over Ed25519 is RECOMMENDED. A recipient MUST reject a bound permit whose "typ" is not "aadp-permit+jwt", and MUST reject any header parameter listed in "crit" that it does not implement.

3.2. Claims

Table 2: Bound Permit Claims
Claim Presence Meaning
iss REQUIRED The issuer. Looked up in the recipient's issuer table (Section 6).
sub REQUIRED The presenter, as an identifier the recipient can associate with the key in "cnf" (for example, a SPIFFE ID). Informational: the binding is "cnf", not "sub".
aud REQUIRED The recipient, as a single value. A permit with more than one audience MUST be rejected. It equals the value of the permit's "present_bound" obligation.
jti REQUIRED An identifier for this bound permit, unique per issuer, minted by the issuer (see below). Also the idempotency key (Section 12).
iat, nbf, exp REQUIRED Issue time, not-before and expiry (Section 3.3).
cnf REQUIRED across a trust boundary The presenter's key [RFC7800], as "jkt", a JWK thumbprint [RFC7638] as used by [RFC9449] (Section 4.2).
authorization_details REQUIRED What was decided, as typed entries [RFC9396] (Section 3.4).
action_digest REQUIRED The digest of the action object A (Section 4.3).
verdict REQUIRED Always "permit". Present so that the object states its own meaning; no other AADP verdict produces a bound permit.
tier OPTIONAL The nominal and effective autonomy tiers from the AADP decide response.
policy_version RECOMMENDED The content digest of the policy in force at the decision, as recorded in the issuer's AADP evidence.
mandate OPTIONAL A mandate reference (Section 5).
currentness REQUIRED "time-bounded" or "status-checked" (Section 7).
status REQUIRED if "currentness" is "status-checked" Where the recipient checks status (Section 7.2).
evidence OPTIONAL References into the issuer's AADP evidence record, to the presenter's preceding evidence record (for example a stage receipt [STAGE-RECEIPTS]), and to earlier hops ("upstream", Section 11.2). Carried and echoed; never required for verification and never a source of authority.

The "jti" is not the AADP "permit_id". [AADP] Section 7 forbids presenting "permit_id" to a target resource as a credential or as a key carrying meaning there, because "permit_id" is the identity of AADP's decision and report lifecycle inside the governed domain. A bound permit is, by design, presented outside that domain, so it carries an identity of its own. The issuer MUST mint "jti" independently of "permit_id", MUST NOT make one derivable from the other, and MUST record the mapping between them in its AADP evidence entry for the decision.

This profile is stricter than [AADP] Section 7, which recommends that a PEP derive a target's idempotency key deterministically from the permit, for example from "permit_id". Where the target is a bound-permit recipient, "jti" is that key (Section 12), and the independent minting required above replaces the derivation Section 7 recommends. Nothing else in [AADP] Section 7 changes.

A claim is REQUIRED only if the recipient's verification consumes it. "policy_version", "mandate" and "evidence" are carried so that both sides' records can name them, not because the recipient can dereference them. Making a recipient depend on fields it cannot use would condition adoption on infrastructure it does not have.

3.3. Time

  • "exp" MUST NOT be later than the deadline of the permit's "execute_within" obligation, where the AADP decide response carries one. A bound permit never outlives the permit it carries.
  • Absent "execute_within", "exp" minus "iat" MUST NOT exceed 120 seconds. Registrations for particular action types MAY set a lower ceiling; none may set a higher one.
  • Recipients MUST apply a declared clock-skew allowance not exceeding 60 seconds, and MUST record the allowance applied.

3.4. authorization_details

Each entry has a "type" naming a registered action type (Section 18) and the fields that type defines. The type's registration states, for each field, its meaning, its units and its comparison rule against an issuer's scope (Section 6). For example:

"authorization_details": [{
  "type": "payments.transfer/1",
  "payee": "acme-gmbh",
  "amount": { "currency": "EUR", "max": "40.00" },
  "reference": "invoice-8841"
}]

A recipient MUST reject a permit carrying an "authorization_details" entry whose "type" it does not implement. Unknown types fail closed, as unknown obligations do in [AADP] Section 6.

3.5. Example

{
  "iss": "https://pdp.payer.example",
  "sub": "spiffe://payer.example/pep/accounts-payable",
  "aud": "https://payments.example.net",
  "jti": "bp-4f0c2a91d7e3",
  "iat": 1790152800, "nbf": 1790152800, "exp": 1790152860,
  "cnf": { "jkt": "0ZcOCORZNYy-DWpqq30jZyJGHTN0d2HglBV3uiguA4I" },
  "authorization_details": [{
    "type": "payments.transfer/1",
    "payee": "acme-gmbh",
    "amount": { "currency": "EUR", "max": "40.00" },
    "reference": "invoice-8841"
  }],
  "action_digest": "sha-256=:9Bh7a3W0...=:",
  "verdict": "permit",
  "tier": { "nominal": 1, "effective": 1 },
  "policy_version": "sha-256=:3f775k...=:",
  "mandate": { "type": "aae", "id": "urn:uuid:7c1e...",
               "digest": "sha256:ab41..." },
  "currentness": "time-bounded",
  "evidence": { "evidence_id": "aud-88213" }
}

4. Binding the Permit to the Request

A bound permit is useless if the request it accompanies can be altered. Three bindings close that: the body to its bytes, the request to the presenter's key, and the bytes to the decided action.

4.1. Body: Content-Digest

The presenter MUST send a Content-Digest field [RFC9530] computed over the exact content bytes sent, using sha-256 or a stronger registered algorithm. The recipient MUST recompute it over the bytes it received, before any parsing, decompression it did not negotiate, or re-serialization. The parsed object is not necessarily the object that was sent; [STAGE-RECEIPTS] applies the same boundary rule to external calls.

4.2. Request and Presenter: HTTP Message Signatures

The presenter MUST sign the request with HTTP Message Signatures [RFC9421], using the private key whose thumbprint is the permit's "cnf.jkt". The signature MUST cover, at minimum, the components "@method", "@authority", "@path", "@query", "content-digest", "idempotency-key" and "aadp-permit", where "aadp-permit" is the field carrying the bound permit (Section 18). Covering the permit field binds the signature to this permit; covering "content-digest" binds it to the body; the derived components bind it to the target. Action types MAY require further components in their registration. Volatile, hop-by-hop or intermediary-rewritten fields MUST NOT be covered.

The recipient MUST verify the signature under the key identified by "cnf.jkt", and MUST reject a request whose signature verifies under any other key. Without this binding a bound permit is a bearer token: whoever holds it can present it until it expires. Bearer permits MAY be used inside one governed domain, where the channel assumptions of [AADP] Section 1.2 hold, and MUST be declared as bearer in that deployment's documentation. They MUST NOT be accepted across a trust boundary.

4.3. Semantics: the Action Object and action_digest

Wire bytes and meaning are different things, and the permit binds both.

  • Each registered action type defines how the action object A is derived from a request: which fields of the parsed body, path or query form A, with what types. A MUST be a JSON object.
  • action_digest = "sha-256=:" || BASE64( SHA-256( "aadp:action:v1" || 0x00 || JCS(A) ) ) || ":", where JCS is the JSON Canonicalization Scheme [RFC8785]. The domain tag separates this digest from every other digest over the same object.
  • The recipient derives A from the request it received, by the type's rules, and MUST reject the request if the recomputed digest differs from the permit's "action_digest".

This digest is an instance binding. [I-D.kroehl-agentic-trust-aae] Section 2.2.2 binds a grant to an action type: the values that distinguish one instance from another (amounts, recipients, sequence numbers) are kept out of its action object and travel beside it, bounded by the grant's constraints, and a binding that commits to an action instance is outside that specification, which neither defines nor forbids it. The "action_digest" here is such an instance binding: its A carries the concrete values the decision was taken about. A recipient that also evaluates an AAE therefore derives two objects from the one request it received, each bound by its own digest under its own domain tag: the mandate says the kind of action is allowed within per-action bounds; the permit says this action was decided. Neither digest can stand in for the other. An action-type registration SHOULD state both derivations, so that they are taken from the same request fields.

5. The Mandate Reference

5.1. Why a Reference, and Not a Mandate

Two requests can be identical in every respect AADP evaluates -- identity, arguments, budget, approval state -- and differ only in the authority under which they are caused: an account suspension under a fraud-response mandate, and the same suspension under an employee-support mandate. The decision may be right in both cases; what fails without a reference is re-derivability, because a reader of the record cannot say under which mandate the action was authorized. This document does not define a mandate. It defines a reference to one:

"mandate": { "type": "aae", "id": "<credential id>",
             "digest": "<mandate digest>" }
  • "type" names the mandate format. "aae" is the only value defined here; others are registered (Section 18).
  • "id" identifies the mandate object.
  • "digest" is the mandate's own content digest as its format defines it. For "aae", it is the mandate digest of [I-D.kroehl-agentic-trust-aae] Section 2.5.3.

5.2. Rules at the Issuer

  • The mandate reference MUST be established the way [AADP] Section 5.1 requires authorization-relevant provenance to be established: through an authenticated channel or credential, represented as context the policy evaluates. A caller MUST NOT be able to name its own mandate in request parameters.
  • The issuer SHOULD record, in its AADP evidence, the mandate reference and the mandate's status as observed at decision time (for AAE, the result of its revocation check where present). Without that, a later reader can say which mandate was named but not that it was valid when the decision was taken.
  • Where the issuer consumes a mandate-layer verdict, the correspondence of [AADP] Section 8.1 applies: no bound permit is issued while that verdict denies or defers the action.

5.3. At the Recipient

Where "mandate.type" is "aae" and the AAE is presented or retrievable:

  1. The recipient evaluates the AAE under [I-D.kroehl-agentic-trust-aae] Section 5, including subject binding, validity, single use, revocation and delegation, against the transaction it derives from the same request.
  2. The recipient MUST compare the evaluated AAE's mandate digest with the permit's "mandate.digest" and reject on mismatch ("mandate-mismatch").
  3. The AAE verdict governs as follows: PERMIT, continue; DENY, refuse ("mandate-denied"); PENDING, refuse ("mandate-pending"). The request MAY be resubmitted once ratification under that document's Section 6.3 has occurred. A bound permit never converts PENDING into PERMIT.

Where the permit names a mandate the recipient cannot obtain or evaluate, the recipient MUST refuse ("mandate-unavailable") unless its local policy explicitly treats that action type as mandate-optional, and it MUST record which it did.

6. Issuer Scope

A trust store that merely lists issuers lets any trusted issuer authorize anything the recipient's local policy fails to stop. This profile replaces the list with a table of scopes. Each entry states:

issuer:        https://pdp.payer.example
keys:          jwks_uri or pinned keys
action types:  [ payments.transfer/1 ]
limits:        per type, per field, using the type's comparison rule
               e.g. payments.transfer/1: amount.max <= EUR 1,000.00
not_after:     2027-01-01T00:00:00Z
currentness:   minimum mode accepted from this issuer

7. Currentness

7.1. The Problem

A bound permit proves that a decision was taken under a policy version and, where named, a mandate. It does not by itself prove that either was still in force when the request arrived. A policy superseded, or a mandate revoked, between decision and execution is invisible to the recipient. Short lifetimes bound that window in time; they do not close it. Inside one domain the PEP can ask again; across a boundary the recipient cannot.

This profile does not claim to close the window by default. It requires every permit to declare which of two modes it is in, and every recipient record to state which mode applied, so that the limitation is visible in each record instead of hidden in each deployment.

7.2. Modes

time-bounded:
The permit is current until "exp" and no further check is made. Declared limitation: a change of policy version or a revocation of the mandate before "exp" is not detected. A recipient MAY accept "time-bounded" only for action types and issuers whose table entry allows it.
status-checked:
The permit carries "status", and the recipient MUST establish, at the moment of verification, that neither the permit, its "policy_version", nor its "mandate" has been revoked or superseded. Two mechanisms are defined. Status list: "status" identifies an entry in a status list published by the issuer, following [I-D.ietf-oauth-status-list]. Stapled freshness: the presenter includes an issuer-signed freshness statement in the "AADP-Permit-Status" field, of the form { "jti", "policy_version", "mandate_digest", "status": "current", "iat" }, whose "iat" is within the table's freshness window (RECOMMENDED: no more than 30 seconds).

If a "status-checked" permit's status cannot be determined -- endpoint unreachable, response unparseable, statement stale -- the recipient MUST refuse ("status-unavailable"). A recipient MAY apply an explicit, locally configured, audited fail-open policy for action types whose risk classification permits it, and MUST record that it did.

8. Recipient Verification

8.1. Order

The REP performs these steps in order, stopping at the first failure. Cheap structural checks come before cryptography, cryptography before state, and state is consumed last, so that a request refused for any other reason does not consume its permit.

Table 3: Verification Order
# Step Refusal reason
1 Permit present, parses, "typ" is "aadp-permit+jwt", "crit" understood malformed
2 "iss" in the issuer table; signature verifies under a current key for that issuer issuer-unknown, signature-invalid
3 "aud" identifies this recipient, as a single value audience-mismatch
4 nbf <= now <= exp within the declared skew; the rules of Section 3.3 hold expired, not-yet-valid, lifetime-invalid
5 Every "authorization_details" type implemented unknown-authorization-type
6 Every entry within the issuer's scope issuer-out-of-scope
7 Content-Digest recomputed over the received bytes matches content-mismatch
8 An HTTP message signature is present, made with the key "cnf.jkt" names, verifies, and covers the required components request-signature-missing, presenter-key-mismatch, request-signature-invalid, binding-incomplete
9 A derived from the request; its digest equals "action_digest" action-mismatch
10 Currentness per "currentness" (Section 7) stale-policy, mandate-revoked, status-unavailable
11 Referenced mandate evaluated (Section 5.3) mandate-mismatch, mandate-denied, mandate-pending, mandate-unavailable
12 Recipient-local policy permits local-policy
13 (iss, jti) consumed atomically (Section 12) replayed, idempotency-conflict

Only after step 13 does the recipient perform the effect.

8.2. Fail Closed

  • A recipient that requires bound permits for an endpoint MUST NOT fall back to other authorization when a permit is absent or fails. Absence is "malformed", not a reason to accept another credential.
  • If any verification dependency is unavailable -- key discovery, status, mandate retrieval, the consume store -- the recipient MUST refuse, with the reason naming the dependency, and MUST distinguish that refusal from a policy denial in its record, as [AADP] Section 5.1 distinguishes a PDP defect from a policy outcome.

8.3. Verification States in the Record

A recipient's record states, per request, one of: "verified" (every step passed); "refused", with its reason code and the step at which it stopped; or "could-not-check", with the dependency that was unavailable. There is no partial verification: a verification in which a required check was not performed is "could-not-check".

9. Refusal Reasons

Refusals are returned as HTTP status 403 (409 for "idempotency-conflict", 503 for "could-not-check") with a Problem Details body [RFC9457] whose "type" is the registered reason URI (Section 18). Reason codes say which check failed. They MUST NOT disclose the recipient's policy, limits or issuer table: "issuer-out-of-scope" names the entry and field that fell outside, not the bound it exceeded. The initial set is the reason column of Table 3, plus "chained-permit-unsupported" (Section 11).

10. The Recipient Confirmation

Each side of a cross-domain action keeps its own record; without more, a dispute is two self-authored accounts. This profile asks for one object the recipient signs and both sides keep. On completing step 13 and attempting the effect, the recipient returns a recipient confirmation, a JWS signed by the recipient, in the "AADP-Recipient-Confirmation" response field:

{
  "typ": "aadp-confirmation+jwt",
  "iss": "https://payments.example.net",
  "permit": { "iss": "https://pdp.payer.example",
              "jti": "bp-4f0c2a91d7e3",
              "digest": "sha-256=:<digest of the permit's compact
                         serialization>:" },
  "request": { "content_digest": "sha-256=:...:",
               "signature_base_digest": "sha-256=:<digest of the
                                        RFC 9421 signature base>:" },
  "action_digest": "sha-256=:...:",
  "mandate_verdict": { "type": "aae", "core_digest": "sha256:..." },
  "outcome": "effected | refused | effect-unknown",
  "reason": "<reason code if refused>",
  "recipient_action_id": "TX-77120",
  "iat": 1790152803
}

A recipient that refuses at any step SHOULD still return a confirmation with "outcome": "refused" and the reason. A refusal the recipient signs is evidence; a refusal it merely returns is an assertion.

11. More Than One Hop

A bound permit authorizes one request, to one audience, by one presenter. This version of the profile is single-hop:

A capability declared unsupported must be refused by the code, not merely left out of the text. Chained permits, in which an intermediary derives a permit from one it received, are out of scope for this version; any future design would be expected to build on existing delegation mechanisms such as [RFC8693].

Two patterns cover most multi-party cases without a chain. Both are informative: they use only what this document already defines.

11.1. Issuer Fan-Out

When one piece of work is carried out as several requests -- an orchestrating agent that hands sub-tasks to sub-agents, or a batch of payments executed by several workers -- each request is decided by the issuer on its own, and the issuer issues one bound permit per request. Each permit names its own audience, its own presenter key in "cnf" (the sub-agent's or worker's key, not the orchestrator's), its own "authorization_details" and "action_digest", and its own "jti".

Budgets are then enforced where they live. Each sub-request reserves against the issuer's cumulative budgets under [AADP] Section 4, so the total across all sub-requests is bounded by the same budgets that bound any other set of actions, and each reservation is resolved by its own report. No party other than the issuer derives or narrows a permit, and no recipient has to verify more than one issuer signature.

11.2. Upstream Reference

When a recipient in one domain acts, in turn, towards a further domain -- A's request causes B to call C -- B takes its own decision and presents its own bound permit to C, as above. C may still want to know what caused B's request. B's issuer MAY include, in the permit's "evidence" claim, an "upstream" member: an array of references to earlier hops, each of the form { "iss", "jti", "digest" }, where "digest" is the digest of that hop's bound permit or of the recipient confirmation B returned for it (Section 10).

  • An upstream reference is evidence, not authority. A recipient MUST NOT treat it as authorizing anything, and MUST NOT relax any verification step because of it.
  • A recipient MAY record upstream references, and MAY refuse under local policy a request that lacks one where its policy requires provenance ("local-policy").
  • Because each hop's confirmation names the permit it answered, and each later permit names the earlier confirmation, the hops form a sequence of records that each side signed, readable after the fact, without any hop deriving its authority from another.

12. Idempotency and Single Use

13. Relationship to AADP

15. Security Considerations

15.1. Bearer Versus Bound

Without "cnf" and the request signature, a bound permit is a bearer token. Section 4.2 forbids bearer permits across a trust boundary. The single most likely deployment mistake is accepting the permit without verifying the request signature; conformance vectors for it are mandatory (Section 17).

15.2. Key Compromise

A compromised issuer key forges permits until the recipient's table stops trusting it. Lifetimes are bounded (Section 3.3), and "status-checked" permits can be revoked at the status list. A compromised presenter key lets an attacker present permits already issued to that presenter, within their lifetimes, to their audiences, for their decided actions only.

15.3. A Compromised Presenter

A presenter that holds direct capability to the recipient can bypass this profile entirely; [AADP] Section 1.2 already disclaims that case. What this profile changes is the case in which the presenter uses the profile and substitutes a request: the substituted request fails "action-mismatch" or "content-mismatch", and the recipient's signed refusal records a decided action and a presented action that differ. That is not prevention. It turns a silent substitution into evidence that names both sides.

15.4. Upstream References

An upstream reference is written by the issuer of the permit that carries it and is not verified by this profile. A recipient that granted it authority would accept, from a compromised or careless issuer, a claim about another domain's decision that no one checked. Section 11.2 therefore makes it evidence only.

15.5. Time

Clock skew is bounded and recorded (Section 3.3). A recipient MUST NOT extend "exp" by its skew allowance beyond the "execute_within" deadline.

15.6. Intermediaries

A proxy that re-encodes the body, rewrites the path or strips unknown fields will cause refusals by design. Deployments with such intermediaries either terminate the profile at the intermediary, which then becomes the recipient with its own record, or configure it to pass covered components untouched.

15.7. What the Profile Does Not Protect

A request that policy should not have permitted; a recipient that lies in its confirmation; issuer and recipient in collusion; a mandate that was wrongly granted. Each needs controls outside this document.

16. Privacy Considerations

A bound permit discloses, to its recipient, the decided action, the presenter's identifier, the issuer, a policy version digest and optionally a mandate reference. It discloses no policy content and no other action. "authorization_details" SHOULD carry the minimum fields the action type needs. Recipient confirmations disclose the recipient's action identifier to the presenter. Digests of personal data SHOULD NOT be used as identifiers where the data is guessable.

17. Conformance Vectors

Each vector is a refusal with a named reason, and each is paired with the valid case beside it, so that a verifier that refuses everything does not pass. Where a rule has an absent case and an empty case, both are vectors.

Table 4: Conformance Vectors
# Mutation Expected
V01 Body changed after signing content-mismatch
V02 Path changed after signing request-signature-invalid
V03 Presented to a different recipient audience-mismatch
V04 "aud" carries two values audience-mismatch
V05 Presented after "exp" expired
V06 "exp" later than "execute_within" lifetime-invalid
V07 Same permit presented twice, same body stored result returned; recorded as a repeat
V08 Same "jti", different body idempotency-conflict
V09 Request signed by a key other than "cnf.jkt" presenter-key-mismatch
V10 Request not signed at all (bearer presentation) request-signature-missing
V11 Signature omits "content-digest" from the covered components binding-incomplete
V12 Body re-serialized with the same meaning, different bytes content-mismatch
V13 Same bytes, A derived differently from what was decided action-mismatch
V14 "authorization_details" type unknown to the recipient unknown-authorization-type
V15 Issuer trusted, action type outside its scope issuer-out-of-scope
V16 Issuer trusted for the type, amount above its limit issuer-out-of-scope
V17 "status-checked", policy version since superseded stale-policy
V18 "status-checked", status endpoint unreachable status-unavailable (could-not-check)
V19 Mandate digest differs from the evaluated AAE mandate-mismatch
V20 Referenced AAE evaluates to PENDING mandate-pending
V21 Permit carries "parent" chained-permit-unsupported
V22 Recipient confirmation names a different request digest detected by the presenter; the report records the discrepancy
V23 Consume store unavailable could-not-check, not a policy denial
V24 Permit valid, local policy denies local-policy, and the permit is not consumed

18. IANA Considerations

This document requests the following registrations. Registration procedures and templates will be completed in a later revision.

  1. Media types: "application/aadp-permit+jwt" and "application/aadp-confirmation+jwt".
  2. HTTP fields: "AADP-Permit" (request; the bound permit), "AADP-Permit-Status" (request; stapled freshness) and "AADP-Recipient-Confirmation" (response).
  3. An AADP Action Types registry: name and version, fields, units, the derivation of A, the comparison rule per field for scope checks, and any further covered components the type requires.
  4. An AADP Mandate Reference Types registry, with the initial value "aae", referencing [I-D.kroehl-agentic-trust-aae].
  5. An AADP Bound Permit Refusal Reasons registry: the codes of Section 9, each with a Problem Details type URI.
  6. JSON Web Token claims: "action_digest", "verdict", "tier", "policy_version", "mandate", "currentness", "status" and "evidence", where not already registered.
  7. OAuth authorization server metadata: "aadp_action_types_supported".

19. References

19.1. Normative References

[AADP]
Saha, S., "The Agent Action Decision Protocol (AADP): Per-Action Authorization for AI Agents", Work in Progress, Internet-Draft, draft-saha-aadp-04, , <https://datatracker.ietf.org/doc/draft-saha-aadp/>.
[I-D.kroehl-agentic-trust-aae]
Kroehl, L. K., "Agent Authorization Envelope (AAE): A Machine-Evaluable Authorization Structure for Autonomous AI Agents", Work in Progress, Internet-Draft, draft-kroehl-agentic-trust-aae-02, , <https://datatracker.ietf.org/doc/draft-kroehl-agentic-trust-aae/>. Normative only for implementations that support the "aae" mandate type.
[RFC2119]
Bradner, S., "Key words for use in RFCs to Indicate Requirement Levels", BCP 14, RFC 2119, , <https://www.rfc-editor.org/info/rfc2119>.
[RFC7515]
Jones, M., Bradley, J., and N. Sakimura, "JSON Web Signature (JWS)", RFC 7515, , <https://www.rfc-editor.org/info/rfc7515>.
[RFC7519]
Jones, M., Bradley, J., and N. Sakimura, "JSON Web Token (JWT)", RFC 7519, , <https://www.rfc-editor.org/info/rfc7519>.
[RFC7638]
Jones, M. and N. Sakimura, "JSON Web Key (JWK) Thumbprint", RFC 7638, , <https://www.rfc-editor.org/info/rfc7638>.
[RFC7800]
Jones, M., Bradley, J., and H. Tschofenig, "Proof-of-Possession Key Semantics for JSON Web Tokens (JWTs)", RFC 7800, , <https://www.rfc-editor.org/info/rfc7800>.
[RFC8174]
Leiba, B., "Ambiguity of Uppercase vs Lowercase in RFC 2119 Key Words", BCP 14, RFC 8174, , <https://www.rfc-editor.org/info/rfc8174>.
[RFC8725]
Sheffer, Y., Hardt, D., and M. Jones, "JSON Web Token Best Current Practices", BCP 225, RFC 8725, , <https://www.rfc-editor.org/info/rfc8725>.
[RFC8785]
Rundgren, A., Jordan, B., and S. Erdtman, "JSON Canonicalization Scheme (JCS)", RFC 8785, , <https://www.rfc-editor.org/info/rfc8785>.
[RFC9396]
Lodderstedt, T., Richer, J., and B. Campbell, "OAuth 2.0 Rich Authorization Requests", RFC 9396, , <https://www.rfc-editor.org/info/rfc9396>.
[RFC9421]
Backman, A., Richer, J., and M. Sporny, "HTTP Message Signatures", RFC 9421, , <https://www.rfc-editor.org/info/rfc9421>.
[RFC9449]
Fett, D., Campbell, B., Bradley, J., Lodderstedt, T., Jones, M., and D. Waite, "OAuth 2.0 Demonstrating Proof of Possession (DPoP)", RFC 9449, , <https://www.rfc-editor.org/info/rfc9449>.
[RFC9457]
Nottingham, M., Wilde, E., and S. Dalal, "Problem Details for HTTP APIs", RFC 9457, , <https://www.rfc-editor.org/info/rfc9457>.
[RFC9530]
Polli, R. and L. Pardue, "Digest Fields", RFC 9530, , <https://www.rfc-editor.org/info/rfc9530>.

19.2. Informative References

[I-D.ietf-httpapi-idempotency-key-header]
Jena, J. and S. Dalal, "The Idempotency-Key HTTP Header Field", Work in Progress, Internet-Draft, draft-ietf-httpapi-idempotency-key-header-07, , <https://datatracker.ietf.org/doc/draft-ietf-httpapi-idempotency-key-header/>.
[I-D.ietf-oauth-status-list]
Looker, T., Bastian, P., and C. Bormann, "Token Status List (TSL)", Work in Progress, Internet-Draft, draft-ietf-oauth-status-list-21, , <https://datatracker.ietf.org/doc/draft-ietf-oauth-status-list/>.
[I-D.ietf-oauth-transaction-tokens]
Tulshibagwale, A., Fletcher, G., and P. Kasselman, "Transaction Tokens", Work in Progress, Internet-Draft, draft-ietf-oauth-transaction-tokens-11, , <https://datatracker.ietf.org/doc/draft-ietf-oauth-transaction-tokens/>.
[I-D.lee-orprg-permit-receipts]
Lee, Y. B., "Permit Receipts for Permit-Before-Commit Authorization of AI-Agent and Workload External Effects", Work in Progress, Internet-Draft, draft-lee-orprg-permit-receipts-00, , <https://datatracker.ietf.org/doc/draft-lee-orprg-permit-receipts/>.
[I-D.mih-agent-bilateral-attestation]
Mih, S., "Bilateral Attestation of Cross-Organization Agent Actions", Work in Progress, Internet-Draft, draft-mih-agent-bilateral-attestation-02, , <https://datatracker.ietf.org/doc/draft-mih-agent-bilateral-attestation/>.
[I-D.munoz-scitt-permit-profile]
Munoz, C., "A SCITT Profile for Pre-Execution AI Action Authorization Records", Work in Progress, Internet-Draft, draft-munoz-scitt-permit-profile-01, , <https://datatracker.ietf.org/doc/draft-munoz-scitt-permit-profile/>.
[I-D.munoz-wimse-authorization-evidence]
Munoz, C., "Signed Authorization-Evidence Records for WIMSE-Authorized AI Agent Actions", Work in Progress, Internet-Draft, draft-munoz-wimse-authorization-evidence-01, , <https://datatracker.ietf.org/doc/draft-munoz-wimse-authorization-evidence/>.
[I-D.ruvalcaba-nhe-authz]
Ruvalcaba, C. X., "NHE Backchannel Authorization: Graduated Autonomy and Intent-Scoped Credentials for Autonomous Agent Actions", Work in Progress, Internet-Draft, draft-ruvalcaba-nhe-authz-00, , <https://datatracker.ietf.org/doc/draft-ruvalcaba-nhe-authz/>.
[I-D.schrock-ep-authorization-receipts]
Schrock, I., "Authorization Receipts for High-Risk Agent Actions", Work in Progress, Internet-Draft, draft-schrock-ep-authorization-receipts-13, , <https://datatracker.ietf.org/doc/draft-schrock-ep-authorization-receipts/>.
[I-D.schrock-ep-bounded-capability-receipts]
Schrock, I., "Bounded Capability Receipts and Durable Spend Control for Agent Actions", Work in Progress, Internet-Draft, draft-schrock-ep-bounded-capability-receipts-06, , <https://datatracker.ietf.org/doc/draft-schrock-ep-bounded-capability-receipts/>.
[I-D.sirkkavaara-vaara-receipt]
"The Vaara Receipt: A Recomputable Receipt Format for Decisions About Autonomous Actions", Work in Progress, Internet-Draft, draft-sirkkavaara-vaara-receipt-11, , <https://datatracker.ietf.org/doc/draft-sirkkavaara-vaara-receipt/>.
[I-D.toraman-noa-action-digest]
Toraman, T., "The NOA Action Digest: a Domain-Separated Correlation Value for Human-Approved Agent Actions", Work in Progress, Internet-Draft, draft-toraman-noa-action-digest-01, , <https://datatracker.ietf.org/doc/draft-toraman-noa-action-digest/>.
[OIDFED]
OpenID Foundation, "OpenID Federation 1.0", <https://openid.net/specs/openid-federation-1_0.html>.
[RFC7942]
Sheffer, Y. and A. Farrel, "Improving Awareness of Running Code: The Implementation Status Section", BCP 205, RFC 7942, , <https://www.rfc-editor.org/info/rfc7942>.
[RFC8414]
Jones, M., Sakimura, N., and J. Bradley, "OAuth 2.0 Authorization Server Metadata", RFC 8414, , <https://www.rfc-editor.org/info/rfc8414>.
[RFC8693]
Jones, M., Nadalin, A., Campbell, B., Bradley, J., and C. Mortimore, "OAuth 2.0 Token Exchange", RFC 8693, , <https://www.rfc-editor.org/info/rfc8693>.
[SPIFFE]
Cloud Native Computing Foundation, "Secure Production Identity Framework for Everyone (SPIFFE)", <https://spiffe.io/docs/latest/spiffe-about/overview/>.
[STAGE-RECEIPTS]
Saha, S., "Stage Receipts: A Verifiable Record Format for Staged Pipelines", Work in Progress, Internet-Draft, draft-saha-stage-receipts-00, , <https://datatracker.ietf.org/doc/draft-saha-stage-receipts/>.

Appendix A. The Incident View (Informative)

The form a record-reading tool should use when a bound-permit request fails, stating what is and is not known:

Action:           payments.transfer/1
First failure:    content-mismatch (step 7)
Established:      Permit bp-4f0c2a91d7e3 decided EUR 40.00 to
                  acme-gmbh. The recipient received a body whose
                  digest does not match. The recipient refused
                  before any effect, and signed the refusal.
Not established:  Which component changed the body.
                  Whether the cause was an attacker or a defect.
Evidence:         permit digest, request content digest,
                  signature base digest, recipient confirmation,
                  the sending stage's record

Appendix B. Implementation Status

[Note to the RFC Editor: please remove this section before publication, as described in RFC 7942.]

This section records the status of known implementations of this profile at the time of posting, following [RFC7942]. Listing an implementation here is not an endorsement.

Implementation:
onedoor, module "onedoor.permit": the issuer and the standalone recipient check (Section 8).
Source:
https://github.com/shamiksaharcciit-oss/onedoor, commit 38acd847372067d74fbc9ae99b8cab3843778406. Readers checking these results should build from that commit.
Licence:
Apache-2.0.
Maturity:
Reference implementation, written to test this profile. It is not a production service.
Coverage:
22 of the 24 conformance vectors in Section 17 are implemented, each with a passing test. The vector table is kept as data in "tests/permit/vectors/manifest.json", and "tests/permit/test_conformance_vectors.py" names the test for each vector and fails if a vector has no stated status.
Not implemented:
V17 ("stale-policy"): no status mechanism is implemented, so a superseded policy version cannot be detected. V22: the recipient confirmation (Section 10) is not implemented, so there is no presenter-side check. Both are stated in the implementation's own vector table.
Result:
At that commit, the permit test suite ("tests/permit") passes in full: 92 tests on CPython 3.12.
Contact:
The author.

Appendix C. Open Issues

  1. Should this profile's action-type registry and a mandate format's action vocabulary be one registry, with each entry stating both derivations from one request?
  2. Should the recipient confirmation carry the mandate layer's verdict core digest in the form proposed here, or in a form that layer defines?
  3. Is recording the mandate status observed at decision time enough for a later reader to reconstruct why the reference was valid, or does a mandate format need to expose its own history?
  4. Should "policy_version" and the mandate reference share one currentness mechanism, since the window in which either can go stale is the same?
  5. Should "time-bounded" be permitted at all for action types above a risk threshold, or only declared?
  6. Should the recipient confirmation be required, rather than recommended, for refusals?

Author's Address

Shamik Saha
Independent
Amsterdam
Netherlands