<?xml version="1.0" encoding="utf-8"?>
<!-- name="GENERATOR" content="github.com/mmarkdown/mmark Mmark Markdown Processor - mmark.miek.nl" -->
<rfc version="3" ipr="trust200902" docName="draft-sharma-oauth-identity-propagation-context-01" submissionType="IETF" category="exp" xml:lang="en" xmlns:xi="http://www.w3.org/2001/XInclude" indexInclude="true">

<front>
<title abbrev="Identity Propagation Context">Identity Propagation Context for Multi-Hop Delegation in OAuth 2.0 Environments</title><seriesInfo value="draft-sharma-oauth-identity-propagation-context-01" stream="IETF" status="experimental" name="Internet-Draft"></seriesInfo>
<author initials="R.K." surname="Sharma" fullname="Ravi Kant Sharma"><organization>Ericsson</organization><address><postal><street></street>
<country>Ireland</country>
</postal><email>ravi.k.kant.sharma@ericsson.com</email>
</address></author><date/>
<area>Applications and Real-Time</area>
<workgroup>Individual Submission</workgroup>
<keyword>OAuth</keyword>
<keyword>identity</keyword>
<keyword>delegation</keyword>
<keyword>multi-hop</keyword>
<keyword>agent</keyword>

<abstract>
<t>This specification defines the Identity Propagation Context (IPC), a signed,
short-lived context artifact that carries identity and delegation chain
information across multi-hop service chains with per-hop cryptographic
re-signing. IPC complements the delegation semantics of OAuth 2.0 Token
Exchange (RFC 8693) by enabling each trust boundary to independently verify
the upstream signature and re-sign the identity context it forwards,
supporting cascade revocation, delegation depth limits, and cross-protocol
propagation (HTTP, gRPC, and event-streaming systems).</t>
<t>Unlike the <tt>act</tt> claim in RFC 8693, which is explicitly designated as
&quot;informational only and not to be considered in access control decisions,&quot; IPC
is designed for identity propagation that can be used in access control
decisions, with per-hop cryptographic re-signing within a single trust
domain. In autonomous agent architectures and multi-service platforms,
Each trust boundary verifies the upstream signature and re-signs with its
own key before forwarding; the delegation chain content is asserted by the
IPC Creator and trusted transitively through the chain of re-signing
intermediaries. This provides single-hop-verifiable integrity (each service
can verify its immediate upstream) rather than end-to-end cryptographic
proof of the full chain.</t>
</abstract>
</front>

<middle>

<section anchor="introduction"><name>Introduction</name>

<section anchor="problem-statement"><name>Problem Statement</name>
<t>Modern cloud-native platforms serve multiple classes of autonomous entities -
AI agents, applications, and sub-agents - that initiate operations traversing
complex service chains. A fundamental problem remains unsolved by existing
standards:</t>
<t>How do you propagate the identity of an entity - including its full delegation
chain back to an accountable human - from the initiating service to the final
enforcement point, across multiple trust boundaries and protocols, in a manner
that is per-hop verified, enforceable, revocable, and depth-limited?</t>
<t>The authentication layer is largely solved: platforms can assign cryptographic
identities to agents (e.g., SPIFFE-based identities with mTLS and DPoP
credential binding) and establish single-hop delegation (e.g., OAuth 2.0 Token
Exchange). What remains unsolved is carrying the full delegation context -
who is the original human, who authorized this agent, what is the complete
chain of delegation - through multi-hop internal service calls where each
intermediate service independently verifies the context before forwarding it.</t>
<t>This problem has been independently formalized as &quot;authorization propagation&quot;
in the academic literature [Tallam2026].</t>
<t>Existing mechanisms fall short:</t>

<ul spacing="compact">
<li><strong>OAuth 2.0 Bearer Tokens (RFC 6750):</strong> Validated at the first hop only, not
propagated with delegation context.</li>
<li><strong>OAuth 2.0 Token Exchange (RFC 8693):</strong> Supports nested <tt>act</tt> claims but
explicitly limits them to &quot;informational only&quot; for access control decisions.
Requires STS interaction to extend the delegation chain at each new
delegation hop (e.g., when an intermediate agent sub-delegates). The
resulting token with nested <tt>act</tt> claims can be forwarded downstream,
but intermediate services cannot independently attest their verification
without producing a new token via the STS.</li>
<li><strong>Agent Identity Protocol (AIP) [I-D.draft-singla-agent-identity-protocol]:</strong>
Defines decentralized agent identity with delegation chains, but requires the
agent to self-issue Credential Tokens. Does not address platform-transparent
propagation through service infrastructure where the agent is unaware of
downstream services. HTTP-only; no gRPC or event-streaming bindings.</li>
<li><strong>WIMSE AI Agent Identity [I-D.draft-ni-wimse-ai-agent-identity]:</strong> Defines
agent identity bootstrapping and attestation via a proxy architecture, but
does not specify how verified identity propagates through multi-hop service
chains after initial credential issuance.</li>
<li><strong>OTEL Baggage (W3C):</strong> Unsigned observability context - not suitable for
enforcement.</li>
<li><strong>Transaction Tokens [I-D.draft-ietf-oauth-transaction-tokens]:</strong> Carry
immutable authorization context (user identity, scope, transaction details)
through a call chain within a Trust Domain. However, Txn-Tokens are passed
unchanged through the chain — they do not support per-hop re-signing,
do not carry a mutable delegation chain that grows with sub-delegation,
and do not provide per-hop verification that each intermediate validated
the context. IPC and Txn-Tokens are complementary: Txn-Tokens for
authorization context, IPC for delegation chain provenance.</li>
<li><strong>mTLS / SPIFFE:</strong> Identifies the transport endpoint, not the originating
entity or its delegation chain. Production platforms (e.g., Google Cloud
Agent Identity) assign per-agent SPIFFE identities and use mTLS + DPoP
for credential binding, yet still lack a mechanism to propagate the full
delegation chain (human → agent → sub-agent) through multi-hop internal
service calls. SPIFFE proves &quot;this workload is Service-A&quot;; it does not
prove &quot;this request originated from operator@example.com who delegated
to energy-optimizer who delegated to cell-configurator.&quot;</li>
<li><strong>AI Agent Authentication Frameworks
[I-D.draft-klrc-aiagent-auth]:</strong> Composes SPIFFE, WIMSE, OAuth, and
OIDC into a unified agent authentication architecture, but focuses on
credential issuance and initial authentication rather than per-hop
identity propagation through service chains after the agent
authenticates.</li>
</ul>
</section>

<section anchor="goals"><name>Goals</name>
<t>This specification defines:</t>

<ol spacing="compact">
<li>A context format (IPC) that carries delegation chain information with per-hop cryptographic re-signing.</li>
<li>Re-signing semantics at trust boundaries.</li>
<li>Delegation depth enforcement rules.</li>
<li>Expiry attenuation (child &lt;= parent).</li>
<li>Cascade revocation semantics.</li>
<li>Protocol bindings for HTTP, gRPC, and event-streaming (e.g., Kafka).</li>
<li>A trust model for IPC issuance, verification, and re-signing.</li>
</ol>
</section>

<section anchor="relationship-to-rfc-8693"><name>Relationship to RFC 8693</name>
<t>IPC is complementary to RFC 8693. Token Exchange may be used at the first hop
to establish the initial delegation relationship. IPC then carries the
verified identity context through the remaining multi-hop chain without
requiring further STS interaction.</t>
</section>

<section anchor="terminology"><name>Terminology</name>

<ul spacing="compact">
<li><strong>IPC:</strong> Identity Propagation Context - a signed context artifact carrying delegation
chain information.</li>
<li><strong>Issuer:</strong> The service that creates or re-signs an IPC at a trust boundary.</li>
<li><strong>Entity:</strong> Any principal (human, agent, application, or system) that
initiates or participates in a delegation chain.</li>
<li><strong>Delegation Chain:</strong> The ordered sequence of entities from the accountable
root (typically human) to the current executing entity.</li>
<li><strong>Trust Boundary:</strong> A point in the service chain where IPC is verified and
re-signed by a new issuer.</li>
<li><strong>Enforcement Point:</strong> A service that reads the IPC and makes authorization
decisions based on its contents.</li>
<li><strong>STS:</strong> Security Token Service - a trusted service that issues delegation
tokens establishing delegation relationships between entities. The STS
authenticates the delegating party and produces a delegation-token that
the IPC Creator consumes during IPC construction (Section 4.2).</li>
</ul>
</section>

<section anchor="requirements-language"><name>Requirements Language</name>
<t>The key words &quot;MUST&quot;, &quot;MUST NOT&quot;, &quot;REQUIRED&quot;, &quot;SHALL&quot;, &quot;SHALL NOT&quot;, &quot;SHOULD&quot;,
&quot;SHOULD NOT&quot;, &quot;RECOMMENDED&quot;, &quot;NOT RECOMMENDED&quot;, &quot;MAY&quot;, and &quot;OPTIONAL&quot; 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.</t>
</section>

<section anchor="scope-of-applicability"><name>Scope of Applicability</name>
<t>IPC is designed for identity propagation in multi-hop service chains within a
single trust domain or platform. It is NOT suitable for:</t>

<ul spacing="compact">
<li>Cross-organization federation (use RFC 8693 + trust frameworks)</li>
<li>User-to-application consent flows (use OAuth 2.0 authorization code flow)</li>
<li>General-purpose session management (use session cookies or OAuth refresh tokens)</li>
<li>Replacing OAuth 2.0 bearer tokens for authentication (IPC travels alongside,
not replacing, the bearer token)</li>
<li>Dynamic topologies where the terminal enforcement point is unknown at
IPC creation time (the IPC Creator must know the target audience)</li>
</ul>
</section>

<section anchor="coexistence-with-oauth-2-0-bearer-tokens"><name>Coexistence with OAuth 2.0 Bearer Tokens</name>
<t>IPC is carried alongside the standard OAuth 2.0 bearer token in requests. The
bearer token handles authentication (proving the caller has valid credentials).
IPC handles identity propagation (carrying the full delegation chain for
enforcement decisions). Both MAY be present in the same request.</t>
</section>

<section anchor="multi-vendor-platform-interoperability"><name>Multi-Vendor Platform Interoperability</name>
<t>A standardized IPC format and verification algorithm enables platform
operators to compose service chains from components provided by multiple
vendors without requiring proprietary identity propagation mechanisms.
Any service that implements this specification can participate in an IPC
chain regardless of its vendor origin, provided it operates within the
platform's trust domain and has access to the upstream issuer's public key.</t>
<t>This is distinct from cross-organization federation: the platform operator
controls the trust domain, manages key distribution, and defines enforcement
policy. The standardization benefit is that vendors do not need bilateral
integration for identity propagation - they implement one specification.</t>
<t>Implementations claiming multi-vendor interoperability conformance
MUST support key discovery via Section 3.6. IPC Creators and Trust
Boundaries MUST support key discovery as both key consumers (querying
upstream endpoints) and key publishers (exposing own keys) per the
conformance requirements in Section 11.3. Enforcement Points that only
verify (and never re-sign) MUST support key discovery as key consumers.
Static key configuration alone is insufficient for multi-vendor claims.</t>
</section>
</section>

<section anchor="ipc-format"><name>IPC Format</name>

<section anchor="claims"><name>Claims</name>
<t>IPC intentionally carries <strong>identity and delegation context</strong>, not authorization
grants or permissions. Authorization decisions are made by policy engines (e.g.,
OpenFGA, OPA, Cedar) that consume IPC identity as input. This separation ensures
that IPC remains a general-purpose propagation mechanism independent of any
particular authorization model.</t>
<t>IPC uses distinct claim names (<tt>entityId</tt>, <tt>issuer</tt>) rather than JWT registered
names (<tt>sub</tt>, <tt>iss</tt>) to avoid semantic confusion when IPCs coexist with
JWTs in the same request context, and because IPC's <tt>issuer</tt> semantics differ
from JWT's <tt>iss</tt> - in IPC, the issuer changes at every re-signing hop.</t>
<t>The IPC is a signed JSON object with the following claims:</t>
<table>
<thead>
<tr>
<th>Claim</th>
<th>Type</th>
<th>Required</th>
<th>Description</th>
</tr>
</thead>

<tbody>
<tr>
<td><tt>version</tt></td>
<td>string</td>
<td>REQUIRED</td>
<td>IPC specification version (e.g., &quot;1.0&quot;)</td>
</tr>

<tr>
<td><tt>entityId</tt></td>
<td>string</td>
<td>REQUIRED</td>
<td>Identifier of the entity currently executing</td>
</tr>

<tr>
<td><tt>entityType</tt></td>
<td>string</td>
<td>REQUIRED</td>
<td>AUTONOMOUS, SUPERVISED, UTILITY, HUMAN, or SYSTEM</td>
</tr>

<tr>
<td><tt>authorizedBy</tt></td>
<td>string</td>
<td>REQUIRED</td>
<td>Identifier of the immediate delegating party</td>
</tr>

<tr>
<td><tt>delegationDepth</tt></td>
<td>integer</td>
<td>REQUIRED</td>
<td>Number of delegation hops from root. MUST equal <tt>delegationChain.length - 1</tt></td>
</tr>

<tr>
<td><tt>delegationChain</tt></td>
<td>array</td>
<td>REQUIRED</td>
<td>Ordered list of entity identifiers from root to current</td>
</tr>

<tr>
<td><tt>audience</tt></td>
<td>string</td>
<td>REQUIRED (Level 2+), OPTIONAL (Level 1)</td>
<td>Intended <strong>terminal</strong> enforcement point service identifier. Verified only by the final service in the chain (not intermediate trust boundaries). Prevents cross-service replay attacks.</td>
</tr>

<tr>
<td><tt>sessionId</tt></td>
<td>string</td>
<td>REQUIRED</td>
<td>Unique identifier for this IPC instance (UUID v7 per RFC 9562 RECOMMENDED). Deployments MAY correlate sessionId with W3C Trace Context through a deployment-internal mapping. Using the same value for both sessionId and externally-visible trace-id is NOT RECOMMENDED when trace context propagates beyond the trust domain boundary, as it enables correlation of identity chains via observability infrastructure.</td>
</tr>

<tr>
<td><tt>issuedAt</tt></td>
<td>string</td>
<td>REQUIRED</td>
<td>ISO 8601 timestamp (MUST be UTC, trailing &quot;Z&quot;, no fractional seconds). Format: e.g., 2026-07-17T08:00:00Z (exactly 20 characters)</td>
</tr>

<tr>
<td><tt>expiresAt</tt></td>
<td>string</td>
<td>REQUIRED</td>
<td>ISO 8601 timestamp (MUST be UTC, trailing &quot;Z&quot;, no fractional seconds). Format: e.g., 2026-07-17T08:00:00Z (exactly 20 characters)</td>
</tr>

<tr>
<td><tt>issuer</tt></td>
<td>string</td>
<td>REQUIRED</td>
<td>Identifier of the service that signed this IPC</td>
</tr>

<tr>
<td><tt>trustLevel</tt></td>
<td>integer</td>
<td>REQUIRED</td>
<td>1 (unsigned), 2 (signed), or 3 (reserved for future use)</td>
</tr>

<tr>
<td><tt>alg</tt></td>
<td>string</td>
<td>REQUIRED</td>
<td>Signing algorithm identifier from the IANA JOSE Algorithms registry: &quot;EdDSA&quot; (default for multi-hop chains; MUST use curve Ed25519 per RFC 8037), &quot;ES256&quot; (FIPS-compatible alternative for multi-hop chains; ECDSA with P-256 per RFC 7518 Section 3.4; see Section 2.6.1), &quot;HS256&quot; (single-hop only; MUST NOT be used when the IPC will cross more than one trust boundary), or &quot;none&quot; (Trust Level 1 only). Deployments MUST choose either EdDSA or ES256 for their asymmetric algorithm; mixing within a chain is prohibited.</td>
</tr>

<tr>
<td><tt>kid</tt></td>
<td>string</td>
<td>REQUIRED (Level 2+)</td>
<td>Key identifier of the signing key. Enables key selection during rotation. The kid format is deployment-specific. The RECOMMENDED format is <tt>&lt;issuer&gt;/&lt;key-version&gt;</tt> (e.g., <tt>api-gateway/key-1</tt>).</td>
</tr>

<tr>
<td><tt>signature</tt></td>
<td>string</td>
<td>REQUIRED (Level 2+)</td>
<td>Cryptographic signature over all other claims</td>
</tr>
</tbody>
</table><t>The <tt>entityType</tt> values are defined as follows:</t>
<table>
<thead>
<tr>
<th>Type</th>
<th>Definition</th>
</tr>
</thead>

<tbody>
<tr>
<td><tt>HUMAN</tt></td>
<td>A natural person authenticated via an identity provider</td>
</tr>

<tr>
<td><tt>SYSTEM</tt></td>
<td>Platform-initiated operation with no direct human trigger</td>
</tr>

<tr>
<td><tt>AUTONOMOUS</tt></td>
<td>Agent operating independently on behalf of a human principal</td>
</tr>

<tr>
<td><tt>SUPERVISED</tt></td>
<td>Agent requiring human approval for sensitive operations</td>
</tr>

<tr>
<td><tt>UTILITY</tt></td>
<td>Stateless service component invoked by other entities</td>
</tr>
</tbody>
</table><t>The <tt>entityType</tt> is assigned at IPC creation time based on the authenticated
entity's registration in the platform. It MUST NOT change during re-signing.</t>
<t>The <tt>entityType</tt> values are case-sensitive. Verifiers MUST perform exact
string matching against the defined values (AUTONOMOUS, SUPERVISED,
UTILITY, HUMAN, SYSTEM).</t>
<t>IPC uses ISO 8601 UTC strings rather than numeric epoch seconds (as used
in JWT) for timestamps. This enables human-readable inspection of IPC
tokens in logs, debugging tools, and wire captures without requiring
numeric-to-date conversion.</t>
<t>Timestamps MUST NOT include fractional seconds. The canonical format is
exactly <tt>YYYY-MM-DDTHH:MM:SSZ</tt> (20 characters). This ensures JCS
canonicalization produces identical bytes regardless of implementation,
preventing signature verification failures during re-signing.</t>
<t>Implementations SHOULD parse ISO 8601 timestamps to integer epoch
representation once upon receipt and perform all subsequent comparisons
as integer arithmetic. This avoids repeated string parsing at each
verification step.</t>
</section>

