← Machine Authority Protocol
Machine Authority Protocol v1.0 Decision Envelope v1.0 Status: STABLE Published: 2026-05-03

Abstract

The Decision Envelope is the response object returned by the Action Authorization Boundary (AAB) when a Canonical Action Representation (CAR) is submitted for evaluation. It carries one of the five AARM v1 §R4 decisions (ALLOW, DENY, DEFER, MODIFY, STEP_UP) or the MAP-defined REVOKE extension that aborts an in-flight ALLOW, the policy version under which the decision was made, an evaluation timestamp, the optional payload that the decision requires, and an aab_signature that the dispatcher verifies before acting on the envelope.

The Decision Envelope is the wire-format binding of AARM v1 §R4 (decision states) and the integrity surface used by §R5 (signed receipts) when the envelope is later embedded in a Cryptographic Attestation of Consent (CAC). The normative artifact for validation is schema/decision-envelope-v1.json.

1. Introduction

When an AAB returns a decision over an unauthenticated transport, an attacker can flip DENY → ALLOW, downgrade STEP_UP → ALLOW, or rewrite the defer_payload.approver_endpoint to point at an attacker-controlled host that issues a forged ApprovalDecision. Today's AARM-aligned proofs of concept either ignore this attack surface or wave at “transport security” (TLS termination at the load balancer is not transport security for an envelope that crosses a service boundary).

The Decision Envelope closes the gap by binding every decision to the AAB's identity key with a JWS-EdDSA signature over the RFC 8785-canonicalized envelope bytes. At v1, every envelope MUST carry both aab_kid and aab_signature. Verifiers MUST refuse to act on a decision that fails signature verification.

2. Conventions

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 RFC 2119.

JSON terms are as defined in RFC 8259. Canonicalization rules are inherited from CAR §6: RFC 8785 (JCS) with NFC normalization of every string and rejection of empty-string keys.

3. Schema

3.1 Top-level fields

Field Type Required Description
envelope_versionstringREQUIREDMUST be "1.0".
decisionstring (enum)REQUIREDOne of ALLOW, DENY, DEFER, MODIFY, STEP_UP, REVOKE.
action_idstring (UUIDv4)REQUIREDMUST equal the action_id of the CAR being decided.
decided_atstring (RFC 3339)REQUIREDUTC timestamp at which the AAB returned the decision.
policy_versionstringREQUIREDOpaque AAB-issued version under which the decision was rendered.
policy_decision_idstring (UUIDv4)RECOMMENDEDGlobally unique decision identifier; used to correlate logs across services.
expires_atstring (RFC 3339)CONDITIONALREQUIRED for ALLOW. The dispatcher MUST NOT execute the action after this timestamp.
reason_codestringCONDITIONALREQUIRED for DENY and REVOKE. Machine-readable code (e.g. policy.rate_limit_exceeded).
reason_detailstringOPTIONALHuman-readable elaboration of reason_code. Never normative.
defer_payloadobjectCONDITIONALREQUIRED for DEFER. See §3.2.
modify_payloadobjectCONDITIONALREQUIRED for MODIFY. See §3.3.
step_up_payloadobjectCONDITIONALREQUIRED for STEP_UP. See §3.4.
aab_kidstringREQUIREDThe kid of the AAB key that signed this envelope. MUST equal the kid in the aab_signature JWS header.
aab_signaturestringREQUIREDJWS Compact Serialization over the canonical envelope (minus this field). See §4.

The schema declares additionalProperties: false. Vendor extensions travel via the extensions map of the originating CAR, not the envelope.

Payload exclusivity (NORMATIVE). At most one of defer_payload, modify_payload, step_up_payload MAY be present, and exactly one MUST be present when decision is DEFER, MODIFY, or STEP_UP respectively. ALLOW, DENY, and REVOKE envelopes MUST carry none of the three. Verifiers MUST refuse any envelope that violates this rule with verdict SCHEMA_VIOLATION — checked before signature verification so a malformed envelope cannot mask its shape under MISSING_SIGNATURE. Without exclusivity an attacker could ship a single signed envelope carrying both a DEFER resume token and a MODIFY rewrite, and downstream services that switched on the wrong field would observe two real-world decisions for one signature.

