| Internet-Draft | SPICE-INTENT-CHAIN | March 2026 |
| Krishnan, et al. | Expires 19 September 2026 | [Page] |
This document defines the intent_chain claim as a companion to the actor_chain claim defined in {{!I-D.draft-mw-spice-actor-chain}}. While the actor chain addresses delegation provenance (WHO delegated to whom), the intent chain addresses content provenance (WHAT was produced and HOW it was transformed).¶
In AI agent workflows, content flows through multiple processing stages including AI agents and filters. The intent chain provides a cryptographically verifiable, tamper-evident record of this content journey. The full intent chain is stored as ordered logs, with only the Merkle root included in the OAuth token for efficiency.¶
Together, the actor chain and intent chain provide complete governance for autonomous AI agent systems, addressing Spoofing, Tampering, Repudiation, and Elevation of Privilege threats in the STRIDE threat model.¶
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 19 September 2026.¶
Copyright (c) 2026 IETF Trust and the persons identified as the document authors. All rights reserved.¶
This document is subject to BCP 78 and the IETF Trust's Legal Provisions Relating to IETF Documents (https://trustee.ietf.org/license-info) in effect on the date of publication of this document. Please review these documents carefully, as they describe your rights and restrictions with respect to this document. Code Components extracted from this document must include Revised BSD License text as described in Section 4.e of the Trust Legal Provisions and are provided without warranty as described in the Revised BSD License.¶
The Actor Chain extension to {{!RFC8693}} (defined in {{!I-D.draft-mw-spice-actor-chain}}) addresses the Delegation Auditability Gap by providing cryptographic proof of the actual delegation path between AI agents. However, it does not address a complementary gap: Content Provenance.¶
In AI agent workflows, content flows through multiple processing stages:¶
Agent A -> Filter -> Filter -> Agent B -> Filter -> Agent C -> Tool¶
Each stage may transform the content. AI agent outputs are inherently non-deterministic and cannot be trusted without validation. Filters (both AI-based and rule-based) transform this content before it reaches the next stage.¶
For complete governance, systems require proof of:¶
Without this proof, the content transformation history cannot be reconstructed for audit or dispute resolution. Consider the following repudiation scenario:¶
Without cryptographic proof binding Agent A's identity to its specific output hash, this claim cannot be disproven. The intent chain solves this by requiring every agent to sign input_hash + output_hash via intent_sig, creating non-repudiable evidence of what each agent received and produced.¶
This specification is part of a three-axis "Truth Stack" for AI agent governance:¶
| Specification | Axis | Question Answered | STRIDE Coverage |
|---|---|---|---|
| Actor Chain ({{!I-D.draft-mw-spice-actor-chain}}) | Identity | WHO delegated to whom? | Spoofing, Repudiation, Elevation of Privilege |
| Intent Chain (this document) | Content | WHAT was produced and transformed? | Repudiation, Tampering |
| Inference Chain ({{!I-D.draft-mw-spice-inference-chain}}) | Computation | HOW was the output computed? | Spoofing (computational), Tampering (model) |
| Chain | Plane | Token Content | Full Chain | Primary Consumer |
|---|---|---|---|---|
| Actor | Data Plane | Full chain inline | In token | Every Relying Party (real-time authorization) |
| Intent | Audit Plane | Merkle root only | External registry | Audit systems, forensic investigators |
| Inference | Audit Plane | Merkle root only | External registry | Auditors, compliance systems |
The three chains are independent and composable:¶
The intent chain is designed with the following goals:¶
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.¶
actor_chain_registry claim in the token.¶
The governance model consists of three layers:¶
Every governance chain traces back to a session. The session provides:¶
Session information is captured in standard OAuth token claims:¶
| Claim | Purpose |
|---|---|
sub
|
Session subject (human or system initiator) |
sid
|
Session identifier — stable across token exchanges within a session. Equals session.session_id. Defined as a top-level claim in {{!I-D.draft-mw-spice-actor-chain}} |
jti
|
Token identifier (unique per token exchange) |
iat
|
Session start time |
exp
|
Session expiry time |
The actor chain is defined in {{!I-D.draft-mw-spice-actor-chain}}. Key properties:¶
This document assumes familiarity with the actor chain specification.¶
The intent chain is defined in this document. Key properties:¶
| Threat | Mitigation | Component |
|---|---|---|
| S - Spoofing | Cryptographic identity, signed chain entries | Actor Chain |
| T - Tampering | Merkle tree integrity, append-only logs | Intent Chain |
| R - Repudiation | Signed delegation (Actor) + Signed outputs (Intent) | Both |
| I - Information Disclosure | Selective disclosure (SD-JWT) | Both (optional) |
| D - Denial of Service | Session expiry, rate limits | Session + Infrastructure |
| E - Elevation of Privilege | Scope attenuation, policy enforcement | Actor Chain |
The intent chain contains two types of entries:¶
| Entry Type | Determinism | Signed Fields | Type-Specific Fields |
|---|---|---|---|
| Non-Deterministic (AI agent output, AI-based filter) | Non-deterministic |
input_hash + output_hash
|
model_info (optional) |
| Deterministic (rule-based filter) | Deterministic |
input_hash + output_hash
|
rule_id, rule_hash
|
All entry types REQUIRE both input_hash and output_hash. This uniform structure ensures that every consecutive pair satisfies entry[i].output_hash == entry[i+1].input_hash, creating a complete content provenance chain. The cost is approximately 40 bytes per entry in the ordered logs — not in the token itself, which carries only the Merkle root regardless of entry count.¶
Non-deterministic entries record outputs from AI agents or AI-based filters whose output cannot be reproduced from the input alone.¶
Examples:¶
Properties:¶
input_hash and output_hash MUST be recorded and signed¶
Agent Output Example:¶
{
"type": "non_deterministic",
"sub": "spiffe://example.com/agent/orchestrator",
"input_hash": "sha256:fff000...",
"output_hash": "sha256:abc123...",
"iat": 1700000010,
"intent_digest": "sha256:...",
"intent_sig": "eyJhbGci..."
}
¶
AI Filter Example:¶
{
"type": "non_deterministic",
"sub": "spiffe://example.com/filter/ai-guardrail",
"filter_version": "v2.1",
"input_hash": "sha256:abc123...",
"output_hash": "sha256:def456...",
"model_info": {
"model": "llama-guard-3",
"categories": ["violence", "pii", "prompt_injection"]
},
"iat": 1700000015,
"intent_digest": "sha256:...",
"intent_sig": "eyJhbGci..."
}
¶
Deterministic filter entries record transformations by rule-based filters whose output can be reproduced from the input and rules.¶
Examples:¶
Properties:¶
input_hash and output_hash MUST be recorded and signed¶
rule_id and rule_hash are type-specific signed fields in the log entry, enabling independent re-verification¶
Structure:¶
{
"type": "deterministic",
"sub": "spiffe://example.com/filter/schema-validator",
"filter_version": "v1.0",
"input_hash": "sha256:def456...",
"output_hash": "sha256:ghi789...",
"rule_id": "ticket-schema-v2",
"rule_hash": "sha256:rrr...",
"transform_applied": {
"fields_validated": ["title", "priority", "amount"],
"fields_modified": ["priority"],
"modification": {
"priority": {
"from": "critical",
"to": "medium",
"reason": "bounds_exceeded"
}
}
},
"reproducible": true,
"iat": 1700000016,
"intent_digest": "sha256:...",
"intent_sig": "eyJhbGci..."
}
¶
All intent chain entries share common fields:¶
| Field | Type | Required | Description |
|---|---|---|---|
type
|
string | REQUIRED | Entry type: non_deterministic, deterministic
|
sub
|
string | REQUIRED | SPIFFE ID of the agent or filter |
input_hash
|
string | REQUIRED | SHA-256 hash of the input content |
output_hash
|
string | REQUIRED | SHA-256 hash of the output content |
iat
|
number | REQUIRED | Timestamp when entry was created |
intent_digest
|
string | REQUIRED | Hash of the canonically serialized entry for Merkle leaf computation |
intent_sig
|
string | REQUIRED | Signature over intent_digest using the agent's or filter's private key |
intent_digest Computation
The intent_digest field is computed as the SHA-256 hash of the canonically serialized entry, excluding the intent_digest and intent_sig fields themselves. This hash serves as the leaf node in the Merkle tree.¶
For an entry E with fields {type, sub, input_hash, output_hash, iat, ...}:¶
intent_digest = SHA-256(canonical_json(E \ {intent_digest, intent_sig}))
¶
Where canonical_json follows JSON Canonicalization Scheme {{JCS}} (RFC 8785) to ensure deterministic serialization.¶
The intent_sig (when REQUIRED) is computed over the intent_digest value using the agent's private key:¶
intent_sig = Sign(agent_key, intent_digest)¶
This two-step process ensures that: (a) the digest is stable and independent of signature ordering, and (b) the signature covers all content-relevant fields of the entry.¶
Additional fields by entry type:¶
| Field | Entry Types | Description |
|---|---|---|
filter_version
|
Filters | Version of filter |
rule_id
|
Deterministic | Identifier of rule applied |
rule_hash
|
Deterministic | Hash of rule definition |
model_info
|
Non-deterministic | AI model information |
transform_applied
|
Filters | Details of transformation |
reproducible
|
Deterministic | Boolean indicating reproducibility |
The intent registry stores immutable intent chain entries as ordered logs.¶
Contents:¶
Intent registry entries MUST NOT contain OAuth tokens, bearer credentials, or signing keys. Entries contain only content hashes, metadata, agent identities, and entry-level signatures. The token references the registry via the
intent_registryclaim; the registry MUST NOT store or reference the token itself.¶
Properties:¶
session.session_id¶
Implementations SHOULD use an append-only log that supports partitioned, ordered retrieval by the token's session.session_id claim and provides tamper-evident guarantees (e.g., via hash chaining or inclusion proofs).¶
Log Structure:¶
{
"session_id": "sess-uuid-12345",
"offset": 0,
"entry": {
"type": "non_deterministic",
"sub": "spiffe://example.com/agent/A",
"input_hash": "sha256:prompt...",
"output_hash": "sha256:abc...",
"iat": 1700000010,
"intent_digest": "sha256:...",
"intent_sig": "eyJ..."
}
}
¶
session_id and jti
The session_id is a stable identifier for the end-user interaction. It remains constant as the delegation chain grows through multiple token exchanges, each of which produces a new token with a distinct jti:¶
User session: sess-uuid-12345
Token Exchange 1 (jti: "tok-aaa")
User → Agent A
Intent entries: offset 0 (Agent A output)
Token Exchange 2 (jti: "tok-bbb")
Agent A → Agent B
Intent entries: offset 1 (filter), offset 2 (Agent B output)
Token Exchange 3 (jti: "tok-ccc")
Agent B → Agent C
Intent entries: offset 3 (filter), offset 4 (Agent C output)
¶
All intent chain entries share session_id: "sess-uuid-12345" regardless of which token exchange produced them. The session_id is carried forward during each token exchange as part of the session claim. During forensic verification, the investigator retrieves all entries for a session_id to reconstruct the complete content journey.¶
The Merkle tree is constructed from ordered log entries. Leaf nodes are the SHA-256 hashes of canonically serialized intent chain entries. Internal nodes are the SHA-256 hash of the concatenation of their two child hashes. When a level has an odd number of nodes, the last node is promoted to the next level.¶
See Appendix A for a visual depiction and reference construction algorithm.¶
Only the Merkle root is included in the OAuth token:¶
{
"intent_root": "sha256:abc123def456...",
"intent_alg": "sha256",
"intent_registry": "https://intent-log.example.com"
}
¶
| Field | Type | Required | Description |
|---|---|---|---|
intent_root
|
string | REQUIRED | Merkle root hash of intent chain |
intent_alg
|
string | OPTIONAL | Hash algorithm (default: sha256) |
intent_registry
|
string | REQUIRED | URI of intent registry for proof retrieval |
The complete token combines session, actor chain, and intent chain:¶
{
"iss": "https://auth.example.com",
"sub": "user-alice",
"aud": "https://api.example.com",
"jti": "tok-aaa-12345",
"sid": "sess-uuid-12345",
"iat": 1700000000,
"exp": 1700003600,
"session": {
"session_id": "sess-uuid-12345",
"type": "human_initiated",
"initiator": "user-alice",
"approval_ref": "approval-uuid-789",
"max_chain_depth": 5
},
"actor_chain": [
{
"sub": "spiffe://example.com/agent/orchestrator",
"iss": "https://auth.example.com",
"iat": 1700000010
},
{
"sub": "spiffe://example.com/agent/support",
"iss": "https://auth.example.com",
"iat": 1700000030
}
],
"intent_root": "sha256:abc123def456789...",
"intent_registry":
"https://intent-log.example.com/sessions/sess-uuid-12345"
}
¶
| Claim | Type | Description |
|---|---|---|
sid
|
string | Session identifier — stable across token exchanges. Equals session.session_id. Defined as a top-level claim in {{!I-D.draft-mw-spice-actor-chain}} |
session.session_id
|
string | Stable identifier for the end-user interaction, assigned by the AS on first token issuance and carried forward during subsequent token exchanges. MUST equal the top-level sid claim |
session.type
|
string | Session type: human_initiated, system_initiated, scheduled
|
session.initiator
|
string | Identity of session initiator |
session.approval_ref
|
string | Reference to approval record |
session.max_chain_depth
|
number | Maximum allowed delegation depth |
Defined in {{!I-D.draft-mw-spice-actor-chain}}.¶
| Claim | Type | Description |
|---|---|---|
intent_root
|
string | Merkle root hash of intent chain |
intent_alg
|
string | Hash algorithm used (default: sha256) |
intent_registry
|
string | URI for retrieving full chain or proofs (REQUIRED) |
{
"iss": "https://auth.example.com",
"sub": "user-alice",
"jti": "tok-bbb-12345",
"iat": 1700000000,
"exp": 1700003600,
"actor_chain": [
{
"sub": "spiffe://example.com/agent/A",
"iss": "https://auth.example.com",
"iat": 1700000010
}
]
}
¶
{
"iss": "https://auth.example.com",
"sub": "user-alice",
"jti": "tok-ccc-12345",
"iat": 1700000000,
"exp": 1700003600,
"intent_root": "sha256:abc123...",
"intent_registry":
"https://intent-log.example.com/sessions/sess-uuid-12345"
}
¶
At request time, the Relying Party performs lightweight checks on the intent chain metadata in the token. Full chain verification is unnecessary on the hot path because:¶
The Relying Party SHOULD:¶
intent_root as a signed claim).¶
intent_root and intent_registry are present (policy: "intent chain coverage required").¶
deterministic entry").¶
The tiered verification table reflects the appropriate level of intent chain checking based on risk:¶
| Risk Level | Actor Chain | Intent Chain | Use Case |
|---|---|---|---|
| Low | Verify JWT signature | Check intent_root present |
Read operations |
| Medium | Verify JWT signature | Async policy check on entry types | Create/update |
| High | Verify JWT signature + actor sigs | Full forensic verification | Delete, transfer, admin |
The primary value of the intent chain is post-hoc troubleshooting and dispute resolution. When an incident occurs, an auditor or investigator performs full chain verification to determine which agent caused the problem.¶
Forensic verification requires two inputs: the original JWT (containing intent_root) and the intent chain entries from the registry. The intent registry stores chain entries but does not store the token itself.¶
To enable forensic analysis, tokens SHOULD be archived by one or more of the following:¶
jti.¶
Archived tokens MUST be stored securely and access-controlled, as they contain identity and delegation information.¶
To perform a complete forensic investigation:¶
intent_root and intent_registry from the archived token. Fetch the full intent chain from the registry using the session_id.¶
intent_root from the token. If they do not match, the chain has been tampered with.¶
intent_sig over intent_digest using the sub's public key (discoverable via SPIFFE trust bundle or JWKS). A failed signature indicates a forged entry.¶
entry[i].output_hash == entry[i+1].input_hash. A broken link indicates content was modified between steps without being recorded.¶
deterministic entries, retrieve the rule definition matching rule_hash, re-apply it to the content matching input_hash, and verify the output matches output_hash. A mismatch indicates the filter did not behave as recorded.¶
sub field of the faulty entry identifies the responsible agent.¶
When used together with the actor chain, forensic verification can answer both WHO and WHAT:¶
non_deterministic: verify that entry.sub appears in the actor_chain of the same token.¶
entry.iat falls within the actor's active window (between the actor's own iat and the next actor's iat in the chain).¶
When a dispute arises (e.g., "Agent A did not produce that harmful output"):¶
sub matches Agent A's SPIFFE ID.¶
intent_sig on each of those entries. A valid signature proves Agent A attested to producing that specific output_hash.¶
input_hash of Agent A's entry to verify what Agent A received as input.¶
input_hash matching Agent A's output_hash and a different output_hash — proving the filter made the change, not Agent A.¶
To verify a single entry without fetching the full chain:¶
intent_root from the token.¶
entry[i] from the registry.¶
intent_root.¶
Proof Size: O(log n) where n is the number of entries.¶
{
"entry": {
"type": "non_deterministic",
"sub": "spiffe://example.com/agent/A",
"input_hash": "sha256:prompt...",
"output_hash": "sha256:abc...",
"iat": 1700000010
},
"proof": {
"index": 0,
"siblings": [
{"position": "right", "hash": "sha256:111..."},
{"position": "right", "hash": "sha256:222..."},
{"position": "left", "hash": "sha256:333..."}
]
}
}
¶
Agent A Filter Agent B Intent
Registry
| | | |
| Produce | | |
| output | | |
|---------------+---------------+---------->|
| | | Append |
| | | non-det |
| | | entry |
| | | |
| Send to | | |
| filter | | |
|-------------->| | |
| | | |
| | Transform | |
| | content | |
| |---------------+---------->|
| | | Append |
| | | filter |
| | | entry |
| | | |
| | Send to | |
| | Agent B | |
| |-------------->| |
| | | |
| | | Produce |
| | | output |
| | |---------->|
| | | Append |
| | | non-det |
| | | entry |
| | | |
¶
Agent B AS Intent Actor
Registry Registry
| | | |
| Token | | |
| Exchange | | |
| Request | | |
|-------------->| | |
| | | |
| | Validate | |
| | existing | |
| | actor_chain| |
| | | |
| | Extend | |
| | actor_chain| |
| |------------+----------->|
| | | Store |
| | | capability |
| | | |
| | Compute | |
| | intent_root| |
| |----------->| |
| | Get Merkle | |
| | root | |
| |<-----------| |
| | | |
| New token | | |
| with both | | |
| chains | | |
|<--------------| | |
| | | |
¶
Agent C Relying Intent Actor
Party Registry Registry
| | | |
| Request + | | |
| Token | | |
|-------------->| | |
| | | |
| | Verify JWT | |
| | signature | |
| | | |
| | Verify | |
| | actor_chain| |
| | (per mode) | |
| | | |
| | Check | |
| | intent_root| |
| | present | |
| | | |
| | Apply | |
| | policy on | |
| | entry types| |
| |----------->| |
| | Fetch entry| |
| | types only | |
| |<-----------| |
| | | |
| | Policy OK: | |
| | Execute | |
| | | |
| Response | | |
|<--------------| | |
| | | |
¶
Every agent output must be followed by at least one filter:¶
require_filtered_outputs {
intent_chain := get_intent_chain(input.intent_root)
agent_outputs := [i |
intent_chain[i].type == "non_deterministic"]
every i in agent_outputs {
# Next entry must be a filter (if not last)
i < count(intent_chain) - 1
intent_chain[i + 1].type != "non_deterministic"
}
}
¶
AI agent outputs must pass through an AI guardrail:¶
require_ai_guardrail {
intent_chain := get_intent_chain(input.intent_root)
every i, entry in intent_chain {
entry.type == "non_deterministic" implies {
# Must be followed by an entry with AI guardrail model
some j
j > i
intent_chain[j].model_info.model ==
"llama-guard-3"
}
}
}
¶
Sensitive fields must be sanitized:¶
require_pii_redaction {
intent_chain := get_intent_chain(input.intent_root)
some i
intent_chain[i].type == "deterministic"
intent_chain[i].rule_id == "pii-redaction-v1"
}
¶
The intent chain claims are designed for consumption by policy engines such as Open Policy Agent (OPA). A policy engine SHOULD:¶
intent_root and intent_registry are present and non-empty.¶
| Threat | Mitigation | Mechanism |
|---|---|---|
| Spoofing | Cryptographic identity | Actor chain, SPIFFE IDs |
| Tampering | Merkle tree integrity | Append-only logs, Merkle root in JWT |
| Repudiation | Signed outputs |
intent_sig on agent and filter entries |
| Information Disclosure | Selective disclosure | SD-JWT, content hashes not content |
| Denial of Service | Session lifecycle | Expiry, rate limits |
| Elevation of Privilege | Scope attenuation | Actor chain, policy enforcement |
| Entry Type | Required Signatures | Rationale |
|---|---|---|
non_deterministic
|
intent_sig over intent_digest (REQUIRED) |
Non-reproducible; signs input_hash + output_hash to prove what was received and produced |
deterministic
|
intent_sig over intent_digest (REQUIRED) |
Reproducible; signs input_hash + output_hash. Type-specific fields (rule_id, rule_hash) enable independent re-verification |
Each intent chain entry includes an iat (issued-at) timestamp. The session-scoped partitioning of the intent registry prevents cross-session replay. Additionally, the Merkle root is bound to the JWT via the intent_root claim, and the JWT itself carries exp and jti claims, providing token-level replay protection.¶
The Merkle tree structure provides tamper evidence for the intent chain. Any modification to an entry changes its leaf hash, which propagates up the tree, changing the Merkle root. Since the Merkle root is included in the signed JWT, any tampering with the intent chain entries is detectable.¶
The append-only property of the intent registry provides additional protection: entries cannot be deleted or modified after creation.¶
Intent registry entries MUST NOT contain OAuth tokens, bearer credentials, or signing keys. The relationship between tokens and registry entries is one-directional: the token references the registry via the intent_registry URI claim, but the registry MUST NOT store or reference the token. This separation ensures that compromise of the intent registry does not expose bearer credentials that could be used for unauthorized access.¶
The intent chain contains content hashes rather than actual content. This provides provenance without exposing the content itself. When combined with SD-JWT {{!I-D.ietf-oauth-selective-disclosure-jwt}}, individual intent chain entries can be selectively disclosed to different verifiers.¶
The intent chain stores SHA-256 hashes of content, not the content itself. This design choice provides:¶
Pre-image resistance caveat: SHA-256 is pre-image resistant for arbitrary-length inputs, but short, low-entropy content (e.g., a 16-digit account number, a boolean flag, or a short enumerated value) may be vulnerable to brute-force guessing. An attacker who knows the hash and the input domain can enumerate all possible inputs and find the one that matches. Deployments handling short, structured content SHOULD salt content before hashing or use SD-JWT to selectively redact content hashes from specific verifiers.¶
The intent registry stores immutable intent chain entries. Recommended properties:¶
session.session_id for isolation¶
A federated IAM/IdM platform (e.g., Keycloak, Microsoft Entra, Okta, PingFederate) MAY host the intent registry alongside the Actor Chain Registry ({{!I-D.draft-mw-spice-actor-chain}}), since the Authorization Server already mediates token exchanges and can append intent chain entries as a side-effect. Most enterprise IAM/IdM platforms support configurable data stores that can be configured for append-only semantics — see {{!I-D.draft-mw-spice-actor-chain}} Section "Registry Hosting" for detailed requirements.¶
In deployments involving multiple Authorization Servers (e.g., federated enterprise environments where different ASes serve different organizational domains), the intent registry is shared across all participating ASes. Each AS appends intent chain entries to the same session-partitioned registry, identified by the sid claim carried in the token. This works without coordination between ASes because:¶
sid value is established at session initiation and carried forward unchanged through all token exchanges.¶
sid partition.¶
intent_root in the token therefore differs at each hop — each successive AS produces a larger Merkle root reflecting the growing chain. This is expected behavior: a growing root is the normal consequence of an append-only chain and indicates that additional intent entries have been recorded.¶
This enables cross-domain content provenance tracking without requiring ASes to share keys or coordinate directly — the session partition and append-only log semantics provide the necessary consistency.¶
Intent registry unavailability does not affect data-plane operation — the token's AS-signed intent_root is sufficient for request-time policy decisions (e.g., "intent chain coverage required"). Per-entry forensic verification is deferred to the audit plane and is not required on the hot path.¶
However, if the registry is permanently lost, forensic verification becomes impossible. Deployments SHOULD:¶
intent_root) separately from intent chain entries, so that Merkle root commitments survive independently of the registry.¶
The intent chain uses a Merkle root in the token rather than embedding the full chain inline. The following table summarizes the trade-offs:¶
| Approach | Token Size | Verification | Privacy | Selective Verify |
|---|---|---|---|---|
| A. Full chain in token | O(n) — grows per entry | Inline, zero latency | Poor — all entries exposed | All-or-nothing |
| B. Merkle root in token | O(1) — ~64 bytes | O(log n) per entry | Good — selective disclosure | Single-entry proofs |
| C. Simple hash of chain | O(1) — ~64 bytes | O(n) — must rehash all | Good — external storage | Must verify all |
| D. No provenance in token | Zero overhead | External lookup | Best — nothing in token | Any pattern |
Approach B is chosen because intent chains can contain 20-50+ entries, making inline embedding impractical for data-plane proxies. The Merkle tree enables O(log n) selective verification of individual entries and provides cryptographic binding between the token and the registry. The actor chain ({{!I-D.draft-mw-spice-actor-chain}}) uses approach A because delegation chains are small (typically 3-5 entries) and every Relying Party needs the full delegation path.¶
When auditing actor and intent chains together, the auditor performs cross-chain binding checks:¶
For each intent chain entry of type non_deterministic: verify that entry.sub appears in actor_chain. Verify that entry.iat falls within the actor's active window. A mismatch indicates an unregistered agent produced content.¶
Full two-chain audit is RECOMMENDED for regulatory submissions, dispute resolution, and post-breach forensic analysis.¶
This document requests registration of the following claims in the "JSON Web Token Claims" registry established by {{!RFC7519}}:¶
intent_root¶
Specification Document(s): [this document]¶
Claim Name: intent_alg¶
Claim Description: Hash algorithm used for intent chain Merkle tree construction.¶
Change Controller: IETF¶
Specification Document(s): [this document]¶
Claim Name: intent_registry¶
Claim Description: URI of the intent registry for proof retrieval.¶
Change Controller: IETF¶
Specification Document(s): [this document]¶
This document requests registration of the following claims in the "CBOR Web Token (CWT) Claims" registry established by {{!RFC8392}}:¶
intent_root¶
Specification Document(s): [this document]¶
Claim Name: intent_registry¶
Claim Description: URI of the intent registry for proof retrieval.¶
CBOR Key: TBD (e.g., 51)¶
Claim Type: tstr¶
Change Controller: IETF¶
Specification Document(s): [this document]¶
Claim Name: intent_alg¶
Claim Description: Hash algorithm used for intent chain Merkle tree construction.¶
CBOR Key: TBD (e.g., 52)¶
Claim Type: tstr¶
Change Controller: IETF¶
Specification Document(s): [this document]¶
intent_root (in JWT)
|
+---------+---------+
| |
Hash(0-2) Hash(3-5)
| |
+-------+-------+ +-------+-------+
| | | |
Hash(0-1) Hash(2) Hash(3-4) Hash(5)
| | | |
+---+---+ | +---+---+ |
| | | | | |
Entry0 Entry1 Entry2 Entry3 Entry4 Entry5
(non-det)(non-det) (det) (non-det)(det) (non-det)
¶
def compute_merkle_root(entries):
if len(entries) == 0:
return None
# Compute leaf hashes
hashes = [sha256(canonical_json(entry)) for entry in entries]
# Build tree bottom-up
while len(hashes) > 1:
next_level = []
for i in range(0, len(hashes), 2):
if i + 1 < len(hashes):
combined = sha256(hashes[i] + hashes[i+1])
else:
combined = hashes[i] # Odd node promoted
next_level.append(combined)
hashes = next_level
return hashes[0]
¶
The following example shows a complete token with session, actor chain, and intent chain:¶
{
"iss": "https://auth.example.com",
"sub": "user-alice",
"aud": "https://api.example.com",
"jti": "tok-ddd-67890",
"iat": 1700000000,
"exp": 1700003600,
"session": {
"session_id": "sess-uuid-12345",
"type": "human_initiated",
"initiator": "user-alice",
"approval_ref": "approval-uuid-789",
"max_chain_depth": 5
},
"actor_chain": [
{
"sub": "spiffe://example.com/agent/orchestrator",
"iss": "https://auth.example.com",
"iat": 1700000010,
"scope": "ticket:*",
"chain_digest": "sha256:aaa...",
"chain_sig": "eyJhbGciOiJFUzI1NiIs..."
},
{
"sub": "spiffe://example.com/agent/support",
"iss": "https://auth.example.com",
"iat": 1700000030,
"scope": "ticket:create",
"chain_digest": "sha256:bbb...",
"chain_sig": "eyJhbGciOiJFUzI1NiIs..."
}
],
"intent_root": "sha256:abc123def456789...",
"intent_registry":
"https://intent-log.example.com/sessions/sess-uuid-12345"
}
¶
The following entries would be stored in the intent registry for the above token:¶
[
{
"offset": 0,
"entry": {
"type": "non_deterministic",
"sub":
"spiffe://example.com/agent/orchestrator",
"input_hash": "sha256:prompt...",
"output_hash": "sha256:abc...",
"iat": 1700000010,
"intent_digest": "sha256:leaf0...",
"intent_sig": "eyJ..."
}
},
{
"offset": 1,
"entry": {
"type": "non_deterministic",
"sub":
"spiffe://example.com/filter/ai-guardrail",
"filter_version": "v2.1",
"input_hash": "sha256:abc...",
"output_hash": "sha256:def...",
"model_info": {
"model": "llama-guard-3",
"categories": ["violence", "pii"]
},
"iat": 1700000012,
"intent_digest": "sha256:leaf1...",
"intent_sig": "eyJ..."
}
},
{
"offset": 2,
"entry": {
"type": "deterministic",
"sub":
"spiffe://example.com/filter/schema-validator",
"filter_version": "v1.0",
"input_hash": "sha256:def...",
"output_hash": "sha256:ghi...",
"rule_id": "ticket-schema-v2",
"rule_hash": "sha256:rrr...",
"reproducible": true,
"iat": 1700000013,
"intent_digest": "sha256:leaf2...",
"intent_sig": "eyJ..."
}
},
{
"offset": 3,
"entry": {
"type": "non_deterministic",
"sub":
"spiffe://example.com/agent/support",
"input_hash": "sha256:ghi...",
"output_hash": "sha256:jkl...",
"iat": 1700000030,
"intent_digest": "sha256:leaf3...",
"intent_sig": "eyJ..."
}
},
{
"offset": 4,
"entry": {
"type": "deterministic",
"sub":
"spiffe://example.com/filter/pii-redactor",
"filter_version": "v1.2",
"input_hash": "sha256:jkl...",
"output_hash": "sha256:mno...",
"rule_id": "pii-redaction-v1",
"rule_hash": "sha256:ppp...",
"reproducible": true,
"iat": 1700000031,
"intent_digest": "sha256:leaf4...",
"intent_sig": "eyJ..."
}
},
{
"offset": 5,
"entry": {
"type": "non_deterministic",
"sub":
"spiffe://example.com/agent/tool-executor",
"input_hash": "sha256:mno...",
"output_hash": "sha256:pqr...",
"iat": 1700000050,
"intent_digest": "sha256:leaf5...",
"intent_sig": "eyJ..."
}
}
]
¶