<section anchor="validation-rules"><name>Validation Rules</name>

<ul spacing="compact">
<li><tt>delegationDepth</tt> MUST equal <tt>delegationChain.length - 1</tt>. If mismatched,
the IPC MUST be rejected.</li>
</ul>
<t>Rationale for carrying both: <tt>delegationDepth</tt> enables fast policy rejection
  (single integer comparison against <tt>maxDepth</tt>) without requiring the verifier
  to parse and compute the length of the <tt>delegationChain</tt> array. The redundancy
  also serves as an integrity check - a mismatch indicates tampering or
  corruption.
- <tt>authorizedBy</tt> MUST equal <tt>delegationChain[delegationChain.length - 2]</tt>
  when the chain has two or more entries. When <tt>delegationChain</tt> has
  exactly one entry (no delegation), <tt>authorizedBy</tt> MUST equal <tt>entityId</tt>.
  If mismatched, the IPC MUST be rejected.</t>
<t>Rationale for carrying <tt>authorizedBy</tt> separately: it enables fast
  authorization policy lookups (&quot;who authorized this entity?&quot;) without
  requiring the verifier to parse and index the <tt>delegationChain</tt> array.
  The redundancy also serves as an integrity check.</t>
<t>The <tt>delegationChain</tt> carries identifiers only, not entity types.
Enforcement points that require type information for intermediate
chain entries MUST resolve types through deployment-specific policy
or a principal registry.</t>

<ul spacing="compact">
<li>If <tt>audience</tt> is present, it identifies the <strong>final enforcement point</strong> in the chain, not each intermediate hop. Trust boundary services that re-sign and forward the IPC MUST NOT verify <tt>audience</tt> against their own service identifier - they are intermediaries, not the intended audience. Only the terminal enforcement point (the service that makes the final authorization decision and does not re-sign) MUST verify that its own service identifier matches the <tt>audience</tt> value. If it does not match, the IPC MUST be rejected.</li>
</ul>
<t>For Trust Level 2+ IPCs, <tt>audience</tt> MUST be a non-empty string
identifying the terminal enforcement point. An empty string value
for <tt>audience</tt> in a Trust Level 2+ IPC MUST be rejected at creation
time. The verification algorithm's empty-audience check (Section 3.2
Step 9f) rejects empty audience for Trust Level 2+, while Step 12
verifies audience matching only at terminal enforcement points.
  In fan-out topologies where an intermediate service forwards to multiple
  terminal services, separate IPCs MUST be created for each terminal path
  (each with its own <tt>audience</tt> value). Fan-out IPC creation MUST NOT be
  performed on incoming HS256-signed IPCs. If the incoming IPC has <tt>alg</tt>
  of &quot;HS256&quot;, the service MUST act as a terminal enforcement point only
  and MUST NOT create fan-out IPCs. HS256-signed IPCs cannot be re-signed
  or forwarded (Section 3.3) — they are terminal by definition.
  The preserved <tt>alg</tt> will therefore always be &quot;EdDSA&quot; in fan-out scenarios.
  In this scenario, the intermediate
  service acts as both a Trust Boundary (verifying the incoming IPC) and an
  IPC Creator (issuing new IPCs for each downstream path). The new IPCs
  MUST preserve the original <tt>delegationChain</tt>, <tt>entityId</tt>, <tt>entityType</tt>,
  <tt>authorizedBy</tt>, <tt>delegationDepth</tt>, <tt>version</tt>, <tt>alg</tt>, and <tt>trustLevel</tt>
  from the verified incoming IPC. Extension claims (Section 2.2) present
  in the incoming IPC MUST also be preserved in fan-out IPCs. Fan-out
  creation is semantically equivalent to re-signing with respect to
  claim preservation: only <tt>issuedAt</tt>, <tt>expiresAt</tt>, <tt>audience</tt>,
  <tt>sessionId</tt>, <tt>issuer</tt>, <tt>kid</tt>, and <tt>signature</tt> differ in each fan-out
  IPC.
  The <tt>issuedAt</tt> of each fan-out IPC MUST be set to the current time
  (the time of fan-out creation), NOT preserved from the incoming IPC.
  The <tt>expiresAt</tt> MUST be set to <tt>min(now() + config.maxTTL, original.expiresAt)</tt>
  to maintain expiry attenuation (the fan-out IPC cannot outlive the
  original). The <tt>audience</tt>, <tt>sessionId</tt>, <tt>issuer</tt>, <tt>kid</tt>, and <tt>signature</tt>
  differ in each fan-out IPC. Each fan-out IPC MUST have a unique
  <tt>sessionId</tt> to avoid replay cache conflicts at shared downstream
  infrastructure.</t>
<t>Note on replay cache interaction: If the fan-out service is also an
  enforcement point (dual-role per Section 11.3), it adds the original
  incoming IPC's <tt>sessionId</tt> to its own replay cache upon verification.
  Each fan-out IPC carries a new, independent <tt>sessionId</tt> that is
  consumed by downstream enforcement points' replay caches. The original
  IPC's replay detection and each downstream path's replay detection
  operate independently.</t>
<t>Fan-out security constraints:
  - The fan-out service MUST be configured with an explicit allowlist
    of downstream <tt>audience</tt> values for which it may create IPCs.
    Fan-out IPC creation for audience values not in the allowlist
    MUST be rejected.
  - The fan-out service MUST enforce a maximum fan-out count per
    incoming IPC (RECOMMENDED default: 10). If the number of
    downstream paths exceeds this limit, the service MUST reject
    the fan-out request.
  - Fan-out IPC creation MUST NOT be performed when the incoming
    IPC was accepted in historical replay mode (Section 6.6) where
    temporal validity checks were skipped. Creating fresh IPCs from
    expired historical records would circumvent expiry attenuation.</t>
<t>Rationale for regenerating <tt>issuedAt</tt>: If the original <tt>issuedAt</tt> were
  preserved, the <tt>maxTokenAge</tt> check (Step 8f) would measure age from the
  original creation time. In fan-out topologies with processing delays, this
  could cause fan-out IPCs to fail <tt>maxTokenAge</tt> checks at downstream
  services despite being freshly created. Regenerating <tt>issuedAt</tt> gives
  each fan-out IPC a fresh lifetime budget while <tt>expiresAt</tt> attenuation
  ensures it cannot outlive the original authority.</t>
<t>A single IPC cannot target
  multiple terminal enforcement points.
- The first entry in <tt>delegationChain</tt> MUST be a human or system principal
  (determined by deployment policy).
- A verifier that receives an IPC with a major version it does not
  implement MUST reject it. Within the same major version (e.g., &quot;1.x&quot;),
  a verifier implementing version &quot;1.0&quot; MUST accept tokens with version
  &quot;1.0&quot; or any later minor version (e.g., &quot;1.1&quot;, &quot;1.2&quot;), ignoring
  unrecognized claims per the extension mechanism above. This enables
  rolling upgrades without fleet-wide flag-day deployments.</t>
<t>Minor version increments (e.g., &quot;1.0&quot; to &quot;1.1&quot;) MUST only add OPTIONAL
claims. A minor version MUST NOT promote an extension claim to REQUIRED
status, MUST NOT change the semantics of existing claims, and MUST NOT
introduce new security-critical properties that earlier minor versions
would silently ignore. Changes that require all verifiers to understand
new claims for security correctness MUST use a major version increment
(e.g., &quot;1.x&quot; to &quot;2.0&quot;). This ensures that a &quot;1.0&quot; verifier can safely
process any &quot;1.x&quot; token without creating a security gap where newer
security properties are silently dropped.</t>
<t>To support non-breaking schema evolution without version bumps, IPC
tokens MAY include additional claims beyond those defined in Section 2.1.
Verifiers MUST ignore unrecognized claims during verification and MUST
NOT reject an IPC solely because it contains claims not defined in
this specification. The <tt>version</tt> field indicates the minimum set of
claims a verifier must understand. Unrecognized claims MUST NOT be
included in authorization decisions.</t>
<t>This mechanism allows deployments to carry deployment-specific metadata
(e.g., trace correlation IDs, cost center tags) without requiring a
version bump, while ensuring that security-critical claims are always
processed by version-aware verifiers. Deployments adding extension claims
SHOULD use a namespace prefix (e.g., <tt>x-&lt;org&gt;-&lt;claim&gt;</tt>) to avoid
collision with future standard claims or other deployments' extensions.</t>

<ul spacing="compact">
<li>Entity identifiers (<tt>entityId</tt>, entries in <tt>delegationChain</tt>, <tt>authorizedBy</tt>,
<tt>issuer</tt>, <tt>audience</tt>) MUST conform to the following constraints:</li>
</ul>
<t>Verifier-enforceable (checked by the verification algorithm):
  - MUST be non-empty strings between 1 and 128 characters
  - MUST be compared using exact byte-for-byte matching (case-sensitive)
  - MUST NOT contain whitespace or control characters
  - SHOULD contain only ASCII printable characters (U+0021 through U+007E).
    If non-ASCII characters are used, identifiers MUST be in Unicode
    Normalization Form C (NFC) to ensure JCS canonicalization produces
    identical bytes across implementations.</t>
<t>Administrative (enforced by deployment policy, not by verifiers):
  - MUST be unique within the deployment's trust domain
  - RECOMMENDED format: URI (e.g., <tt>mailto:operator@example.com</tt>,
    <tt>urn:service:api-gateway</tt>), email address, or deployment-scoped
    identifier with a namespace prefix (e.g., <tt>agent:energy-optimizer</tt>)
  - Using email-format identifiers for HUMAN entities (e.g.,
    <tt>operator@example.com</tt>) and namespace-prefixed identifiers for agents
    (e.g., <tt>agent:energy-optimizer</tt>, <tt>service:api-gateway</tt>) is RECOMMENDED
    for human readability in audit logs.</t>
</section>

<section anchor="example"><name>Example</name>

<sourcecode type="json"><![CDATA[{
  "version": "1.0",
  "entityId": "cell-configurator",
  "entityType": "UTILITY",
  "authorizedBy": "energy-optimizer",
  "delegationDepth": 2,
  "delegationChain": [
    "operator@example.com",
    "energy-optimizer",
    "cell-configurator"
  ],
  "audience": "ncmp.platform.example.com",
  "sessionId": "0191a0b0-c8f4-7b2e-9a1c-3d4e5f6a7b8c",
  "issuedAt": "2026-07-17T08:00:00Z",
  "expiresAt": "2026-07-17T08:05:00Z",
  "issuer": "api-gateway",
  "trustLevel": 2,
  "alg": "EdDSA",
  "kid": "api-gateway/key-1",
  "signature": "Base64url(...)"
}
]]></sourcecode>
</section>

<section anchor="encoding"><name>Encoding</name>
<t>The IPC MUST be serialized as JSON, then Base64url-encoded for transport.</t>
</section>

<section anchor="signature-computation"><name>Signature Computation</name>
<t>The signature is computed over a canonical serialization of all claims EXCLUDING
the <tt>signature</tt> field itself. The process is:</t>

<ol spacing="compact">
<li>Construct a JSON object containing all claims except <tt>signature</tt>.</li>
<li>Serialize using JSON Canonicalization Scheme (JCS) per RFC 8785 to produce
a deterministic byte sequence.</li>
<li>Compute the signature over the canonical byte sequence using the algorithm
specified in the <tt>alg</tt> claim.</li>
<li>Base64url-encode the signature (without padding, per RFC 4648 Section 5)
and include it as the <tt>signature</tt> claim.</li>
</ol>
<t>The <tt>issuer</tt> field IS included in the signature input. This means that when a
trust boundary re-signs the IPC:
1. The <tt>issuer</tt> field is updated to the re-signing service's identifier.
2. A new signature is computed over all claims (including the new <tt>issuer</tt>)
   using the re-signing service's private key.
3. The new signature replaces the old <tt>signature</tt> value.</t>
<t>This ensures that each signature cryptographically binds the issuer to the IPC
content, proving which service validated and re-signed it.</t>
<t>The signature input includes the current <tt>issuer</tt> and <tt>kid</tt> values at
the time of signing. When a trust boundary re-signs, it updates both
<tt>issuer</tt> and <tt>kid</tt> before computing the new signature. The downstream
verifier's canonical bytes will therefore reflect the re-signing
service's identity and key identifier, not the original creator's.</t>
<t>Implementations that re-sign IPCs MUST NOT re-serialize claims
through a non-JCS-compliant JSON serializer. The canonical bytes for
signature computation MUST be produced by a JCS-compliant implementation
operating on the complete token object (including any unrecognized
extension claims). Re-serializing through a non-compliant serializer
may alter number representation, Unicode normalization, or key ordering,
causing downstream signature verification to fail.</t>
<t>The integer claims <tt>delegationDepth</tt> and <tt>trustLevel</tt> MUST be serialized
as JSON integers without decimal points (e.g., <tt>2</tt>, not <tt>2.0</tt>).
JCS (RFC 8785) specifies that integer-valued numbers serialize without
a decimal point, but intermediate deserialization into floating-point
types may produce incorrect canonical output.</t>
<t>Note on JCS sort order: JCS (RFC 8785 Section 3.2.3) sorts object
member names by comparing their UTF-16 code unit sequences. For
ASCII-only member names (all claims defined in this specification),
this is equivalent to byte-order sorting. Extension claims (Section 2.2)
that use non-ASCII characters in claim names MUST be aware that JCS
sort order follows RFC 8785 Section 3.2.3 exactly, including surrogate
pair handling. Extension claim names SHOULD be restricted to ASCII
characters for maximum interoperability across JCS implementations.</t>
</section>

<section anchor="algorithm-restrictions"><name>Algorithm Restrictions</name>
<t>EdDSA with curve Ed25519 (per RFC 8037) MUST be used for IPCs that traverse
multiple trust boundaries (the common case). Ed25519's asymmetric key model
allows each trust boundary to verify using the upstream's public key without
sharing secret material.</t>
<t>HS256 (HMAC-SHA-256, per RFC 7518 Section 3.2) MAY be used only in single-hop
deployments where exactly one service creates the IPC and exactly one service
verifies it. In this configuration, both services share the symmetric secret.
HS256 MUST NOT be used when:</t>

<ul spacing="compact">
<li>The IPC will be re-signed at any trust boundary</li>
<li>More than two services participate in the chain</li>
<li>The deployment topology may change to include additional hops</li>
</ul>
<t>Deployments that begin with single-hop HMAC and later add trust boundaries
MUST migrate to EdDSA before enabling re-signing.</t>
</section>

<section anchor="fips-compatible-algorithm-profile"><name>FIPS-Compatible Algorithm Profile</name>
<t>Deployments operating in FIPS 140-2/140-3 constrained environments
(e.g., government, regulated telecom, banking, healthcare) where
Ed25519 is not available in validated cryptographic modules MAY use
ES256 (ECDSA with P-256 and SHA-256, per RFC 7518 Section 3.4) as
an alternative asymmetric signing algorithm.</t>
<t>When ES256 is used:
- The <tt>alg</tt> claim MUST be set to &quot;ES256&quot;
- Key discovery endpoints MUST serve keys with <tt>&quot;kty&quot;: &quot;EC&quot;</tt>,
  <tt>&quot;crv&quot;: &quot;P-256&quot;</tt> per RFC 7518 Section 6.2
- The signature is computed over the same JCS canonical bytes as
  EdDSA (Section 2.5)
- The signature value is the base64url-encoded concatenation of
  the R and S values (64 bytes total, per RFC 7518 Section 3.4)</t>
<t>Deployments MUST NOT mix Ed25519 and ES256 within the same service
chain. All trust boundaries and enforcement points in a given
chain MUST use the same asymmetric algorithm. The choice is
deployment-wide, not per-service.</t>
<t>The verification algorithm (Section 3.2, Step 5) MUST accept
&quot;ES256&quot; as a valid algorithm value when the deployment is
configured for the FIPS profile. Step 6g dispatches to
ECDSA-P256.Verify for &quot;ES256&quot; tokens.</t>
<t>Services configured as Trust Boundaries (i.e., services that re-sign IPCs)
MUST have <tt>allowHMAC = false</tt>. Implementations MUST reject configurations
where a service is assigned the Trust Boundary role (Section 11.3) and
<tt>allowHMAC = true</tt>. This prevents misconfigured services from accepting
HMAC-signed tokens in a multi-hop chain where symmetric key material
would need to be shared across trust boundaries.</t>
</section>

<section anchor="design-rationale-why-not-jws"><name>Design Rationale: Why Not JWS?</name>
<t>IPC uses a JSON format with JCS (RFC 8785) canonicalization rather than
JWS (RFC 7515). This is a deliberate design choice driven by IPC's
per-hop re-signing workflow. The following analysis evaluates both JWS
compact serialization and JWS JSON Serialization:</t>
<t><strong>JWS Compact Serialization (RFC 7515 Section 7.1):</strong></t>
<t>JWS compact serialization represents a signed token as
BASE64URL(header).BASE64URL(payload).BASE64URL(signature). Re-signing
an IPC at a trust boundary would require:
1. Base64url-decode the payload to access claims
2. Modify <tt>issuer</tt> and <tt>kid</tt> in the decoded payload
3. Re-encode the modified payload as base64url
4. Construct a new protected header (or reuse)
5. Compute signature over the new header.payload concatenation
6. Assemble the new compact token</t>
<t>IPC's approach requires:
1. Parse the JSON object
2. Modify <tt>issuer</tt> and <tt>kid</tt>
3. Compute JCS canonical bytes (excluding signature)
4. Sign and set the signature field</t>
<t>The difference is not in operation count but in <strong>verification
determinism</strong>: JWS requires the verifier to use the exact base64url
encoding the signer produced (since the signature covers the encoded
form). IPC's JCS approach allows the verifier to independently
reconstruct the canonical signed bytes from the parsed JSON object
without depending on the signer's encoding choices. This eliminates a
class of interoperability failures where base64url implementations
differ in whitespace handling, line-wrapping, or padding.</t>
<t><strong>JWS JSON Serialization (RFC 7515 Section 7.2):</strong></t>
<t>JWS JSON Serialization could in principle support IPC's re-signing model
by placing mutable fields (<tt>issuer</tt>, <tt>kid</tt>) in the unprotected header.
However, this approach has practical drawbacks:</t>