3.2 defer_payload (decision = DEFER)

Field Type Required Description
resume_tokenstringREQUIREDBound to the dispatcher key per Elicitation Loop §4.
approver_endpointstring (https URI)REQUIREDHTTPS endpoint where the dispatcher submits the DeferredActionRequest.
expires_atstring (RFC 3339)REQUIREDIf no ApprovalDecision arrives by this timestamp, the dispatcher MUST treat the deferral as DENY.
dispatcher_jktstring (base64url)REQUIREDJWK SHA-256 thumbprint (RFC 7638) of the dispatcher key authorized to resume. The approver MUST verify the inbound DPoP proof against this thumbprint.
approver_audienceobjectRECOMMENDEDHint identifying the approver audience. One of {spiffe_id}, {did}, {url}. Not authoritative.

3.3 modify_payload (decision = MODIFY)

Field Type Required Description
modified_argumentsobjectREQUIREDReplacement value for arguments on the new CAR. MUST validate against the same schema the original arguments validated against.
child_action_idstring (UUIDv4)REQUIREDThe action_id the dispatcher MUST assign to the rewritten CAR.
parent_action_idstring (UUIDv4)REQUIREDMUST equal the envelope's action_id; preserved for audit linkage.
modification_reasonstringOPTIONALHuman-readable explanation.

MODIFY is AAB-internal: a human approver loop never returns MODIFY. The dispatcher MUST mint a fresh CAR carrying child_action_id, modified_arguments, and a parent_action_id field equal to the parent's action_id, then resubmit that CAR to the AAB. The new CAR gets its own envelope and (if approved) its own CAC. Receipt traceability runs along the parent → child chain.

3.4 step_up_payload (decision = STEP_UP)

Field Type Required Description
required_acrstringREQUIREDRFC 9470 acr_values syntax — the Authentication Context Class Reference the actor MUST satisfy before the action proceeds.
required_amrarray of stringOPTIONALRFC 8176 Authentication Method Reference values.
step_up_endpointstring (https URI)REQUIREDEndpoint where the actor performs the step-up authentication.
expires_atstring (RFC 3339)REQUIREDIf the step-up is not completed by this timestamp, treat as DENY.

After step-up succeeds, the dispatcher resubmits the original CAR with an updated actor block reflecting the elevated authentication; the AAB returns a new envelope with a new policy_decision_id. Like MODIFY, STEP_UP is AAB-internal.

3.5 Reason-code namespacing

reason_code values are dotted lowercase identifiers in three reserved prefixes:

  • policy.* — the request was rejected by policy evaluation.
  • identity.* — actor identity, PoP, or delegation chain failed validation.
  • aab.* — the AAB itself rejected the request (malformed envelope, expired CAR, version mismatch).

Vendor-specific codes MUST use a reverse-DNS prefix (e.g. com.veto.policy.high_risk_data). The reserved prefixes above are normative and MUST NOT be used for vendor extensions.

4. Envelope signature (aab_signature)

4.1 Algorithm

aab_signature is a JWS Compact Serialization (RFC 7515) value computed as follows:

  1. Build the unsigned envelope. Serialize every required and conditional field of the envelope except aab_signature itself.
  2. Canonicalize. Run the result through the CAR canonicalizer (CAR §6.2): RFC 8785 with NFC normalization of every string and rejection of empty-string keys. The output is the canonical envelope bytes.
  3. Sign. Produce a JWS Compact Serialization over the canonical envelope bytes using:
    • alg: EdDSA (Ed25519 curve).
    • kid: identifier resolvable to a public key under the AAB's identity (see CAR §4 for identity formats).
    • typ: MAP-DECISION-ENVELOPE-1.
    • b64: false and the corresponding crit: ["b64"] per RFC 7797. The payload is the canonical envelope bytes; only the JOSE header and signature are base64url-encoded.
  4. Embed. Set aab_signature to the resulting JWS Compact Serialization (<header>..<signature> with the empty middle segment).