<ol>
<li><t><strong>Unprotected header semantics vary across implementations.</strong>
Libraries differ in how they expose and validate unprotected headers.
Some JOSE libraries reject or ignore unprotected headers entirely.
A constrained profile mandating specific unprotected header behavior
would require the same implementation effort as IPC's native format.</t>
</li>
<li><t><strong>Base64url-within-base64url nesting.</strong> The IPC payload would be
base64url-encoded inside the JWS structure, which is then base64url-
encoded for transport. IPC's flat structure means the decoded JSON is
directly inspectable without double decoding - valuable for debugging,
log analysis, and operator tooling.</t>
</li>
<li><t><strong>Single-object manipulation model.</strong> IPC middleware operates on one
JSON object throughout the verify-modify-sign cycle. JWS JSON
Serialization requires managing separate <tt>protected</tt>, <tt>header</tt>,
<tt>payload</tt>, and <tt>signature</tt> fields with different encoding rules,
increasing the surface area for implementation bugs in the re-signing
path.</t>
</li>
</ol>
<t><strong>The core design principle:</strong> IPC optimizes for the re-signing hot path
(verify upstream, update issuer, re-sign, forward) that executes at every
trust boundary in every request. JCS provides a deterministic canonical
form that any conformant implementation can independently reproduce from
the parsed claims, without depending on how the upstream serialized the
token.</t>
<t><strong>Tradeoffs acknowledged:</strong></t>

<ul spacing="compact">
<li>IPCs are not directly verifiable by existing JWT/JWS libraries without
an IPC-specific implementation. This is acceptable because IPC operates
within platform infrastructure where purpose-built middleware is deployed
at each trust boundary.</li>
<li>This choice increases the implementation barrier compared to JWS-based
approaches: deployments MUST verify that their JCS implementation
produces correct canonical output (Appendix E provides a test vector
for this purpose) before using it in production.</li>
<li>The JCS library ecosystem (RFC 8785) is smaller than the JWS ecosystem.
Implementors SHOULD validate their chosen JCS library against the
RFC 8785 test vectors before production deployment.</li>
<li>A future revision of this specification MAY define a JWS-based profile
for deployments that prefer JOSE tooling compatibility, provided the
re-signing semantics and verification determinism requirements can be
met. The authors welcome WG input on whether a constrained JWS JSON
Serialization profile should be developed as an alternative encoding.</li>
</ul>
</section>
</section>

<section anchor="trust-boundaries-and-re-signing"><name>Trust Boundaries and Re-signing</name>

<section anchor="concept"><name>Concept</name>
<t>At each trust boundary, the receiving service:</t>

<ol spacing="compact">
<li>Extracts the IPC from the incoming protocol binding.</li>
<li>Verifies the signature using the upstream issuer's public key.</li>
<li>Validates all enforcement rules (Section 5).</li>
<li>Re-signs the IPC with its own private key, updating the <tt>issuer</tt> field.</li>
<li>Forwards the re-signed IPC to the next service.</li>
</ol>
</section>

<section anchor="verification-algorithm"><name>Verification Algorithm</name>
<t>A service receiving an IPC MUST execute the following steps in order.
The service MUST reject at the first step that fails.</t>

<artwork><![CDATA[VERIFY-IPC(encoded_ipc, key_resolver, config):

  0. Size and Format Pre-check:
     If len(encoded_ipc) == 0 -> REJECT (401, "malformed_ipc")
     If len(encoded_ipc) > 4096 -> REJECT (401, "malformed_ipc")
     If encoded_ipc contains any character not in [A-Za-z0-9_-]
     -> REJECT (401, "malformed_ipc")
     // Base64url encoding MUST NOT include padding ('=' characters).
     // This pre-check prevents resource exhaustion from oversized
     // inputs (Section 8.6) and rejects obviously malformed values
     // before incurring decode and parse costs.

  1. Decode: base64url-decode encoded_ipc to obtain JSON bytes.
     If decoding fails -> REJECT (401, "malformed_ipc")

  2. Parse: JSON-parse the bytes into a Token object.
     If parsing fails -> REJECT (401, "malformed_ipc")
     If the JSON object contains duplicate member names
     -> REJECT (401, "malformed_ipc")
     // RFC 8259 states names SHOULD be unique but does not mandate it.
     // IPC requires uniqueness to prevent ambiguous claim values that
     // could differ between parser implementations, creating security
     // vulnerabilities where verification and enforcement use different
     // values for the same claim name.
     //
     // Implementation note: Verifiers MUST use a JSON parser that
     // detects and reports duplicate member names. Parsers that silently
     // retain only the first or last value for duplicate names are
     // non-conformant. Many standard library JSON parsers (e.g.,
     // Python's json.loads into dict, Go's json.Unmarshal into map)
     // silently discard duplicates. Implementations MUST use a parser
     // mode or wrapper that detects duplicates before constructing the
     // Token object.

  3. Version:
     If Token.version is not a string -> REJECT (401, "malformed_ipc")
     If Token.version does not match the format "^[1-9][0-9]*\.[0-9]+$"
     -> REJECT (401, "malformed_ipc")
     // Version MUST be exactly "<major>.<minor>" where major is a
     // non-zero-padded positive integer and minor is a non-negative
     // integer. Examples: "1.0", "2.1", "10.0". Invalid: "01.0",
     // "1", "1.0.1", "1.0-beta", "v1.0".
     Parse Token.version as "<major>.<minor>".
     If major version != "1" -> REJECT (401, "unsupported_version")
     // Minor version differences are acceptable per Section 2.2.

  4. Trust Level Dispatch:
     If Token.trustLevel is not an integer -> REJECT (401, "malformed_ipc")
     If Token.trustLevel == 1:
       If Token.alg is absent OR Token.alg is not a string OR Token.alg != "none"
       -> REJECT (401, "malformed_ipc")
       If Token.kid is present and Token.kid != ""
       -> REJECT (401, "malformed_ipc")
       If Token.signature is present and Token.signature != ""
       -> REJECT (401, "malformed_ipc")
       // Trust Level 1 IPCs MUST NOT carry kid or signature values.
       // A non-empty kid or signature in an unsigned IPC indicates
       // either corruption or a downgrade attempt.
       If config.isEnforcementPoint == true
       -> REJECT (401, "unsigned_token")
       // Enforcement points MUST NOT accept Trust Level 1 IPCs regardless
       // of config.allowUnsignedForAudit. This prevents downgrade attacks
       // where an unsigned IPC is substituted for a signed one.
       If config.allowUnsignedForAudit != true
       -> REJECT (401, "unsigned_token")
       Skip steps 5-6 (key resolution and signature verification)
       and proceed to Step 7. Trust Level 1 IPCs undergo structural
       validation only and MUST NOT be used for authorization decisions.
     If Token.trustLevel < 1 or Token.trustLevel > 2
     -> REJECT (401, "malformed_ipc")
     // For trustLevel == 2: continue to Step 5

  5. Algorithm Validation (Level 2+ only):
     If Token.alg is not one of: "EdDSA", "ES256", "HS256"
     -> REJECT (401, "unsupported_algorithm")
     If Token.alg is "EdDSA" or "ES256":
       If config.asymmetricAlgorithm is configured AND
       Token.alg != config.asymmetricAlgorithm
       -> REJECT (401, "algorithm_not_permitted")
       // Deployments MUST NOT mix EdDSA and ES256 within a chain.
       // config.asymmetricAlgorithm enforces deployment-wide
       // algorithm consistency.
     If Token.signature is not a string or is empty or is absent
     -> REJECT (401, "malformed_ipc")

  6. Key Resolution and Signature Verification:
     Pre-check: If Token.kid is absent, null, or not a string
        -> REJECT (401, 'malformed_ipc')
     If Token.issuer is absent, null, or not a string
        -> REJECT (401, 'malformed_ipc')
     a. Extract Token.kid and Token.issuer
     b. Resolve upstream_public_key using the tuple
        (Token.issuer, Token.kid, Token.alg):
        - Via key discovery endpoint (Section 3.6), OR
        - Via static key configuration
        The verifier MUST maintain a mapping of known upstream issuers
        to their key sources (via static config, key discovery endpoint,
        or service mesh control plane).
     c. If Token.kid is unknown or key not found
        -> REJECT (401, "unknown_key")
     c2. If the resolved key has status "compromised"
        -> REJECT (401, "unknown_key")
        // Compromised keys MUST be rejected regardless of token
        // validity or expiry.
     c3. Pre-validate Token.issuedAt for key validity comparison:
        If Token.issuedAt is absent, not a string, or does not match
        the canonical format YYYY-MM-DDTHH:MM:SSZ (20 characters)
        -> REJECT (401, "malformed_ipc")
        // This minimal validation allows safe comparison against
        // key validity timestamps. Full temporal validation
        // (including clock skew and expiry checks) occurs in Step 8.
        If the resolved key has a `validFrom` timestamp and
        Token.issuedAt < key.validFrom
        -> REJECT (401, "unknown_key")
        // The token claims to have been issued before this key
        // existed.
     c4. If the resolved key has status "retired" and key.validUntil
        is set:
        Accept only if Token.issuedAt <= key.validUntil.
        If Token.issuedAt > key.validUntil
        -> REJECT (401, "unknown_key")
        // A retired key should only verify tokens issued during
        // the key's active period.
     d. Verify that the resolved key belongs to Token.issuer. If the
        key source serves keys for multiple services, confirm the kid
        is associated with Token.issuer. When keys are statically
        configured, the configuration MUST explicitly associate each
        kid with its owning issuer identifier. A kid that resolves
        successfully but is not mapped to Token.issuer indicates a
        potential confused-deputy attack (T13). If mismatch:
        -> REJECT (401, "unknown_key")
     e. Construct canonical_bytes = JCS(Token excluding "signature")
     f. Decode sig = base64url-decode(Token.signature)
     g. Dispatch on Token.alg:
        If Token.alg == "EdDSA":
          Verify: Ed25519.Verify(upstream_public_key, canonical_bytes, sig)
        Else if Token.alg == "ES256":
          Verify: ECDSA-P256-SHA256.Verify(upstream_public_key, canonical_bytes, sig)
        Else if Token.alg == "HS256":
          If config.allowHMAC != true -> REJECT (401, "algorithm_not_permitted")
          Verify: HMAC-SHA256.Verify(shared_secret, canonical_bytes, sig)
        // Note: No Else branch is needed here. Step 5 has already
        // validated that Token.alg is one of the supported values.
        // Implementations MAY include a defensive Else -> REJECT for
        // defense-in-depth, but it is unreachable in conformant code.
     If verification fails -> REJECT (401, "signature_invalid")

  7. Type Validation:
     All REQUIRED claims MUST be present and of the correct JSON type
     as defined in Section 2.1 (string, integer, or array).
     - entityId, entityType, authorizedBy, sessionId, issuedAt,
       expiresAt, issuer, version: MUST be strings
     - delegationDepth, trustLevel: MUST be integers. Implementations
       MUST verify that these values are encoded as JSON integer
       literals without decimal points or exponent notation (matching
       the pattern `0|[1-9][0-9]*`). Values such as `2.0`, `2e0`, or
       `2E0` MUST be rejected as `malformed_ipc` even if mathematically
       integral. This ensures JCS canonicalization produces identical
       bytes across implementations regardless of parser numeric
       handling.
     - delegationChain: MUST be an array of strings
     - audience: MUST be a string. MUST be present when trustLevel >= 2.
        If trustLevel >= 2 and audience is absent -> REJECT (401, 'malformed_ipc')
        // Note: The audience non-empty check is enforced again at Step 9f
        // as defense-in-depth. Step 7 checks presence; Step 9f checks
        // semantic validity (non-empty, non-whitespace).
     - alg: MUST be a string (REQUIRED at all trust levels)
     - kid, signature (if present): MUST be strings.
       MUST be present when trustLevel >= 2.
       If trustLevel >= 2 and (kid is absent OR signature is absent)
       -> REJECT (401, 'malformed_ipc')
     - entityType MUST be one of: AUTONOMOUS, SUPERVISED, UTILITY,
       HUMAN, SYSTEM (case-sensitive exact match)
     If any claim is missing, null, of wrong type, or entityType is
     unrecognized -> REJECT (401, "malformed_ipc")

  8. Temporal Validity:
     a. Parse Token.issuedAt and Token.expiresAt as ISO 8601 UTC timestamps.
        Both MUST match the canonical format YYYY-MM-DDTHH:MM:SSZ (exactly
        20 characters, trailing "Z", no fractional seconds).
        If either fails to parse or does not match the canonical format
        -> REJECT (401, 'malformed_ipc')
     b. now = current UTC time
     c. If Token.expiresAt <= Token.issuedAt -> REJECT (401, "malformed_ipc")
        // expiresAt must be strictly after issuedAt (positive lifetime)
     c2. If (Token.expiresAt - Token.issuedAt) > config.maxTTL
        -> REJECT (401, "malformed_ipc")
        // The token's stated lifetime MUST NOT exceed the deployment's
        // maximum configured TTL. This prevents a compromised or
        // misconfigured IPC Creator from issuing long-lived tokens
        // that bypass short-TTL replay protection.
     d. If Token.issuedAt > now + config.clockSkew -> REJECT (403, "issued_in_future")
     e. If now > Token.expiresAt + config.clockSkew -> REJECT (403, "token_expired")
     f. If config.maxTokenAge > 0:
        age = now - Token.issuedAt
        If age > config.maxTokenAge + config.clockSkew -> REJECT (403, "token_too_old")

     Note: maxTokenAge is OPTIONAL and provides defense against tokens
     that are valid but were issued unreasonably long ago (e.g., due to
     clock manipulation at the creator). Deployments that do not require
     this check SHOULD set config.maxTokenAge to 0 (disabled).

     Note: The addition of config.clockSkew to the maxTokenAge comparison
     is intentional. Without this tolerance, tokens issued at the boundary
     of maxTokenAge could be rejected by verifiers whose clocks are
     slightly behind the issuer's clock. The effective maximum age is
     therefore maxTokenAge + clockSkew. Deployments that require a strict
     maximum age SHOULD account for this extension when configuring
     maxTokenAge.

  9. Structural Integrity:
     a. If len(Token.delegationChain) == 0 -> REJECT (403, "empty_chain")
     b. If Token.delegationDepth != len(Token.delegationChain) - 1
        -> REJECT (403, "depth_mismatch")
     c. If len(Token.delegationChain) >= 2 AND
        Token.authorizedBy != Token.delegationChain[len-2]
        -> REJECT (403, "chain_consistency_error")
     d. If len(Token.delegationChain) == 1 AND
        Token.authorizedBy != Token.entityId
        -> REJECT (403, "chain_consistency_error")
     e. If Token.entityId != Token.delegationChain[len(Token.delegationChain) - 1]
        -> REJECT (403, "chain_consistency_error")
     f. If Token.trustLevel >= 2 AND (Token.audience == "" OR Token.audience contains only whitespace)
        -> REJECT (401, "malformed_ipc")
        // An empty or whitespace-only audience for Trust Level 2+ is a
        // structural validity error (malformed token), not a semantic
        // audience mismatch.
     g. Identifier constraints:
        For each of: Token.entityId, Token.authorizedBy, Token.issuer,
        Token.audience (if present), and each entry in Token.delegationChain:
          If len(identifier) < 1 or len(identifier) > 128
          -> REJECT (401, 'malformed_ipc')
          If identifier contains whitespace or control characters (U+0000-U+001F, U+007F)
          -> REJECT (401, 'malformed_ipc')

  10. Depth Enforcement:
     If Token.delegationDepth > config.maxDepth
     -> REJECT (403, "depth_exceeded")

  11. Circular Detection:
     seen = {}
     For each entity in Token.delegationChain:
       If entity in seen -> REJECT (403, "circular_delegation")
       seen.add(entity)

  12. Audience (terminal enforcement points only):
      If config.isTerminalEnforcementPoint:
        If Token.audience is present AND Token.audience != "" AND Token.audience != config.serviceId
        -> REJECT (403, "audience_mismatch")
      // Trust boundaries that re-sign and forward MUST skip this step.
      // Only the terminal service that makes the final authorization
      // decision (and does not re-sign) executes audience verification.
      //
      // isTerminalEnforcementPoint is a static configuration property
      // of the service's role in the deployment topology.
      //
      // Services that ALWAYS act as terminal enforcement points (never
      // forward) set isTerminalEnforcementPoint = true.
      //
      // Services that ALWAYS act as pass-through trust boundaries (never
      // make final decisions) set isTerminalEnforcementPoint = false.
      //
      // Services that sometimes forward and sometimes terminate MUST
      // set isTerminalEnforcementPoint = false in the verification
      // algorithm and perform audience verification in application
      // logic AFTER VERIFY-IPC returns ACCEPT, only when the service
      // determines it will not forward. Such services MUST be
      // configured as both Trust Boundary and Enforcement Point
      // (per Section 11.3) and MUST document their audience
      // verification behavior.

  13. Revocation (if RevocationChecker configured):
      // Revocation checks MUST use a local in-memory cache or
      // bounded-latency shared cache. Verifiers MUST NOT perform
      // unbounded synchronous network calls per entity during
      // verification. If pull-based revocation is used, polling
      // MUST refresh a local cache asynchronously; verification
      // reads from the cache only.
      // If revocation state is unavailable, enforcement points
      // MUST either fail closed (reject the IPC) or operate under
      // an explicitly configured degraded mode that is logged and
      // alerted.
      For each entity in Token.delegationChain:
        revocation = RevocationChecker.GetRevocation(entity)
        If revocation is not null:
          If revocation.scope == "cascade"
          -> REJECT (403, "entity_revoked")
          If revocation.scope == "single" AND entity == Token.entityId
          -> REJECT (403, "entity_revoked")
      // "single" revocations only reject if the revoked entity is
      // the current executor (entityId), not an intermediate in the chain.

  13b. Caller-to-Issuer Binding (RECOMMENDED):
      If config.callerBindingEnabled AND caller_identity is available
      (from mTLS certificate, bearer token, or service mesh metadata):
        If caller_identity does not match Token.issuer AND
        caller_identity is not in config.allowedPresentersFor[Token.issuer]
        -> REJECT (403, "issuer_caller_mismatch")
      // This step verifies that the authenticated transport identity
      // of the immediate caller is authorized to present IPCs signed
      // by Token.issuer. Without this check, a service that obtains
      // a valid IPC could present it directly to a downstream service
      // bypassing the intended chain (confused deputy, T13).
      // This step requires caller_identity to be available from the
      // transport layer (mTLS, bearer token, service mesh). If caller
      // identity is not available, this step is skipped but a warning
      // SHOULD be logged.

  14. Replay Detection (enforcement points only):
      If config.isEnforcementPoint:
        // The replay cache operation MUST be atomic (check-and-insert).
      // Implementations SHOULD use atomic primitives (e.g., Redis SETNX,
      // CAS operations) to prevent race conditions (T18).
      If replay cache is unavailable or at capacity:
        -> REJECT (503, "replay_cache_unavailable")
        // Enforcement points MUST fail closed when replay detection
        // cannot be performed. Failing open would allow replay attacks
        // during infrastructure degradation — exactly when attacks are
        // most likely.
      If Token.sessionId exists in replay cache
        -> REJECT (403, "token_replayed")
      Else: Atomically insert Token.sessionId into replay cache with
          TTL = max(config.replayCacheTTL, Token.expiresAt - now() + config.clockSkew)
        // The replay cache TTL MUST be at least config.clockSkew to
        // prevent premature eviction. The config.replayCacheTTL parameter
        // (Section 5.6) provides the normative minimum floor.
      // Pure trust boundaries that only re-sign MAY skip this step.

     // Implementations MUST NOT authorize or execute the requested
     // operation until all steps complete successfully. Optimistic
     // execution that begins processing before ACCEPT is non-conformant.

  15. ACCEPT: Token is valid.
]]></artwork>
</section>