4.2 Verification

A dispatcher verifying an inbound envelope MUST:

  1. Reject envelopes whose decision is not in the §3.1 enum.
  2. Reject envelopes that lack required conditional payloads (defer_payload for DEFER, etc.).
  3. Reject envelopes that omit aab_kid or aab_signature with aab.unsigned_envelope.
  4. Recompute the canonical envelope bytes by removing aab_signature from the parsed object and applying the canonicalizer. aab_kid is part of the signed body and MUST remain when canonicalizing.
  5. Resolve kid to a JWK using the AAB's published identity bundle (SPIFFE trust bundle, DID document, or <aab_url>/.well-known/map-aab-keys.json for URL-typed identities).
  6. Verify the JWS over the canonical envelope bytes.
  7. Reject the envelope if any step fails. The dispatcher MUST NOT execute, defer, modify, or step up on an envelope that fails verification.

4.3 Why a JWS over the canonical envelope (not just car_hash)

A signature solely over car_hash would prove the AAB knew about the CAR but would not prevent rewriting decision, expires_at, approver_endpoint, required_acr, or reason_code. Each of those fields is a security boundary. The MAP envelope signature MUST cover the complete envelope state because any field is policy-bearing.

4.4 Stability commitment

The Decision Envelope wire format is locked at v1.0. A breaking change (field rename, added required field, removed optional field, semantic shift) MUST be published as a separate v2-versioned schema with at least a 12-month deprecation window during which v1 verifiers continue to operate. The MAP council ratifies any such bump.

5. State machine

                +-------+        +--------+
   submit  ---->| AAB   |------->| ALLOW  | --> dispatcher executes
                |       |        +--------+
                |       |
                |       |        +--------+
                |       |------->| DENY   | --> dispatcher refuses
                |       |        +--------+
                |       |
                |       |        +--------+
                |       |------->| DEFER  | --> dispatcher enters Elicitation Loop
                |       |        +--------+
                |       |
                |       |        +--------+
                |       |------->| MODIFY | --> dispatcher mints child CAR, resubmits
                |       |        +--------+
                |       |
                |       |        +--------+
                |       |------->|STEP_UP | --> actor re-authenticates, resubmits CAR
                |       |        +--------+
                |       |
                |       |        +--------+
                |       |------->| REVOKE | --> dispatcher aborts an in-flight session
                +-------+        +--------+

REVOKE is the only decision the AAB MAY send asynchronously: it applies to a previously-issued ALLOW within its expires_at window and tells the dispatcher to halt or roll back any work derived from that ALLOW. A REVOKE envelope MUST carry the original action_id and SHOULD carry a reason_code of policy.revoked.*.

6. Examples

See examples/decision-allow.json, examples/decision-defer.json, examples/decision-modify.json, and examples/decision-step-up.json.

7. Security considerations

  1. Replay across actions. aab_signature covers action_id and decided_at, so a captured envelope cannot be replayed against a different CAR. Within the same action the expires_at window bounds reuse.
  2. Replay across deployments. Implementations SHOULD include a deployment-scoped value in policy_version so an envelope signed by a staging AAB cannot be replayed at a production dispatcher with the same trust bundle.
  3. Compromise of the AAB key. Rotation is via the standard identity-bundle mechanism; revoked keys MUST NOT verify. A transparency-log profile is on the post-v1.0 roadmap.
  4. Approver-endpoint substitution. defer_payload.approver_endpoint is signed; an attacker cannot redirect the Elicitation Loop without forging the AAB key.
  5. MODIFY argument poisoning. The replacement modified_arguments is policy-evaluated when the dispatcher resubmits the child CAR. Implementations MUST NOT short-circuit re-evaluation; the parent's policy_decision_id is for audit, not authorization.

8. References