<section anchor="re-signing-algorithm"><name>Re-signing Algorithm</name>
<t>A trust boundary that forwards requests downstream MUST re-sign the
IPC after successful verification. The re-signing algorithm is:</t>
<t>Trust boundaries MUST NOT re-sign IPCs with <tt>alg</tt> set to &quot;HS256&quot;.
If a trust boundary receives an HMAC-signed IPC that passes verification
(because it is configured as the single-hop verifier), it MUST NOT
forward a re-signed IPC downstream. HMAC-signed IPCs are terminal by
definition — the trust boundary may only act as an Enforcement Point
for that request, not as a pass-through re-signer. If re-signing and
forwarding is required, the deployment MUST migrate to EdDSA
(Section 2.6).</t>

<artwork><![CDATA[RESIGN-IPC(token, service_id, signing_private_key):

  1. Verify that token passed VERIFY-IPC (Section 3.2).
     Re-signing MUST NOT occur without prior verification.
     If token.alg == "HS256" -> ABORT (cannot re-sign HMAC tokens)

  2. Update mutable fields:
     token.issuer = service_id
     token.kid = signing_key_id

  3. Compute new signature:
     a. canonical_bytes = JCS(token excluding "signature")
     b. Dispatch on token.alg:
        If token.alg == "EdDSA":
          sig = Ed25519.Sign(signing_private_key, canonical_bytes)
        Else if token.alg == "ES256":
          sig = ECDSA-P256-SHA256.Sign(signing_private_key, canonical_bytes)
     c. token.signature = base64url-encode(sig)

  4. Immutability check:
     All claims other than "issuer", "kid", and "signature" MUST remain
     unchanged from the verified token. If any other field was
     modified, the implementation is non-conformant.

     Note: The token object used for JCS canonicalization in Step 3a
     MUST include all claims present in the verified token, including
     any unrecognized extension claims (Section 2.2). Implementations
     MUST preserve the complete JSON object through the re-signing
     process. Deserializing into a typed struct that discards unknown
     fields will produce incorrect canonical output, causing downstream
     signature verification to fail.

  5. Encode for transport:
     encoded = base64url-encode(JSON(token))

  6. Set protocol binding:
     HTTP:  Set header Identity-Propagation-Context: <encoded>
     gRPC:  Set metadata identity-propagation-context: <encoded>
     Kafka: Set record header identity-propagation-context: <encoded>
]]></artwork>
</section>

<section anchor="immutability"><name>Immutability</name>
<t>The IPC payload (all claims except <tt>issuer</tt>, <tt>kid</tt>, and <tt>signature</tt>) MUST NOT be
modified during re-signing. Only the <tt>issuer</tt>, <tt>kid</tt>, and <tt>signature</tt> fields change during re-signing.</t>
</section>

<section anchor="key-management"><name>Key Management</name>
<t>Each trust boundary service maintains:
- Its own signing key (private)
- The public key of the upstream issuer (for verification)</t>
<t>A key discovery endpoint (similar to OIDC <tt>.well-known/jwks.json</tt>) is
RECOMMENDED for dynamic key retrieval.</t>
</section>

<section anchor="key-discovery-endpoint"><name>Key Discovery Endpoint</name>
<t>Services fulfilling the Trust Boundary or IPC Creator conformance
classes (Section 11.3) MUST make their public keys available through
at least one of the following mechanisms:</t>

<ol spacing="compact">
<li>The IPC key discovery HTTP endpoint defined below (REQUIRED for
multi-vendor interoperability claims per Section 1.8).</li>
<li>An authenticated platform control plane that provides equivalent
key material and metadata (e.g., service mesh xDS/SDS, SPIFFE
bundle endpoints, Kubernetes secrets).</li>
</ol>
<t>The HTTP endpoint is the mandatory-to-implement interoperability
profile. Deployments using control-plane key distribution alone
MUST ensure that the distributed key material includes all fields
defined below (serviceId, keys with status, validFrom, validUntil).
Enforcement Points that only verify (and never re-sign) MAY use
static key configuration alone but SHOULD support key discovery for
operational flexibility.</t>
<t>The key discovery endpoint MUST be available at:</t>

<artwork><![CDATA[GET /.well-known/ipc-keys
]]></artwork>
<t>The response MUST be a JSON object:</t>

<sourcecode type="json"><![CDATA[{
  "serviceId": "scope-enforcement",
  "cacheMaxAge": 300,
  "keys": [
    {
      "kid": "scope-enforcement-key-1",
      "kty": "OKP",
      "crv": "Ed25519",
      "x": "<base64url-encoded public key>",
      "use": "sig",
      "status": "active",
      "validFrom": "2026-07-01T00:00:00Z",
      "validUntil": null
    }
  ]
}
]]></sourcecode>

<ul spacing="compact">
<li><tt>serviceId</tt> (string, REQUIRED): This service's IPC issuer identifier.
The <tt>serviceId</tt> value in the key discovery response MUST exactly match
the <tt>issuer</tt> claim value that this service sets when signing or
re-signing IPCs. Verifiers use this correspondence to confirm that
a resolved key belongs to the claimed issuer (Section 3.2, Step 6d).</li>
<li><tt>cacheMaxAge</tt> (integer, OPTIONAL): Maximum number of seconds a client
SHOULD cache this response. If absent, clients use their locally configured
cache TTL (RECOMMENDED default: 300 seconds). The server MAY reduce this
value during key rotation to accelerate downstream key refresh (e.g., set
to 30 seconds after publishing a new key). Clients MUST NOT cache responses
longer than this value when present.</li>
<li><t><tt>keys</tt> (array, REQUIRED): JWK objects with IPC metadata.</t>

<ul spacing="compact">
<li><tt>status</tt> (string, REQUIRED): &quot;active&quot;, &quot;retired&quot;, or &quot;compromised&quot;.</li>
<li><tt>validFrom</tt> (string, REQUIRED): ISO 8601 UTC timestamp.</li>
<li><tt>validUntil</tt> (string or null, REQUIRED): null if still active.</li>
</ul></li>
</ul>
<t>Implementations MUST cache key discovery responses for no longer than the
configured cache TTL (RECOMMENDED default: 300 seconds) and MUST refresh
before using a key whose <tt>validUntil</tt> has passed.</t>
<t>Key material validation depends on the algorithm:
- For Ed25519 keys (<tt>&quot;kty&quot;: &quot;OKP&quot;</tt>, <tt>&quot;crv&quot;: &quot;Ed25519&quot;</tt>): the <tt>x</tt> field
  MUST be exactly 43 characters of base64url (representing 32 bytes of
  public key material, without padding). Verifiers MUST reject keys
  where <tt>x</tt> does not decode to exactly 32 bytes.
- For ES256 keys (<tt>&quot;kty&quot;: &quot;EC&quot;</tt>, <tt>&quot;crv&quot;: &quot;P-256&quot;</tt>): the <tt>x</tt> and <tt>y</tt>
  fields MUST each be exactly 43 characters of base64url (representing
  32 bytes each). Both coordinates are REQUIRED. Verifiers MUST reject
  keys where <tt>x</tt> or <tt>y</tt> does not decode to exactly 32 bytes.</t>
<t>The key discovery endpoint exposes only public key material and MAY
be served without authentication (public keys are not secret). However,
deployments SHOULD apply rate limiting to prevent enumeration attacks
and SHOULD restrict access to the platform's internal network when
the endpoint is not intended for external consumption.</t>
<t>Deployments MUST maintain a mapping from issuer identifiers to key
discovery URLs. This mapping MAY be distributed via service mesh
configuration, DNS service discovery, or static configuration. The
mechanism is deployment-specific and not defined by this specification.</t>
</section>

<section anchor="error-response-format"><name>Error Response Format</name>
<t>When IPC verification or enforcement fails, the error response MUST use
the following JSON structure:</t>

<sourcecode type="json"><![CDATA[{
  "error": "signature_invalid",
  "errorDescription": "IPC signature verification failed",
  "sessionId": "0191a0b0-c8f4-7b2e-9a1c-3d4e5f6a7b8c",
  "entityId": "cell-configurator",
  "issuer": "api-gateway"
}
]]></sourcecode>
<t>Fields:</t>

<ul spacing="compact">
<li><tt>error</tt> (string, REQUIRED): Machine-readable error code. MUST be one of:
<tt>malformed_ipc</tt>, <tt>unsupported_version</tt>, <tt>unsigned_token</tt>,
<tt>signature_invalid</tt>, <tt>issued_in_future</tt>, <tt>token_expired</tt>,
<tt>token_replayed</tt>, <tt>unknown_key</tt>, <tt>token_too_old</tt>, <tt>empty_chain</tt>,
<tt>depth_mismatch</tt>, <tt>chain_consistency_error</tt>, <tt>depth_exceeded</tt>,
<tt>circular_delegation</tt>, <tt>audience_mismatch</tt>, <tt>entity_revoked</tt>,
<tt>algorithm_not_permitted</tt>, <tt>unsupported_algorithm</tt>,
<tt>replay_cache_unavailable</tt>, <tt>issuer_caller_mismatch</tt>.</li>
<li><tt>errorDescription</tt> (string, REQUIRED): Human-readable description.</li>
<li><tt>sessionId</tt> (string, OPTIONAL): From the IPC if parseable.</li>
<li><tt>entityId</tt> (string, OPTIONAL): From the IPC if parseable.</li>
<li><tt>issuer</tt> (string, OPTIONAL): From the IPC if parseable.</li>
</ul>
<t>IPC error responses use camelCase field names (e.g., <tt>errorDescription</tt>)
to align with the IPC claims naming convention (<tt>entityId</tt>, <tt>sessionId</tt>).
This intentionally differs from OAuth 2.0's <tt>error_description</tt> convention.</t>
<t>The <tt>sessionId</tt>, <tt>entityId</tt>, and <tt>issuer</tt> fields SHOULD be included when
the IPC was successfully parsed (even if signature verification failed).
They MUST NOT be included if the IPC could not be decoded.</t>
</section>

<section anchor="verification-failure"><name>Verification Failure</name>
<t>When IPC verification fails at a trust boundary, the service MUST:</t>

<ol spacing="compact">
<li>Reject the request. The IPC MUST NOT be forwarded downstream.</li>
<li><t>Return an appropriate error to the caller:</t>

<ul spacing="compact">
<li>HTTP: 401 Unauthorized if the IPC signature is invalid, or if the
IPC header is absent and fail-closed mode is configured (Section 3.9).</li>
<li>HTTP: 403 Forbidden if the IPC is valid but fails enforcement rules
(e.g., <tt>delegationDepth &gt; maxDepth</tt>, expired, audience mismatch).</li>
<li>gRPC: UNAUTHENTICATED (code 16) or PERMISSION_DENIED (code 7)
following the same distinction.</li>
</ul></li>
<li>Log the failure with sufficient detail for audit (sessionId, entityId,
failure reason) without logging the full IPC (which could be
replayed from logs).</li>
</ol>
<t>Trust Boundaries and Enforcement Points MUST log the following for every
IPC verification (successful or failed):
- <tt>sessionId</tt> of the IPC
- <tt>entityId</tt> of the current executor
- Verification result (accept/reject)
- If rejected: the error code from Section 3.7
- Timestamp of the verification event</t>
<t>This requirement is normative because the single-signer model (Section 8.5)
relies on audit trails for forensic traceability. Without audit logging,
a compromised intermediate cannot be detected after the fact.</t>
<t>During migration periods, deployments MAY configure enforcement points to
operate in &quot;permissive mode&quot; where IPC failures are logged but not enforced.
This MUST be a temporary configuration and MUST NOT be used in production
enforcement.</t>
</section>

<section anchor="absent-ipc"><name>Absent IPC</name>
<t>The behavior when an IPC header is absent from a request is
deployment-specific:</t>

<ul spacing="compact">
<li>Security-critical enforcement points SHOULD reject requests lacking a
valid IPC (fail-closed).</li>
<li>Services operating in mixed environments during migration MAY accept
requests without IPC, provided the bearer token alone provides sufficient
authorization for the requested operation.</li>
<li>Deployments MUST document which services require IPC and which operate
in a degraded mode without it.</li>
</ul>
</section>
</section>

<section anchor="delegation-issuance"><name>Delegation Issuance</name>

<section anchor="ipc-creation"><name>IPC Creation</name>
<t>An IPC is created at the platform trust boundary (e.g., an API gateway) when:</t>

<ol spacing="compact">
<li>An entity authenticates (e.g., via OAuth 2.0 JWT).</li>
<li>Delegation context is provided (e.g., via a delegation-token from an STS).</li>
<li>The trust boundary validates both and constructs the IPC.</li>
</ol>
</section>

<section anchor="delegation-token"><name>Delegation Token</name>
<t>A delegation-token is a short-lived, signed token issued by a platform STS
that establishes the delegation relationship. It is consumed by the trust
boundary during IPC creation and discarded afterward.</t>
<t>The delegation-token is NOT the IPC. It is the input from which the IPC is
constructed. The format and issuance protocol for delegation-tokens is out of
scope for this specification - platforms MAY use RFC 8693 Token Exchange,
proprietary STS protocols, or any mechanism that provides authenticated
delegation context to the trust boundary.</t>
</section>

<section anchor="ipc-creation-algorithm"><name>IPC Creation Algorithm</name>
<t>The following algorithm defines how an IPC Creator constructs a new IPC:</t>

<artwork><![CDATA[CREATE-IPC(authenticated_entity, delegation_context, config):

  1. Determine delegation chain:
     If delegation_context is present:
       chain = delegation_context.chain + [authenticated_entity.id]
     Else:
       chain = [authenticated_entity.id]

  2. Validate chain depth:
     If len(chain) - 1 > config.maxDepth -> REJECT

  3. Set claims:
     version = "1.0"
     entityId = authenticated_entity.id
     entityType = authenticated_entity.type
     If len(chain) == 1:
       authorizedBy = entityId  // self-authorized root
     Else:
       authorizedBy = chain[len(chain) - 2]  // immediate parent
     delegationDepth = len(chain) - 1
     delegationChain = chain
     audience = config.targetServiceId  // final enforcement point
       If trustLevel >= 2 AND audience is empty -> REJECT (creation failed)
     sessionId = generateUUIDv7()
     issuedAt = now()
     If delegation_context is present:
       expiresAt = min(now() + config.maxTTL,
                       delegation_context.expiresAt)  // attenuation
     Else:
       expiresAt = now() + config.maxTTL
     issuer = config.serviceId
     kid = config.currentKeyId
     trustLevel = 2
     alg = config.asymmetricAlgorithm  // "EdDSA" (default) or "ES256" (FIPS)

  4. Sign:
     canonical_bytes = JCS(token excluding "signature")
     If alg == "EdDSA":
       token.signature = base64url(Ed25519.Sign(config.privateKey,
                                                canonical_bytes))
     Else if alg == "ES256":
       token.signature = base64url(ECDSA-P256-SHA256.Sign(config.privateKey,
                                                          canonical_bytes))

  5. Encode:
     encoded = base64url(JSON(token))

  6. Attach to outbound request via protocol binding (Section 6)
]]></artwork>
<t>IPC Creators SHOULD implement rate limiting per authenticated entity to
prevent resource exhaustion at downstream replay caches and signing
infrastructure. RECOMMENDED limits: no more than 100 IPC creations per
second per entity under normal operation. Deployments with
higher-throughput requirements MUST ensure their replay cache
infrastructure can sustain the expected creation rate without evicting
entries before their TTL expires.</t>
<t>A new IPC MUST be created for each sub-delegation. Existing IPCs MUST
NOT be modified to add entries to the delegation chain - the immutability
rule (Section 3.4) prevents this. When an entity sub-delegates, the
platform creates a fresh IPC with the extended chain.</t>
</section>
</section>

<section anchor="enforcement-rules"><name>Enforcement Rules</name>

<section anchor="maximum-delegation-depth"><name>Maximum Delegation Depth</name>
<t>Each deployment MUST configure a maximum delegation depth (<tt>maxDepth</tt>). An IPC
with <tt>delegationDepth &gt; maxDepth</tt> MUST be rejected.</t>
<t>Deployments SHOULD configure consistent <tt>maxDepth</tt> values across all
trust boundaries and enforcement points in a given service chain.
Inconsistent values may result in IPCs being accepted at early hops
but rejected downstream. The IPC Creator SHOULD set delegation chain
depth based on the most restrictive enforcement point in the target
path.</t>
</section>

<section anchor="chain-verification"><name>Chain Verification</name>
<t>Every entry in <tt>delegationChain</tt> MUST be traceable to an accountable root (the
first entry). The root SHOULD be either:</t>

<ul spacing="compact">
<li>A human principal (entityType: HUMAN), OR</li>
<li>A system principal (entityType: SYSTEM) for platform-initiated operations</li>
</ul>
<t>Root type enforcement is a deployment policy decision. Deployments
without a principal registry MAY accept any root identifier format
that conforms to the identifier constraints in Section 2.2.</t>
<t>Deployments MAY restrict roots to human principals only via policy. An IPC
where the first entry in <tt>delegationChain</tt> does not match the deployment's
allowed root types MUST be rejected.</t>
<t>The root entity's type is determined by the IPC creator (trust boundary) at
issuance time based on the authenticated credential. The <tt>entityType</tt> claim
in the IPC reflects the <em>current</em> executing entity, not the root. Verifiers
determine root type via deployment-specific policy (e.g., convention that
email-format identifiers are HUMAN, or a local principal registry lookup).</t>
<t>Root type enforcement is a policy decision applied after the verification
algorithm (Section 3.2) completes. It is not encoded in the verification
algorithm because the IPC does not carry the root entity's type -
only the current entity's type (<tt>entityType</tt>). Enforcement points that
require root-type validation MUST resolve the root identity's type
through external policy.</t>
</section>

<section anchor="expiry-attenuation"><name>Expiry Attenuation</name>
<t>Expiry attenuation applies at IPC <strong>creation</strong> time, not during re-signing.</t>
<t>When a new IPC is created as a result of sub-delegation (e.g., Agent-1
delegates to Sub-Agent-1, and the platform creates a new IPC for the
sub-agent's request):</t>

<artwork><![CDATA[child.expiresAt <= parent.expiresAt
]]></artwork>
<t>A child IPC MUST NOT have a longer lifetime than the parent delegation
that authorized it. This ensures authority shrinks over time, never grows.</t>
<t>This rule does NOT apply during re-signing (Section 3.3). Re-signing
preserves the original <tt>expiresAt</tt> unchanged - the immutability rule
(Section 3.4) prevents any modification of <tt>expiresAt</tt> during re-signing.</t>
<t>Security note: Expiry attenuation is enforced by the IPC Creator at
issuance time. Downstream services cannot independently verify that
attenuation was correctly applied. A compromised or misconfigured IPC
Creator could issue child IPCs with longer lifetimes than their parent
delegation. The <tt>maxTokenAge</tt> check (verification Step 8f) provides a
deployment-wide hard cap independent of attenuation.</t>
</section>

<section anchor="circular-detection"><name>Circular Detection</name>
<t>If any identifier appears more than once in the <tt>delegationChain</tt>, the IPC
MUST be rejected. This prevents circular delegation.</t>
</section>

<section anchor="cascade-revocation"><name>Cascade Revocation</name>
<t>When a parent entity is revoked:</t>

<ol spacing="compact">
<li>A revocation event is published (e.g., via event-streaming topic).</li>
<li>All enforcement points receiving IPCs where the revoked entity
appears in the <tt>delegationChain</tt> MUST reject the IPC as specified
by the revocation scope rules in Section 5.5.1.</li>
<li>Additionally, IPCs naturally expire within their short TTL,
providing a secondary bound on exposure.</li>
</ol>

<section anchor="revocation-event-format"><name>Revocation Event Format</name>
<t>The revocation event is a JSON object published to a well-known topic
(e.g., <tt>ipc-revocation</tt>):</t>

<sourcecode type="json"><![CDATA[{
  "version": "1.0",
  "eventType": "entity.revoked",
  "eventId": "evt-0191b2c3-d4e5-7f6a-8b9c-0d1e2f3a4b5c",
  "entityId": "energy-optimizer",
  "revokedAt": "2026-07-17T08:02:30Z",
  "reason": "compromised",
  "scope": "cascade",
  "issuedBy": "platform-admin",
  "alg": "EdDSA",
  "kid": "platform-admin/key-1",
  "signature": "Base64url(...)"
}
]]></sourcecode>
<t>Field definitions:</t>
<table>
<thead>
<tr>
<th>Field</th>
<th>Type</th>
<th>Required</th>
<th>Description</th>
</tr>
</thead>

<tbody>
<tr>
<td><tt>version</tt></td>
<td>string</td>
<td>REQUIRED</td>
<td>Revocation event schema version (e.g., &quot;1.0&quot;). Major/minor semantics per Section 2.2 versioning rules. Receivers MUST reject events with an unsupported major version.</td>
</tr>

<tr>
<td><tt>eventType</tt></td>
<td>string</td>
<td>REQUIRED</td>
<td>MUST be &quot;entity.revoked&quot;</td>
</tr>

<tr>
<td><tt>eventId</tt></td>
<td>string</td>
<td>REQUIRED</td>
<td>Unique event identifier (UUID v7 RECOMMENDED)</td>
</tr>

<tr>
<td><tt>entityId</tt></td>
<td>string</td>
<td>REQUIRED</td>
<td>Identifier of the revoked entity</td>
</tr>

<tr>
<td><tt>revokedAt</tt></td>
<td>string</td>
<td>REQUIRED</td>
<td>ISO 8601 UTC timestamp of revocation</td>
</tr>

<tr>
<td><tt>reason</tt></td>
<td>string</td>
<td>REQUIRED</td>
<td>One of: &quot;compromised&quot;, &quot;policy_violation&quot;, &quot;operator_request&quot;, &quot;expired&quot;, &quot;rotation&quot;</td>
</tr>

<tr>
<td><tt>scope</tt></td>
<td>string</td>
<td>REQUIRED</td>
<td>&quot;cascade&quot; or &quot;single&quot;</td>
</tr>

<tr>
<td><tt>issuedBy</tt></td>
<td>string</td>
<td>REQUIRED</td>
<td>Identifier of the revoking authority</td>
</tr>

<tr>
<td><tt>alg</tt></td>
<td>string</td>
<td>REQUIRED (production)</td>
<td>Signing algorithm identifier (&quot;EdDSA&quot; or &quot;ES256&quot;). Enables algorithm-agnostic verification.</td>
</tr>

<tr>
<td><tt>kid</tt></td>
<td>string</td>
<td>REQUIRED (production)</td>
<td>Key identifier for the revoking authority's signing key. Enables key selection during rotation.</td>
</tr>

<tr>
<td><tt>signature</tt></td>
<td>string</td>
<td>REQUIRED (production), OPTIONAL (development)</td>
<td>Signature over all other fields (JCS-canonical, excluding <tt>signature</tt> itself) using the algorithm specified in <tt>alg</tt>.</td>
</tr>
</tbody>
</table><t>Revocation event signing keys are discovered via the same key discovery
mechanism as IPC signing keys (Section 3.6). The <tt>issuedBy</tt> field
corresponds to the <tt>serviceId</tt> in the key discovery endpoint. If no
key discovery endpoint is available for the revoking authority, signature
verification is deployment-specific.</t>
<t>Processing rules:</t>

<ul spacing="compact">
<li>When <tt>scope</tt> is <tt>cascade</tt>, enforcement points MUST reject any IPC where
the revoked <tt>entityId</tt> appears anywhere in the <tt>delegationChain</tt>.</li>
<li>When <tt>scope</tt> is <tt>single</tt>, only IPCs where the revoked entity is the
<tt>entityId</tt> (current executor) are rejected.</li>
<li>Enforcement points SHOULD verify the <tt>signature</tt> before acting on the
event. Enforcement points operating in production MUST reject revocation events
without a valid signature unless the event source is authenticated by the
transport layer (e.g., mTLS-authenticated event producer). In development
environments, unsigned revocation events MAY be accepted with a logged
warning.</li>
<li>Duplicate <tt>eventId</tt> values MUST be ignored (idempotent processing).
Enforcement points MUST maintain deduplication state for <tt>eventId</tt>
values for at least 24 hours after first receipt. Revocation events
with <tt>revokedAt</tt> timestamps older than 24 hours from the current time
SHOULD be rejected as stale, preventing replay of old revocation events.
Deployments requiring a different deduplication window MUST document
their chosen value and ensure it exceeds the maximum propagation delay
in their event infrastructure.</li>
<li>Enforcement points MUST NOT remove revocation state until the entity's
maximum IPC TTL has elapsed after <tt>revokedAt</tt>.</li>
</ul>
<t>Enforcement points SHOULD garbage-collect revocation state entries whose
<tt>revokedAt</tt> timestamp is older than the maximum configured IPC TTL. Once
all IPCs that could reference a revoked entity have expired, the
revocation entry serves no enforcement purpose and MAY be removed from
the active revocation set (while retaining it in audit logs).</t>
<t>Guidance on scope selection:
- Use <tt>cascade</tt> when the entity is compromised or its key material
  may be leaked - all downstream authority derived from this entity
  is suspect.
- Use <tt>single</tt> when the entity's current execution should be stopped
  but its prior delegations remain trustworthy (e.g., an agent that
  completed its task and should not accept new requests, but its
  sub-agents are still valid). With <tt>single</tt>, the entity MAY still
  appear as an intermediate in other IPCs' <tt>delegationChain</tt> without
  causing rejection.</t>
</section>

<section anchor="propagation-guarantees"><name>Propagation Guarantees</name>
<t>This specification does not mandate a specific propagation latency.
However, implementations SHOULD target sub-5-second propagation to
minimize the window of exposure. The short IPC TTL (RECOMMENDED default:
&lt;= 5 minutes; deployment-configurable) provides a hard upper bound on
exposure regardless of revocation propagation timing.</t>
</section>

<section anchor="partition-tolerance"><name>Partition Tolerance</name>
<t>During network partitions where revocation events cannot be delivered,
enforcement points MUST continue to validate IPC expiry (<tt>expiresAt</tt>).
The short TTL ensures that even without revocation delivery, compromised
IPCs expire naturally within the configured window.</t>
</section>

<section anchor="revocation-checking-interface"><name>Revocation Checking Interface</name>
<t>Enforcement points that support revocation MUST implement one of the
following revocation checking mechanisms:</t>
<t><strong>Option A: Event-Driven (Push)</strong>
- Subscribe to the <tt>ipc-revocation</tt> topic (or equivalent event stream)
- Maintain a local revocation set in memory or shared cache
- Check every IPC's <tt>delegationChain</tt> against the local revocation set
- Reject if any entity in the chain appears in the revocation set</t>
<t><strong>Option B: Polling (Pull)</strong>
- Query a revocation status endpoint:
  <tt>GET /ipc/v1/revocation-status/{entityId}</tt>
- Response: <tt>{&quot;revoked&quot;: true/false, &quot;revokedAt&quot;: &quot;...&quot;, &quot;scope&quot;: &quot;...&quot;}</tt>
- Cache responses for no longer than the deployment's configured
  revocation cache TTL (RECOMMENDED default: 30 seconds)
- Check every IPC's <tt>delegationChain</tt> against cached status</t>
<t><strong>Option C: Expiry-Only (No Infrastructure)</strong>
- If no revocation infrastructure is deployed, enforcement points
  MUST rely solely on IPC expiry (<tt>expiresAt</tt>) for revocation
- This is acceptable only when TTLs are short (RECOMMENDED default:
  &lt;= 5 minutes)
- Deployments using this option MUST document the maximum exposure
  window in their security assessment</t>
<t>The revocation checking mechanism is deployment-specific. This specification
does not mandate a single mechanism but requires that deployments explicitly
choose and document which option they use.</t>
<t>When a revocation check determines that an entity is revoked, the
enforcement point MUST reject the IPC with <tt>entity_revoked</tt> regardless
of other validity checks.</t>
</section>
</section>

<section anchor="configuration-parameters"><name>Configuration Parameters</name>
<t>This section defines the configuration parameters referenced throughout
this specification, with normative defaults and constraints to ensure
interoperability between independent implementations.</t>
<table>
<thead>
<tr>
<th>Parameter</th>
<th>Type</th>
<th>Default</th>
<th>Normative Constraint</th>
<th>Description</th>
</tr>
</thead>

<tbody>
<tr>
<td><tt>maxDepth</tt></td>
<td>integer</td>
<td>5</td>
<td>MUST be &gt;= 1, MUST NOT exceed 10</td>
<td>Maximum delegation chain depth</td>
</tr>

<tr>
<td><tt>maxTTL</tt></td>
<td>duration</td>
<td>300s (5 min)</td>
<td>MUST be &gt;= 30s, MUST NOT exceed 600s (10 min)</td>
<td>Maximum IPC lifetime</td>
</tr>

<tr>
<td><tt>clockSkew</tt></td>
<td>duration</td>
<td>30s</td>
<td>MUST be &gt;= 5s, MUST NOT exceed 60s, MUST be less than <tt>maxTTL</tt></td>
<td>Clock skew tolerance for temporal checks. The constraint <tt>clockSkew &lt; maxTTL</tt> ensures that the skew tolerance does not exceed the token lifetime, which would undermine short-TTL replay protection.</td>
</tr>

<tr>
<td><tt>maxTokenAge</tt></td>
<td>duration</td>
<td>0 (disabled)</td>
<td>If &gt; 0, MUST be &gt;= <tt>maxTTL</tt></td>
<td>Maximum age from issuedAt (0 = disabled). The constraint <tt>maxTokenAge &gt;= maxTTL</tt> prevents false rejections: since a token's lifetime can be up to <tt>maxTTL</tt>, setting <tt>maxTokenAge</tt> below <tt>maxTTL</tt> would cause valid tokens to be rejected before their <tt>expiresAt</tt>. The <tt>maxTokenAge</tt> check provides value as a deployment-wide hard cap when verifiers and creators have different <tt>maxTTL</tt> configurations, or as defense against clock manipulation at the creator.</td>
</tr>

<tr>
<td><tt>replayCacheTTL</tt></td>
<td>duration</td>
<td>derived</td>
<td>MUST be &gt;= <tt>maxTTL</tt> + <tt>clockSkew</tt></td>
<td>Minimum replay cache entry lifetime</td>
</tr>

<tr>
<td><tt>keyDiscoveryCacheTTL</tt></td>
<td>duration</td>
<td>300s</td>
<td>MUST be &gt;= 30s, MUST NOT exceed 3600s</td>
<td>Key discovery response cache duration</td>
</tr>

<tr>
<td><tt>allowUnsignedForAudit</tt></td>
<td>boolean</td>
<td>false</td>
<td>N/A</td>
<td>Whether Trust Level 1 IPCs are accepted</td>
</tr>

<tr>
<td><tt>allowHMAC</tt></td>
<td>boolean</td>
<td>false</td>
<td>N/A</td>
<td>Whether HS256 (HMAC-SHA-256) is permitted</td>
</tr>

<tr>
<td><tt>isTerminalEnforcementPoint</tt></td>
<td>boolean</td>
<td>(required)</td>
<td>N/A</td>
<td>Whether this service always acts as terminal (true), never terminal (false). Dual-role services set false and check audience in application logic (Section 3.2 Step 12).</td>
</tr>

<tr>
<td><tt>isEnforcementPoint</tt></td>
<td>boolean</td>
<td>(required)</td>
<td>N/A</td>
<td>Whether this service makes authorization decisions</td>
</tr>

<tr>
<td><tt>serviceId</tt></td>
<td>string</td>
<td>(required)</td>
<td>MUST conform to identifier constraints (Section 2.2)</td>
<td>This service's IPC issuer identifier</td>
</tr>
</tbody>
</table><t>Configuration consistency constraints:</t>

<ul spacing="compact">
<li>If <tt>isTerminalEnforcementPoint</tt> is <tt>true</tt>, then <tt>isEnforcementPoint</tt>
MUST also be <tt>true</tt>. A terminal enforcement point is by definition an
enforcement point (it makes the final authorization decision).
Implementations MUST reject configurations where
<tt>isTerminalEnforcementPoint = true</tt> and <tt>isEnforcementPoint = false</tt>.</li>
<li>If the service is configured as a Trust Boundary (i.e., it re-signs
IPCs per Section 3.3), <tt>allowHMAC</tt> MUST be <tt>false</tt>. Implementations
MUST reject configurations where a Trust Boundary service has
<tt>allowHMAC = true</tt> (Section 2.6).</li>
</ul>
<t>Normative requirements:</t>

<ul spacing="compact">
<li>Implementations MUST use the default values specified above unless
explicitly overridden by deployment configuration.</li>
<li>Implementations MUST reject configuration where <tt>clockSkew</tt> is
greater than or equal to <tt>maxTTL</tt>. A clock skew tolerance that
equals or exceeds the token lifetime effectively disables temporal
validity as a security control.</li>
<li>Implementations MUST reject configuration where <tt>maxTTL</tt> exceeds 600
seconds (10 minutes). Deployments requiring longer lifetimes are outside
the threat model of this specification and MUST conduct a dedicated
security assessment documenting the increased replay and revocation
exposure windows.</li>
<li>Implementations MUST reject configuration where <tt>maxDepth</tt> exceeds 10.
Deeper chains indicate an architectural issue that should be resolved
by restructuring the service topology.</li>
<li>The <tt>replayCacheTTL</tt> MUST be at least <tt>maxTTL</tt> + <tt>clockSkew</tt> to
ensure replay detection covers the full validity window of any IPC.
Implementations SHOULD derive this automatically from the configured
<tt>maxTTL</tt> and <tt>clockSkew</tt> values rather than requiring manual configuration.</li>
<li>Two conformant implementations using default configuration values MUST
be able to interoperate without additional coordination.</li>
</ul>
</section>
</section>

<section anchor="protocol-bindings"><name>Protocol Bindings</name>
<t>IPCs MUST be transmitted only over channels protected by TLS 1.2
or later. Transmission of IPCs over unencrypted channels is a
protocol violation. This requirement applies to all protocol bindings
defined in this section.</t>
<t>Note on IETF Working Group scope: The HTTP binding (Section 6.1) is
the primary interoperability profile and falls within the scope of
IETF working groups focused on HTTP-based protocols (e.g., OAuth,
WIMSE). The gRPC (Section 6.2) and event-streaming (Section 6.3)
bindings address real deployment requirements in multi-protocol
platforms but may be considered for separate companion documents if
the adopting WG's charter is limited to HTTP. The IPC semantics
(format, verification algorithm, trust model) are protocol-agnostic;
only the transport encoding differs across bindings.</t>

<section anchor="http"><name>HTTP</name>
<t>The IPC is carried in the HTTP header:</t>

<artwork><![CDATA[Identity-Propagation-Context: <base64url-encoded IPC>
]]></artwork>
<t>Note: The <tt>X-</tt> prefix is not used, per RFC 6648 which deprecates custom header
prefixes for new headers.</t>
</section>

<section anchor="grpc"><name>gRPC</name>
<t>The IPC is carried in gRPC metadata:</t>

<artwork><![CDATA[identity-propagation-context: <base64url-encoded IPC>
]]></artwork>
</section>

<section anchor="event-streaming-kafka"><name>Event-Streaming (Kafka)</name>
<t>The IPC is carried in a record header:</t>

<artwork><![CDATA[Header key: identity-propagation-context
Header value: <base64url-encoded IPC>
]]></artwork>
</section>

<section anchor="protocol-transition-behavior"><name>Protocol Transition Behavior</name>
<t>When a service receives a request via one protocol and emits via another
(e.g., HTTP to gRPC, or gRPC to Kafka), this SHOULD be treated as a trust boundary unless the deployment's security model explicitly exempts it.
The service MUST:</t>

<ol spacing="compact">
<li>Verify the incoming IPC using the inbound protocol's binding</li>
<li>Execute the full verification algorithm (Section 3.2)</li>
<li>Re-sign the IPC (Section 3.3)</li>
<li>Emit the re-signed IPC using the outbound protocol's binding</li>
</ol>
<t>The verification and re-signing requirements apply regardless of the
protocol direction. A Kafka consumer that triggers an HTTP call to a
downstream service MUST verify the IPC from the Kafka record header
and propagate it in the HTTP <tt>Identity-Propagation-Context</tt> header.</t>
<t>Audience semantics apply at the terminal enforcement point regardless
of which protocol delivered the IPC.</t>
<t>IPC is a request-direction mechanism. Responses do not carry IPC.
Asynchronous callbacks or event-driven responses that initiate new
request chains require fresh IPC creation at the originating service.</t>
</section>

<section anchor="header-value-abnf"><name>Header Value ABNF</name>
<t>The <tt>Identity-Propagation-Context</tt> header value is defined by the
following ABNF [RFC5234]:</t>

<artwork><![CDATA[IPC-header-value = base64url-encoded-ipc
base64url-encoded-ipc = 1*4096base64url-char
base64url-char = ALPHA / DIGIT / "-" / "_"

; The decoded value is a JSON object conforming to the IPC
; schema defined in Section 2.1, serialized per Section 2.4.
]]></artwork>
<t>The header field MUST contain exactly one Base64url-encoded IPC.
Multiple IPCs in a single header value are not permitted.
If a request contains multiple <tt>Identity-Propagation-Context</tt> headers,
the verifier MUST reject with <tt>malformed_ipc</tt>.</t>
<t>Base64url encoding MUST be used without padding (per RFC 4648 Section 5).</t>
</section>

<section anchor="event-streaming-retention-considerations"><name>Event-Streaming Retention Considerations</name>
<t>In event-streaming systems (e.g., Kafka) where records are retained
beyond the IPC's <tt>expiresAt</tt>, consumers processing historical records
will encounter expired IPCs. The following guidance applies:</t>

<ul spacing="compact">
<li>During real-time processing, temporal validity (Step 8) MUST
be enforced normally.</li>
<li>During historical replay or reprocessing, enforcement points MAY
skip temporal validity checks (Step 8) while still validating
structural integrity and chain semantics. This mode MUST be
explicitly configured and MUST be limited to replay/audit scenarios.
Services operating in historical replay mode MUST NOT create new
IPCs (including fan-out IPCs) from records processed in this mode,
as doing so would circumvent expiry attenuation by minting fresh
tokens from expired authority.</li>
<li>Alternatively, deployments MAY strip IPC from records at retention
boundaries and rely on audit logs for historical provenance.</li>
</ul>
<t>Intermediate services that introduce queuing delays (e.g., message
broker consumers with processing backlogs) SHOULD verify temporal
validity before forwarding and SHOULD reject IPCs with remaining
lifetime shorter than the expected downstream processing time.</t>
</section>
</section>

<section anchor="trust-levels"><name>Trust Levels</name>
<table>
<thead>
<tr>
<th>Level</th>
<th>Name</th>
<th>Description</th>
<th>Use Case</th>
</tr>
</thead>

<tbody>
<tr>
<td>1</td>
<td>Unsigned</td>
<td>No signature, audit trail only</td>
<td>Development, logging</td>
</tr>

<tr>
<td>2</td>
<td>Signed</td>
<td>HS256 or EdDSA (Ed25519) signature</td>
<td>Authorization, enforcement</td>
</tr>

<tr>
<td>3</td>
<td>Reserved</td>
<td>Reserved for future extension</td>
<td>N/A</td>
</tr>
</tbody>
</table><t>Note: Trust Level 3 is reserved for future specification of attested or
countersigned IPCs (e.g., hardware-attested or multi-party countersigned).
This version of the specification defines only Levels 1 and 2. Implementations
MUST NOT use Level 3 until a future revision defines its semantics.
Verifiers implementing this version of the specification MUST reject IPCs
with <tt>trustLevel</tt> values other than 1 or 2 (per Section 3.2, Step 4).
A future version that defines Trust Level 3 semantics will require
verifier upgrades; existing verifiers will safely reject Level 3 IPCs
rather than processing them with undefined behavior.</t>
<t>For Trust Level 1 (unsigned) IPCs:
- The <tt>alg</tt> claim MUST be set to the string <tt>&quot;none&quot;</tt>
- The <tt>kid</tt> claim MUST be absent or empty string
- The <tt>signature</tt> claim MUST be absent or empty string
- All other claims MUST be present and conform to Section 2.1
- Trust Level 1 IPCs MUST NOT be used for authorization decisions
  (Section 8.3) and exist solely for development, debugging, and
  audit trail purposes</t>
</section>

<section anchor="security-considerations"><name>Security Considerations</name>

<section anchor="replay-protection"><name>Replay Protection</name>
<t>IPCs SHOULD have short TTLs (RECOMMENDED default: &lt;= 5 minutes;
deployments MUST configure a maximum TTL appropriate to their threat model)
to limit replay windows. The <tt>sessionId</tt> claim provides request-level
uniqueness.</t>
<t>Enforcement points MUST maintain a replay cache keyed by <tt>sessionId</tt> for
at least the deployment's maximum configured IPC TTL. If a <tt>sessionId</tt>
has been seen before within the cache window, the IPC MUST be rejected
with <tt>token_replayed</tt>.</t>
<t>The replay cache SHOULD be shared across all instances of the same enforcement
point (e.g., via Redis or equivalent shared store) in horizontally-scaled
deployments. A local in-process cache is acceptable only for single-instance
deployments.</t>
<t>Without a shared replay cache, same-service replay within the TTL window
is possible from a different instance. The short TTL and audience binding
provide partial mitigation. Deployments that cannot deploy shared state
MUST document this as an accepted risk.</t>
<t>For Trust Level 2 deployments, the combination of REQUIRED <tt>audience</tt> and
<tt>sessionId</tt> replay cache provides defense-in-depth against both cross-service
and same-service replay attacks.</t>
<t>Trust boundary services that also make enforcement decisions (i.e.,
services fulfilling both the Trust Boundary and Enforcement Point
conformance classes from Section 11.3) MUST also maintain a replay
cache. A pure pass-through trust boundary that only re-signs without
making authorization decisions MAY skip replay checking.</t>
<t>Each IPC creation produces a unique <tt>sessionId</tt>. When an entity
sub-delegates (creating a new IPC with an extended chain), the child
IPC receives its own unique <tt>sessionId</tt>. The parent IPC's <tt>sessionId</tt>
and the child IPC's <tt>sessionId</tt> are independent values. Replay caches
operate per-IPC: they prevent the same IPC from being presented twice,
not multiple IPCs from the same original request.</t>
<t>For high-throughput deployments where a centralized replay cache creates
unacceptable latency or availability risk, implementations MAY use
probabilistic data structures (e.g., Bloom filters) as a RECOMMENDED
alternative. Probabilistic structures may produce false-positive rejections
but MUST NOT produce false negatives (missed replays). Deployments using
probabilistic replay detection MUST document the expected false-positive
rate in their operational assessment.</t>
<t>Note on retries: IPC is single-use per enforcement point within the
replay cache window. If a request times out and the client retries with
the same IPC, the enforcement point will reject the retry as
<tt>token_replayed</tt>. Retry scenarios require fresh IPC creation at the
platform boundary. Deployments using automatic retry policies (HTTP
retry-after, gRPC retry, Kafka consumer rebalancing) MUST ensure that
retried requests obtain fresh IPCs from the IPC Creator, or MUST
configure intermediate trust boundaries to skip replay detection
(acceptable when they only re-sign without making authorization
decisions).</t>
</section>

<section anchor="key-compromise"><name>Key Compromise</name>
<t>If a signing key is compromised:
1. Rotate the key immediately.
2. Publish revocation events for all IPCs signed by the compromised key.
3. Short TTL bounds the exposure window naturally.</t>
</section>

<section anchor="spoofing"><name>Spoofing</name>
<t>An unsigned IPC (Trust Level 1) MUST NOT be used for enforcement decisions.
Only signed (Level 2+) IPCs may be used for authorization.</t>
</section>

<section anchor="chain-forgery"><name>Chain Forgery</name>
<t>Because the IPC payload is immutable (only signature changes at re-signing),
an attacker cannot inject entries into the delegation chain without
invalidating the signature.</t>
</section>

<section anchor="single-signer-verification-model"><name>Single-Signer Verification Model</name>
<t>IPC uses a &quot;trust your immediate upstream&quot; model. At any given hop, only the
most recent signer's signature is present - previous signatures are replaced
during re-signing. This means:</t>

<ul spacing="compact">
<li>Service C can verify that Service B signed the IPC.</li>
<li>Service C CANNOT cryptographically verify that Service A signed it before B.</li>
<li>Service C trusts that B performed proper verification before re-signing.</li>
</ul>
<t>This is a deliberate design choice that enables local verification (each
service only needs its upstream's public key, not a full chain of keys).
The tradeoff is that compromise of a single intermediate service allows it
to forge or modify IPCs for all downstream services.</t>
<t>Mitigations:
- Short TTLs limit the window of exploitation.
- Revocation events enable rapid response to compromised intermediaries.
- Audit logs at each trust boundary provide forensic traceability even
  though the cryptographic proof is single-hop.</t>
<t>Important: IPC provides per-hop re-signing with transitive trust, not
end-to-end cryptographic proof of the full chain. Each trust boundary
verifies the upstream signature before re-signing, attesting (implicitly,
via its own signature) that it performed this verification. This is analogous to TLS certificate
chain validation - each hop trusts its immediate upstream. A compromised
intermediate can fabricate or alter the chain for downstream services.
IPC's security model relies on all trust boundaries being platform-operated
and within a single administrative domain. Cross-domain propagation where
intermediate trust boundaries are not fully trusted is explicitly out of
scope (Section 1.6).</t>
<t>In fan-out scenarios (Section 2.2), an intermediate service acts as both
Trust Boundary and IPC Creator. Downstream services cannot distinguish
a legitimately fan-out-created IPC from one fabricated by a compromised
intermediate. This is an inherent limitation of the single-trust-domain
model: all IPC Creators and Trust Boundaries are assumed to be
platform-operated and trustworthy. Deployments SHOULD monitor fan-out
services with heightened scrutiny and SHOULD log all IPC creation events
for forensic analysis.</t>
</section>

<section anchor="size-considerations"><name>Size Considerations</name>
<t>IPC size grows linearly with delegation chain depth due to the
<tt>delegationChain</tt> array. Deployments MUST consider transport-level limits:</t>

<ul spacing="compact">
<li>HTTP headers: Most proxies and servers limit individual header values to
8,192 bytes. A delegation chain of depth 10 with 64-character entity
identifiers produces an IPC of approximately 1,200 bytes (Base64url-encoded),
well within limits.</li>
<li>gRPC metadata: Default maximum metadata size is 8 KB per entry.</li>
<li>Kafka record headers: No protocol-level limit, but implementations commonly
limit total header size to 64 KB.</li>
</ul>
<t>The base64url-encoded IPC (as transmitted in protocol bindings) MUST NOT
exceed 4096 bytes. Verifiers MUST reject encoded IPCs exceeding this
limit before JSON parsing, to prevent resource exhaustion via oversized
tokens. This limit accommodates delegation chains of depth 10 with
128-character identifiers and reasonable extension claims while remaining
well within HTTP header size constraints.</t>
<t>The configured <tt>maxDepth</tt> serves as the primary mitigation for IPC size
growth. Deployments SHOULD set <tt>maxDepth</tt> to the minimum value that
accommodates legitimate delegation patterns (RECOMMENDED: &lt;= 5 for most
deployments).</t>
</section>

<section anchor="threat-model"><name>Threat Model</name>
<t>IPC identifies the following threat categories:</t>
<table>
<thead>
<tr>
<th>ID</th>
<th>Threat</th>
<th>Mitigation</th>
</tr>
</thead>

<tbody>
<tr>
<td>T1</td>
<td>Token Replay</td>
<td>Short TTL (&lt;= 5 min), sessionId uniqueness, TLS-only transport</td>
</tr>

<tr>
<td>T2</td>
<td>Key Compromise at Trust Boundary</td>
<td>Key rotation (Section 8.8), short TTL limits exposure, revocation events</td>
</tr>

<tr>
<td>T3</td>
<td>Delegation Chain Injection</td>
<td>Signature covers all claims; payload immutable during re-sign</td>
</tr>

<tr>
<td>T4</td>
<td>Circular Delegation</td>
<td>Explicit duplicate detection in delegationChain (Section 5.4)</td>
</tr>

<tr>
<td>T5</td>
<td>Depth Escalation</td>
<td>Configurable maxDepth enforcement at every trust boundary</td>
</tr>

<tr>
<td>T6</td>
<td>Revocation Delay</td>
<td>Dual-layer: event-streaming (sub-5s target) + short TTL hard bound</td>
</tr>

<tr>
<td>T7</td>
<td>Cross-Service Replay</td>
<td>Audience claim binding; REQUIRED for Trust Level 2+</td>
</tr>

<tr>
<td>T8</td>
<td>Intermediate Service Compromise</td>
<td>Single-signer model tradeoff (Section 8.5); mitigated by short TTL + revocation</td>
</tr>

<tr>
<td>T9</td>
<td>IPC Stripping</td>
<td>Fail-closed enforcement (Section 3.9); requests without IPC are rejected</td>
</tr>

<tr>
<td>T10</td>
<td>Downgrade to Trust Level 1</td>
<td>Enforcement points MUST reject unsigned IPCs (Section 8.10)</td>
</tr>

<tr>
<td>T11</td>
<td>Clock Skew Exploitation</td>
<td>Clock synchronization requirements (Section 8.9)</td>
</tr>

<tr>
<td>T12</td>
<td>Denial of Service via Large Chains</td>
<td>Token size limits (Section 8.6); maxDepth enforcement</td>
</tr>

<tr>
<td>T13</td>
<td>Confused Deputy at Trust Boundary</td>
<td>Trust boundaries MUST verify that the presenting service identity (mTLS cert, service account) matches the expected upstream for the IPC's issuer. Reject if mismatch.</td>
</tr>

<tr>
<td>T14</td>
<td>Compromised IPC Creator</td>
<td>IPC Creator requires highest security posture: HSM-protected signing keys, restricted access, audit logging of all creation events, maxTokenAge provides deployment-wide hard cap</td>
</tr>

<tr>
<td>T15</td>
<td>Token theft from memory/logs</td>
<td>Short TTLs limit replay window; TLS prevents network interception; deployments SHOULD sanitize IPC values from application logs; enforcement points SHOULD NOT log full IPC header values</td>
</tr>

<tr>
<td>T16</td>
<td>Colluding IPC Creator and Intermediate</td>
<td>A compromised Creator can fabricate chains; a colluding intermediate can re-sign without verifying. Combined, they can produce back-dated IPCs with arbitrary chains that pass downstream verification. Mitigation: IPC Creators and Trust Boundaries are assumed platform-operated (Section 1.6); HSM-protected keys at Creators (T14); <tt>maxTokenAge</tt> caps temporal exploitation; audit logging (Section 3.8) provides forensic detection. This threat is inherent to the single-trust-domain model.</td>
</tr>

<tr>
<td>T17</td>
<td>Time-of-Check-to-Time-of-Use (TOCTOU)</td>
<td>Between <tt>VERIFY-IPC</tt> returning ACCEPT and application code using the claims for authorization, semantic state may change (e.g., entity revoked, permissions altered). Mitigation: short TTLs (≤ 5 min) bound the window; applications performing asynchronous or deferred processing SHOULD re-verify temporal validity at point-of-use; revocation checking (Step 13) provides near-real-time state when infrastructure is deployed.</td>
</tr>

<tr>
<td>T18</td>
<td>Replay Cache Race Condition</td>
<td>In horizontally-scaled enforcement points, two requests with the same <tt>sessionId</tt> may arrive at different instances simultaneously, both passing the replay cache check before either writes. Mitigation: the race window is bounded by cache write propagation delay (typically &lt; 10ms for Redis); <tt>audience</tt> binding prevents cross-service exploitation; shared cache with atomic check-and-set operations (e.g., Redis <tt>SETNX</tt>) eliminates the window entirely. Deployments SHOULD use atomic cache operations for replay detection.</td>
</tr>

<tr>
<td>T19</td>
<td>Compromised Key Discovery Endpoint</td>
<td>An attacker who can serve forged key material at <tt>/.well-known/ipc-keys</tt> can inject arbitrary public keys, enabling full IPC forgery for any claimed issuer. This is distinct from T2 (key compromise) because no legitimate key is stolen — arbitrary keys are injected. Mitigations: serve key discovery endpoints over mTLS with client certificate validation restricting access to known platform services; implement key pinning where verifiers reject new keys that differ from previously-seen keys for the same issuer unless a rotation event is expected; monitor for unexpected key changes via audit logging; key discovery endpoints MUST be served over HTTPS only; deployments SHOULD restrict access to the platform's internal network.</td>
</tr>

<tr>
<td>T20</td>
<td>Cross-Cluster Replay</td>
<td>In deployments where a logical service (same <tt>audience</tt> value) spans multiple clusters with independent replay caches, an attacker can replay the same IPC at both clusters within the TTL window. The <tt>audience</tt> binding provides no protection because the replayed IPC targets the same audience. Mitigations: use a shared cross-cluster replay cache (e.g., globally-replicated Redis); reduce IPC TTL to minimize the exploitation window; deploy cluster-scoped audience identifiers (e.g., <tt>backend.cluster-a.example.com</tt>) when strict replay prevention is required across clusters. Deployments that cannot share replay state across clusters MUST document this as an accepted risk with the exposure window bounded by <tt>maxTTL</tt>.</td>
</tr>

<tr>
<td>T21</td>
<td>Compromised Service Mesh Sidecar</td>
<td>In sidecar deployments (Appendix D), the sidecar proxy holds IPC signing keys and shares the pod network namespace with the application container. A compromised sidecar (e.g., via container escape, supply chain attack on sidecar image, or control plane compromise) can forge or modify IPCs for all requests transiting that pod. This is a specialization of T8 (intermediate compromise) with a distinct attack surface: sidecars are often pulled from third-party registries, key material is injected via mesh control plane, and the shared network namespace enables request interception without network-level detection. Mitigations: verify sidecar image signatures and provenance; restrict signing key access to the sidecar process only (not the application container); monitor for unexpected IPC creation patterns from sidecar instances; use pod security policies to prevent container escapes. Note that HSM-protected signing keys (T14 mitigation) are generally impractical for sidecar deployments due to scale and ephemeral pod lifecycle.</td>
</tr>
</tbody>
</table></section>

<section anchor="key-rotation-procedure"><name>Key Rotation Procedure</name>
<t>When a trust boundary service rotates its signing key:</t>

<ol spacing="compact">
<li>Generate a new Ed25519 keypair.</li>
<li>Publish the new public key at the key discovery endpoint (Section 3.6)
with <tt>status: &quot;active&quot;</tt> and <tt>validFrom</tt> set to the current time.
The old key remains <tt>status: &quot;active&quot;</tt> during the transition.</li>
<li>Wait at least <tt>cacheMaxAge</tt> (from the key discovery response, or
the deployment's configured <tt>keyDiscoveryCacheTTL</tt>) to ensure all
downstream verifiers have fetched the new key.</li>
<li>Begin signing new IPCs with the new key. The old key is no longer
used for signing but remains accepted for verification.</li>
<li>Update the old key's status to <tt>&quot;retired&quot;</tt> and set <tt>validUntil</tt> to
the current time.</li>
<li>Downstream services MUST continue accepting IPCs signed by the
retired key until <tt>validUntil</tt> plus the maximum configured IPC TTL
(<tt>maxTTL</tt>) plus <tt>clockSkew</tt> has elapsed.</li>
</ol>
<t>During the rotation window (up to the cache TTL), downstream services MAY
receive IPCs signed by either the old or new key. Services MUST accept
both until the old key's <tt>validUntil</tt> plus the maximum configured IPC TTL
(Section 5.6 <tt>maxTTL</tt>) has elapsed.</t>
<t>Key rotation MUST NOT require coordination with upstream or downstream
services beyond the key discovery endpoint. This enables rolling restarts
and zero-downtime key rotation.</t>
<t>If a key is suspected compromised:</t>

<ol spacing="compact">
<li>Set the key status to <tt>&quot;compromised&quot;</tt> at the discovery endpoint.</li>
<li>Publish a revocation event with <tt>reason: &quot;rotation&quot;</tt> for any
IPCs that may have been signed by the compromised key.</li>
<li>Generate and publish a new key immediately.</li>
<li>Downstream services MUST reject IPCs signed by a compromised key
regardless of their expiry time.</li>
</ol>
</section>

<section anchor="clock-synchronization"><name>Clock Synchronization</name>
<t>IPC's temporal validity checks (issuedAt, expiresAt) require synchronized
clocks across trust boundary services. The following requirements apply:</t>

<ul spacing="compact">
<li>All services participating in IPC verification MUST synchronize their
clocks using NTP [RFC 5905] or an equivalent time synchronization
protocol.</li>
<li>Clock skew between any two adjacent trust boundary services SHOULD NOT
exceed the configured clock skew tolerance.</li>
<li>Verification implementations MUST apply a configurable clock skew
tolerance (RECOMMENDED default: 30 seconds) when checking <tt>issuedAt</tt>
and <tt>expiresAt</tt>. Deployments with tighter synchronization guarantees
MAY use a lower tolerance.</li>
<li>If a deployment cannot guarantee clock synchronization within the
configured tolerance, it MUST increase the tolerance accordingly and
MUST document the increased tolerance as a security risk.</li>
<li>Services MUST NOT issue IPCs with <tt>issuedAt</tt> derived from
an unsynchronized clock. If NTP synchronization is lost, the service
SHOULD reject incoming requests rather than issuing IPCs with
unreliable timestamps.</li>
</ul>
<t>Note: An IPC verified as valid at one trust boundary may expire before
reaching the next service (in-flight expiry). The clock skew tolerance
(RECOMMENDED default: 30 seconds) is designed to accommodate this
scenario for normal request propagation latency. Deployments with unusually high
inter-service latency (&gt; 10 seconds) SHOULD increase their clock skew
tolerance or reduce their IPC TTL to compensate.</t>
</section>

<section anchor="downgrade-attack-prevention"><name>Downgrade Attack Prevention</name>
<t>An attacker may attempt to downgrade IPC security by:</t>

<ol>
<li><t><strong>Stripping the IPC header entirely</strong>: Mitigated by fail-closed
enforcement (Section 3.9). Enforcement points MUST reject requests
without IPC in production.</t>
</li>
<li><t><strong>Replacing a signed IPC with an unsigned one (Trust Level 1)</strong>:
Enforcement points MUST reject Trust Level 1 IPCs for authorization
decisions (Section 8.3). A service configured for Trust Level 2
enforcement MUST NOT accept Trust Level 1 IPCs.</t>
</li>
<li><t><strong>Replaying an expired IPC from a weaker configuration</strong>: Temporal
validity checks reject expired IPCs regardless of other claims.</t>
</li>
<li><t><strong>Presenting an IPC signed by an unknown issuer</strong>: Services MUST
verify the IPC was signed by a known upstream issuer. Unknown issuers
MUST be rejected with <tt>signature_invalid</tt>.</t>
</li>
<li><t><strong>Forcing fallback to permissive mode</strong>: Permissive mode (Section 3.8)
MUST be a temporary deployment configuration. Implementations SHOULD
support a deployment-wide flag that disables permissive mode entirely
and SHOULD alert operators if permissive mode remains enabled beyond
a configured duration (RECOMMENDED: 7 days maximum).</t>
</li>
</ol>
</section>

<section anchor="denial-of-service-considerations"><name>Denial of Service Considerations</name>
<t>IPC introduces the following DoS attack surfaces:</t>

<ul>
<li><t><strong>Verification cost</strong>: Ed25519 signature verification is computationally
inexpensive (~50 microseconds). An attacker sending invalid IPCs
imposes minimal CPU cost per request. Rate limiting at the transport
layer (e.g., connection limits) is the primary mitigation.</t>
</li>
<li><t><strong>Key discovery amplification</strong>: An attacker presenting IPCs with many
distinct issuer values could trigger key discovery endpoint lookups.
Mitigations: cache resolved keys (max 300s), reject unknown issuers
immediately, rate-limit key discovery requests.</t>
</li>
<li><t><strong>Revocation event flooding</strong>: An attacker with revocation authority
could flood enforcement points with revocation events. Mitigations:
authenticate revocation event sources (signature verification),
deduplicate by eventId, rate-limit revocation submissions.</t>
</li>
<li><t><strong>Large delegation chains</strong>: Token size grows with chain depth.
Mitigations: maxDepth enforcement (RECOMMENDED &lt;= 5), transport-layer
header size limits provide a natural cap.</t>
</li>
</ul>
</section>

<section anchor="proof-of-possession"><name>Proof-of-Possession</name>
<t>IPC does not include a proof-of-possession mechanism binding the IPC
to the presenting entity. Possession of a valid IPC header value is
sufficient to present it. This is a deliberate design choice that enables
transparent propagation through middleware and sidecars without requiring
per-hop proof construction.</t>
<t>This is mitigated by:
1. Short TTLs limiting the replay window
2. The <tt>audience</tt> claim preventing cross-service use
3. The replay cache preventing same-service reuse
4. TLS preventing passive interception
5. The coexisting bearer token providing PoP when DPoP or mTLS is
   used at the transport layer</t>
<t>Deployments requiring stronger binding between IPC and the presenting
entity SHOULD use mTLS at the transport layer, which cryptographically
binds each request to the presenting service's certificate.</t>
</section>

<section anchor="bearer-token-and-ipc-binding"><name>Bearer Token and IPC Binding</name>
<t>IPC travels alongside the OAuth 2.0 bearer token (Section 1.7) but is
not cryptographically bound to it. An attacker who obtains both a valid
bearer token for Entity A and a valid IPC for Entity B could present
them together, creating a mismatch between transport-level authentication
and identity-chain claims.</t>
<t>Enforcement points MUST verify that the authenticated identity established
by the bearer token (e.g., the token's <tt>sub</tt>, <tt>azp</tt>, or <tt>client_id</tt> claim)
is consistent with the IPC's <tt>issuer</tt> field or identifies a known trust
boundary in the deployment's topology. If the bearer token identifies an
entity that is not a legitimate upstream issuer for the presented IPC,
the enforcement point MUST reject the request. The RECOMMENDED binding
verification procedure is:</t>

<ol spacing="compact">
<li>Extract the <tt>azp</tt> (authorized party) claim from the bearer token.
If <tt>azp</tt> is absent, fall back to <tt>client_id</tt>.</li>
<li>Compare the extracted value against the IPC's <tt>issuer</tt> field.</li>
<li>If the values match, binding is confirmed.</li>
<li>If the values do not match, check whether the bearer token's subject
identifies a known trust boundary service in the deployment's topology
(e.g., via a configured allowlist of trust boundary identifiers).</li>
<li>If neither check succeeds, REJECT the request.</li>
</ol>
<t>Deployments that use a different bearer token claim for binding
verification (e.g., a custom <tt>service_id</tt> claim) MUST document the
specific claim used and the comparison semantics in their deployment's
security architecture. Two implementations within the same deployment
MUST use the same binding claim to ensure consistent enforcement.</t>
<t>When the enforcement point cannot determine consistency (e.g., bearer
token lacks identifying claims, or the mapping between bearer token
subjects and IPC issuers is unavailable), it SHOULD log a warning and
MAY proceed based on deployment policy. Deployments operating in this
degraded mode MUST document the accepted risk.</t>
<t>In multi-hop chains, each intermediate service presents its own bearer
token (or mTLS identity) when calling the next service downstream. The
binding verification at each hop compares the immediate caller's
transport-level identity against the IPC's current <tt>issuer</tt> field
(which was set by the re-signing service at the previous hop). This
means binding verification is always between the direct caller and the
IPC's <tt>issuer</tt> — not between the original client and the IPC creator.</t>
<t>For opaque bearer tokens (where claims are not directly available),
enforcement points MUST use token introspection (RFC 7662) to obtain
the <tt>client_id</tt> or equivalent identifier for binding verification,
or rely on transport-level identity (mTLS) instead.</t>
<t>Deployments using mTLS or DPoP [RFC 9449] at the transport layer gain
stronger implicit binding: the transport-level identity is
cryptographically proven and SHOULD be compared against the IPC's
<tt>issuer</tt> to detect token-swapping attacks. When mTLS or DPoP [RFC 9449]
is available, enforcement points MUST use the transport-level identity
for binding verification rather than relying solely on bearer token
claims.</t>
</section>
</section>

<section anchor="privacy-considerations"><name>Privacy Considerations</name>
<t>IPCs contain identity information (entity IDs, delegation chains) that
reveals organizational structure, delegation relationships, and
operational patterns. The following privacy requirements apply:</t>

<section anchor="transport-protection"><name>Transport Protection</name>
<t>IPCs MUST only be transmitted over encrypted channels (TLS). Unencrypted
transmission exposes the full delegation chain to passive observers.</t>
</section>

<section anchor="delegation-chain-as-sensitive-metadata"><name>Delegation Chain as Sensitive Metadata</name>
<t>The <tt>delegationChain</tt> array reveals:
- Organizational hierarchy (who delegates to whom)
- Service naming conventions (internal service identifiers)
- Operational patterns (which agents are used for which tasks)
- Delegation depth as a proxy for system complexity</t>
<t>Deployments SHOULD consider using opaque or pseudonymous identifiers
rather than human-readable names in <tt>delegationChain</tt> entries when IPCs
may be exposed beyond the immediate trust domain (e.g., captured in
shared logging infrastructure, debugging tools, or error responses).</t>
<t>When opaque identifiers are used, a mapping to human-readable names
MUST be maintained by the platform operator for audit purposes but
SHOULD NOT be distributed to all services in the chain.</t>
</section>

<section anchor="logging-and-retention"><name>Logging and Retention</name>
<t>Section 3.8 requires normative audit logging of IPC verification events.
Deployments MUST establish retention policies for IPC audit logs that
balance forensic traceability against data minimization:</t>

<ul spacing="compact">
<li>Audit logs SHOULD retain <tt>sessionId</tt>, <tt>entityId</tt>, verification result,
and timestamp — not the full IPC payload.</li>
<li>Full IPC payloads (including <tt>delegationChain</tt>) MUST NOT be logged
unless required for active security investigation.</li>
<li>Log retention periods SHOULD be defined by deployment policy and
communicated to affected parties per applicable data protection
regulations.</li>
</ul>
</section>

<section anchor="data-minimization"><name>Data Minimization</name>
<t>Deployments SHOULD minimize the identity information included in IPCs
based on the principle of data minimization:</t>

<ul spacing="compact">
<li>Entity identifiers SHOULD NOT contain personally identifiable
information (PII) beyond what is necessary for enforcement decisions.</li>
<li>The <tt>delegationChain</tt> carries the minimum information needed for
cascade revocation and depth enforcement. Deployments that do not
require these features at enforcement points MAY truncate the chain
to include only the root and current entity (reducing metadata exposure)
while documenting the loss of cascade revocation capability.</li>
</ul>
</section>

<section anchor="correlation-risks"><name>Correlation Risks</name>
<t>The <tt>sessionId</tt> enables correlation of a single request across multiple
services. Deployments MUST NOT use <tt>sessionId</tt> for cross-request user
tracking. The RECOMMENDED use of UUID v7 (which contains a timestamp
component) means that <tt>sessionId</tt> values reveal approximate creation
time even without access to the <tt>issuedAt</tt> claim.</t>
<t>As noted in Section 2.1, using the same value for <tt>sessionId</tt> and
externally-visible trace identifiers (e.g., W3C Trace Context) is
NOT RECOMMENDED when trace context propagates beyond the trust domain
boundary.</t>
</section>
</section>

<section anchor="iana-considerations"><name>IANA Considerations</name>

<section anchor="header-registration"><name>Header Registration</name>
<t>Registration request for a new HTTP header field:</t>

<ul spacing="compact">
<li>Header Field Name: <tt>Identity-Propagation-Context</tt></li>
<li>Applicable Protocol: HTTP</li>
<li>Status: Standard</li>
<li>Structured Type: N/A (opaque base64url-encoded value; not a Structured
Field per RFC 8941)</li>
<li>Specification Document: This document, Section 6.1</li>
</ul>
<t>Note: gRPC metadata keys and Kafka record header keys do not have IANA
registries. The metadata key <tt>identity-propagation-context</tt> (Section 6.2) and
Kafka record header key <tt>identity-propagation-context</tt> (Section 6.3) are
defined by this specification and SHOULD be treated as reserved by
implementations that support IPC.</t>
</section>

<section anchor="claims-registration"><name>Claims Registration</name>
<t>This specification defines the following claims for use in IPCs. Should
a future profile encode IPC as a JWT (RFC 7519), these claims would be
registered in the &quot;JSON Web Token Claims&quot; registry. For this version, the
claims are defined locally within this specification.</t>
<table>
<thead>
<tr>
<th>Claim Name</th>
<th>Description</th>
<th>Change Controller</th>
<th>Specification</th>
</tr>
</thead>

<tbody>
<tr>
<td><tt>entityId</tt></td>
<td>Identity of executing entity</td>
<td>IESG</td>
<td>This document, Section 2.1</td>
</tr>

<tr>
<td><tt>entityType</tt></td>
<td>Classification (AUTONOMOUS/SUPERVISED/UTILITY/HUMAN/SYSTEM)</td>
<td>IESG</td>
<td>This document, Section 2.1</td>
</tr>

<tr>
<td><tt>authorizedBy</tt></td>
<td>Immediate delegating party</td>
<td>IESG</td>
<td>This document, Section 2.1</td>
</tr>

<tr>
<td><tt>delegationDepth</tt></td>
<td>Hops from root</td>
<td>IESG</td>
<td>This document, Section 2.1</td>
</tr>

<tr>
<td><tt>delegationChain</tt></td>
<td>Ordered provenance chain</td>
<td>IESG</td>
<td>This document, Section 2.1</td>
</tr>

<tr>
<td><tt>trustLevel</tt></td>
<td>Signature assurance level: 1 (unsigned), 2 (signed), 3 (reserved)</td>
<td>IESG</td>
<td>This document, Section 2.1</td>
</tr>
</tbody>
</table></section>

<section anchor="well-known-uri-registration"><name>Well-Known URI Registration</name>
<t>Registration request for a new well-known URI suffix per RFC 8615:</t>

<ul spacing="compact">
<li>URI Suffix: ipc-keys</li>
<li>Change Controller: IESG</li>
<li>Specification Document: This document, Section 3.6</li>
<li>Related Information: N/A</li>
</ul>
</section>

<section anchor="ipc-entity-type-registry"><name>IPC Entity Type Registry</name>
<t>This specification establishes the &quot;IPC Entity Type&quot; registry under the
&quot;Identity Propagation Context (IPC)&quot; heading. The registration policy
is &quot;Specification Required&quot; per RFC 8126.</t>
<t>Initial registry contents:</t>
<table>
<thead>
<tr>
<th>Value</th>
<th>Description</th>
<th>Change Controller</th>
<th>Reference</th>
</tr>
</thead>

<tbody>
<tr>
<td><tt>HUMAN</tt></td>
<td>A natural person authenticated via an identity provider</td>
<td>IESG</td>
<td>This document, Section 2.1</td>
</tr>

<tr>
<td><tt>SYSTEM</tt></td>
<td>Platform-initiated operation with no direct human trigger</td>
<td>IESG</td>
<td>This document, Section 2.1</td>
</tr>

<tr>
<td><tt>AUTONOMOUS</tt></td>
<td>Agent operating independently on behalf of a human principal</td>
<td>IESG</td>
<td>This document, Section 2.1</td>
</tr>

<tr>
<td><tt>SUPERVISED</tt></td>
<td>Agent requiring human approval for sensitive operations</td>
<td>IESG</td>
<td>This document, Section 2.1</td>
</tr>

<tr>
<td><tt>UTILITY</tt></td>
<td>Stateless service component invoked by other entities</td>
<td>IESG</td>
<td>This document, Section 2.1</td>
</tr>
</tbody>
</table><t>Registration requirements for new entity types:
- Value MUST be an uppercase ASCII string matching <tt>^[A-Z][A-Z0-9_]{1,31}$</tt>
- A specification document MUST define the semantics and intended use
- New values MUST NOT conflict semantically with existing registered types</t>
</section>
</section>

<section anchor="relationship-to-other-specifications"><name>Relationship to Other Specifications</name>
<table>
<thead>
<tr>
<th>Specification</th>
<th>Relationship</th>
</tr>
</thead>

<tbody>
<tr>
<td>RFC 8693 (Token Exchange)</td>
<td>Complementary - used at first hop; IPC carries identity through remaining chain</td>
</tr>

<tr>
<td>Transaction Tokens [I-D.draft-ietf-oauth-transaction-tokens]</td>
<td>Complementary - Transaction Tokens carry immutable authorization context (scope, transaction details) through a call chain unchanged. IPC carries the mutable delegation chain identity with per-hop re-signing. Txn-Tokens answer &quot;what is this transaction authorized to do?&quot;; IPC answers &quot;who initiated this request and through what delegation chain?&quot; Both may coexist in the same request: the Txn-Token in the <tt>Txn-Token</tt> header and the IPC in the <tt>Identity-Propagation-Context</tt> header.</td>
</tr>

<tr>
<td>RFC 6750 (Bearer Tokens)</td>
<td>IPC extends bearer token concepts with delegation chain and re-signing</td>
</tr>

<tr>
<td>RFC 7519 (JWT)</td>
<td>IPC uses JSON with JCS (RFC 8785) for canonical serialization rather than JWT's JWS structure. JWT claim names are intentionally distinct to avoid semantic collision.</td>
</tr>

<tr>
<td>Agent Identity Protocol (AIP) [I-D.draft-singla-agent-identity-protocol]</td>
<td>Complementary - AIP requires the agent to actively participate (generate keys, sign tokens); IPC requires nothing from the agent beyond a standard OAuth JWT. AIP is HTTP-only; IPC adds gRPC and event-streaming bindings.</td>
</tr>

<tr>
<td>WIMSE AI Agent Identity [I-D.draft-ni-wimse-ai-agent-identity]</td>
<td>Complementary - WIMSE defines agent identity bootstrapping and attestation; IPC defines how the resulting verified identity propagates through service chains after credential issuance.</td>
</tr>

<tr>
<td>AgentID Protocol [I-D.draft-gudlab-agentid-protocol]</td>
<td>Shared principle - both require cryptographic traceability to a human/organization root. IPC adds per-hop re-signing and cross-protocol propagation.</td>
</tr>

<tr>
<td>W3C Baggage</td>
<td>Complementary - Baggage for observability, IPC for enforcement</td>
</tr>

<tr>
<td>RFC 9449 (DPoP)</td>
<td>Complementary - DPoP provides proof-of-possession for OAuth tokens at the transport layer; IPC provides identity propagation through the service chain. When both are deployed, DPoP strengthens IPC-to-bearer-token binding (Section 8.13).</td>
</tr>

<tr>
<td>SPIFFE/SPIRE</td>
<td>Complementary - SPIFFE identifies workloads (pod-to-pod), IPC identifies the initiating entity and its delegation chain through those workloads</td>
</tr>

<tr>
<td>AI Agent Authentication Framework [I-D.draft-klrc-aiagent-auth]</td>
<td>Complementary - klrc composes SPIFFE (identity), WIMSE (credential), and OAuth (authorization) into a layered agent auth stack, using RFC 8693 token exchange for downstream delegation. IPC addresses klrc's &quot;Delegation (downstream)&quot; layer without per-hop STS interaction: after the agent authenticates and obtains delegated authority per klrc's model, IPC carries the verified delegation chain through multi-hop internal service calls. klrc's <tt>act</tt> claim is informational; IPC makes it enforceable with per-hop re-signing.</td>
</tr>

<tr>
<td>Google Cloud Agent Identity</td>
<td>Complementary (informative) - Google assigns per-agent SPIFFE identities with mTLS + DPoP credential binding, but does not specify delegation chain propagation through internal service hops. IPC fills this gap: after the agent authenticates with its SPIFFE credential at the platform boundary, IPC carries the full delegation chain through downstream services.</td>
</tr>
</tbody>
</table>
<section anchor="detailed-comparison-with-aip"><name>Detailed Comparison with AIP</name>
<t>The Agent Identity Protocol (AIP) is the most comprehensive existing
specification for AI agent identity. IPC and AIP operate at different
architectural layers:</t>
<table>
<thead>
<tr>
<th>Aspect</th>
<th>AIP</th>
<th>IPC</th>
</tr>
</thead>

<tbody>
<tr>
<td><strong>Purpose</strong></td>
<td>Agent proves its identity to a relying party</td>
<td>Platform carries verified identity through its internal chain</td>
</tr>

<tr>
<td><strong>Who creates the artifact?</strong></td>
<td>Agent self-issues Credential Tokens</td>
<td>Platform trust boundary creates IPC</td>
</tr>

<tr>
<td><strong>Agent involvement</strong></td>
<td>Active - agent implements the protocol</td>
<td>None - agent is not aware of IPC</td>
</tr>

<tr>
<td><strong>Re-signing at hops</strong></td>
<td>Not supported - agent-signed once</td>
<td>Re-signed at every trust boundary</td>
</tr>

<tr>
<td><strong>Protocol support</strong></td>
<td>HTTP only (Bearer/DPoP)</td>
<td>HTTP, gRPC, Kafka</td>
</tr>

<tr>
<td><strong>Verification model</strong></td>
<td>Registry lookup</td>
<td>Locally-verifiable - no per-request STS calls</td>
</tr>

<tr>
<td><strong>Registry dependency</strong></td>
<td>Required for Tier 2+</td>
<td>No registry needed</td>
</tr>
</tbody>
</table><t>Both support delegation chains, depth limits, expiry attenuation, cascade
revocation, and circular detection - confirming shared problem understanding
with different architectural solutions.</t>
<t><strong>When to use which:</strong></t>

<ul spacing="compact">
<li>Use <strong>AIP</strong> when the agent directly presents credentials to a relying party
and the relying party can verify against a registry.</li>
<li>Use <strong>IPC</strong> when identity must propagate transparently through platform
infrastructure services that the agent does not directly interact with,
especially across protocol boundaries (HTTP -&gt; gRPC -&gt; Kafka).</li>
<li>Use <strong>both</strong> when the agent authenticates at the platform boundary using AIP,
and the platform then propagates identity internally using IPC.</li>
</ul>
</section>

<section anchor="mapping-to-authorization-propagation-requirements"><name>Mapping to Authorization Propagation Requirements</name>
<t>Tallam [Tallam2026] formalizes &quot;authorization propagation&quot; as a workflow-level
property of multi-agent systems and derives seven structural requirements (R1-R7)
for any sufficient authorization architecture. IPC addresses these requirements
as follows:</t>
<table>
<thead>
<tr>
<th>Requirement</th>
<th>Description</th>
<th>IPC Coverage</th>
</tr>
</thead>

<tbody>
<tr>
<td>R1</td>
<td>Agent principals as first-class authorization subjects</td>
<td>[Full] <tt>entityId</tt> + <tt>entityType</tt> - agents are named, typed principals</td>
</tr>

<tr>
<td>R2</td>
<td>Delegation explicit, bounded, auditable</td>
<td>[Full] <tt>delegationChain</tt> + <tt>authorizedBy</tt> + re-signing proves each hop validated</td>
</tr>

<tr>
<td>R3</td>
<td>Authorization at every data retrieval boundary</td>
<td>[Full] Re-signing at every trust boundary; each service independently verifies</td>
</tr>

<tr>
<td>R4</td>
<td>Aggregation policies expressible and enforceable</td>
<td>[Partial] IPC provides identity trace; aggregation logic is delegated to the policy engine</td>
</tr>

<tr>
<td>R5</td>
<td>Authorization traces workflow-scoped and self-contained</td>
<td>[Full] IPC is a single, self-contained artifact carrying the full delegation chain, inspectable at any hop</td>
</tr>

<tr>
<td>R6</td>
<td>Temporal validity as policy decision</td>
<td>[Full] Configurable <tt>expiresAt</tt> + dual-layer revocation (event-streaming + expiry)</td>
</tr>

<tr>
<td>R7</td>
<td>Recovery traceable through synthesis graph</td>
<td>[Full] <tt>sessionId</tt> + <tt>delegationChain</tt> enable identification of all downstream results derived from a specific authorization</td>
</tr>
</tbody>
</table><t>IPC does not claim to solve aggregation inference (R4) in the general case -
this is acknowledged as an open research problem. However, IPC provides the
prerequisite infrastructure: a self-contained, per-hop verified
identity trace that makes the set of contributing sources inspectable after
the fact, which is necessary for any aggregation policy enforcement.</t>
</section>

<section anchor="conformance-classes"><name>Conformance Classes</name>
<t>This specification defines three conformance classes:</t>
<t><strong>IPC Creator</strong> (e.g., API Gateway):
- MUST create IPCs conforming to the format in Section 2
- MUST sign with EdDSA (Ed25519) for multi-hop deployments (Section 2.6)
- MUST enforce expiry attenuation for sub-delegated IPCs (Section 5.3)
- MUST set <tt>audience</tt> for Trust Level 2 IPCs (Section 2.1)
- MUST populate all REQUIRED claims
- MUST publish its own public key via the key discovery endpoint (Section 3.6)
- MUST support key discovery as a key publisher; SHOULD support key
  discovery as a key consumer if it also verifies upstream IPCs</t>
<t><strong>Trust Boundary</strong> (e.g., intermediate service that re-signs):
- MUST execute the verification algorithm (Section 3.2) before re-signing
- MUST execute the re-signing algorithm (Section 3.3)
- MUST NOT modify any claims other than <tt>issuer</tt>, <tt>kid</tt>, and <tt>signature</tt>
- MUST reject IPCs that fail any verification step
- MUST maintain upstream public keys (via Section 3.6 or static config)
- MUST publish its own public key via the key discovery endpoint (Section 3.6)
- MUST support key discovery as both a key consumer (querying upstream
  endpoints) and a key publisher (exposing own keys)</t>
<t><strong>Enforcement Point</strong> (e.g., terminal service):
- MUST execute the verification algorithm (Section 3.2)
- MUST enforce all applicable rules from Section 5
- MUST check revocation state if revocation infrastructure is deployed
- MUST maintain a replay cache for <tt>sessionId</tt> (Section 8.1)
- MUST reject Trust Level 1 IPCs for authorization decisions
- MAY skip re-signing if it is the terminal service in the chain</t>
<t>An implementation MAY fulfill multiple conformance classes simultaneously
(e.g., a service that both re-signs and makes enforcement decisions).</t>
</section>
</section>


</middle>

<back>

<section anchor="acknowledgements"><name>Acknowledgements</name>
<t>The authors thank the reviewers who provided feedback on early drafts of this
specification.</t>
</section>

<section anchor="references"><name>References</name>

<section anchor="normative-references"><name>Normative References</name>

<ul spacing="compact">
<li>[RFC 2119] Bradner, S., &quot;Key words for use in RFCs to Indicate Requirement Levels&quot;, March 1997.</li>
<li>[RFC 4648] Josefsson, S., &quot;The Base16, Base32, and Base64 Data Encodings&quot;, October 2006.</li>
<li>[RFC 5234] Crocker, D. and P. Overell, &quot;Augmented BNF for Syntax Specifications: ABNF&quot;, STD 68, January 2008.</li>
<li>[RFC 5905] Mills, D., et al., &quot;Network Time Protocol Version 4: Protocol and Algorithms Specification&quot;, June 2010.</li>
<li>[RFC 6648] Saint-Andre, P., et al., &quot;Deprecating the 'X-' Prefix and Similar Constructs in Application Protocols&quot;, June 2012.</li>
<li>[RFC 6750] Jones, M., et al., &quot;OAuth 2.0 Bearer Token Usage&quot;, October 2012.</li>
<li>[RFC 7519] Jones, M., et al., &quot;JSON Web Token (JWT)&quot;, May 2015.</li>
<li>[RFC 7518] Jones, M., &quot;JSON Web Algorithms (JWA)&quot;, May 2015.</li>
<li>[RFC 8037] Liusvaara, I., &quot;CFRG Elliptic Curve Diffie-Hellman (ECDH) and Signatures in JSON Object Signing and Encryption (JOSE)&quot;, January 2017.</li>
<li>[RFC 8174] Leiba, B., &quot;Ambiguity of Uppercase vs Lowercase in RFC 2119 Key Words&quot;, May 2017.</li>
<li>[RFC 8259] Bray, T., &quot;The JavaScript Object Notation (JSON) Data Interchange Format&quot;, STD 90, December 2017.</li>
<li>[RFC 8615] Nottingham, M., &quot;Well-Known Uniform Resource Identifiers (URIs)&quot;, May 2019.</li>
<li>[RFC 8693] Jones, M., et al., &quot;OAuth 2.0 Token Exchange&quot;, January 2020.</li>
<li>[RFC 8785] Rundgren, A., et al., &quot;JSON Canonicalization Scheme (JCS)&quot;, June 2020.</li>
<li>[RFC 9110] Fielding, R., et al., &quot;HTTP Semantics&quot;, June 2022.</li>
</ul>
</section>

<section anchor="informative-references"><name>Informative References</name>

<ul spacing="compact">
<li>[RFC 7515] Jones, M., et al., &quot;JSON Web Signature (JWS)&quot;, May 2015.</li>
<li>[RFC 7662] Richer, J., &quot;OAuth 2.0 Token Introspection&quot;, October 2015.</li>
<li>[RFC 6749] Hardt, D., &quot;The OAuth 2.0 Authorization Framework&quot;, October 2012.</li>
<li>[RFC 8126] Cotton, M., et al., &quot;Guidelines for Writing an IANA Considerations Section in RFCs&quot;, June 2017.</li>
<li>[RFC 9449] Fett, D., et al., &quot;OAuth 2.0 Demonstrating Proof of Possession (DPoP)&quot;, September 2023.</li>
<li>[RFC 8610] Birkholz, H., et al., &quot;Concise Data Definition Language (CDDL): A Notational Convention to Express Concise Binary Object Representation (CBOR) and JSON Data Structures&quot;, June 2019.</li>
<li>[RFC 9562] Davis, K., et al., &quot;Universally Unique IDentifiers (UUIDs)&quot;, May 2024.</li>
<li>[RFC 8032] Josefsson, S. and I. Liusvaara, &quot;Edwards-Curve Digital Signature Algorithm (EdDSA)&quot;, January 2017.</li>
<li>[RFC 8941] Nottingham, M. and P-H. Kamp, &quot;Structured Field Values for HTTP&quot;, February 2021.</li>
<li>[I-D.draft-singla-agent-identity-protocol] Singla, P., &quot;Agent Identity Protocol (AIP): Decentralized Identity and Delegation for AI Agents&quot;, Work in Progress, Internet-Draft, draft-singla-agent-identity-protocol-03, June 2026.</li>
<li>[I-D.draft-ni-wimse-ai-agent-identity] Ni, Y. and C. P. Liu, &quot;WIMSE Applicability for AI Agents&quot;, Work in Progress, Internet-Draft, draft-ni-wimse-ai-agent-identity-02, February 2026.</li>
<li>[I-D.draft-gudlab-agentid-protocol] &quot;AgentID Protocol&quot;, Work in Progress, Internet-Draft.</li>
<li>[I-D.draft-klrc-aiagent-auth] Kataria, L., et al., &quot;AI Agent Authentication and Authorization&quot;, Work in Progress, Internet-Draft, draft-klrc-aiagent-auth-00, March 2026. Note: Informational draft (Ping Identity, AWS, Zscaler, Defakto) composing SPIFFE, WIMSE, OAuth, and OIDC for agent authentication. Thesis: existing standards are sufficient for agent auth; no new protocol needed.</li>
<li>[I-D.draft-ietf-oauth-transaction-tokens] Tulshibagwale, A., Fletcher, G., and P. Kasselman, &quot;Transaction Tokens&quot;, Work in Progress, Internet-Draft, draft-ietf-oauth-transaction-tokens-09, July 2026. Note: OAuth WG adopted draft (In WG Last Call) defining short-lived, transaction-bound JWTs for propagating immutable authorization context through call chains within a Trust Domain.</li>
<li>[GCP-Agent-Identity] Google Cloud, &quot;Agent Identity overview&quot;, <eref target="https://cloud.google.com/iam/docs/agent-identity-overview">https://cloud.google.com/iam/docs/agent-identity-overview</eref>, 2026.</li>
<li>[Tallam2026] Tallam, K., &quot;Authorization Propagation in Multi-Agent AI Systems: Identity Governance as Infrastructure&quot;, arXiv:2605.05440, May 2026. Note: This is an arXiv preprint that has not undergone formal peer review. It is cited for its problem formalization (R1-R7 requirements), not as a normative authority.</li>
<li>[SPIFFE] CNCF, &quot;Secure Production Identity Framework for Everyone&quot;</li>
<li>[W3C-Baggage] W3C, &quot;Baggage Specification&quot;</li>
</ul>
</section>
</section>


</back>

</rfc>
