| Internet-Draft | Identity Propagation Context | July 2026 |
| Sharma | Expires 24 January 2027 | [Page] |
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).¶
Unlike the act claim in RFC 8693, which is explicitly designated as
"informational only and not to be considered in access control decisions," 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.¶
This Internet-Draft is submitted in full conformance with the provisions of BCP 78 and BCP 79.¶
Internet-Drafts are working documents of the Internet Engineering Task Force (IETF). Note that other groups may also distribute working documents as Internet-Drafts. The list of current Internet-Drafts is at https://datatracker.ietf.org/drafts/current/.¶
Internet-Drafts are draft documents valid for a maximum of six months and may be updated, replaced, or obsoleted by other documents at any time. It is inappropriate to use Internet-Drafts as reference material or to cite them other than as "work in progress."¶
This Internet-Draft will expire on 24 January 2027.¶
Copyright (c) 2026 IETF Trust and the persons identified as the document authors. All rights reserved.¶
This document is subject to BCP 78 and the IETF Trust's Legal Provisions Relating to IETF Documents (https://trustee.ietf.org/license-info) in effect on the date of publication of this document. Please review these documents carefully, as they describe your rights and restrictions with respect to this document. Code Components extracted from this document must include Revised BSD License text as described in Section 4.e of the Trust Legal Provisions and are provided without warranty as described in the Revised BSD License.¶
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:¶
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?¶
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.¶
This problem has been independently formalized as "authorization propagation" in the academic literature [Tallam2026].¶
Existing mechanisms fall short:¶
act claims but
explicitly limits them to "informational only" 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 act claims can be forwarded downstream,
but intermediate services cannot independently attest their verification
without producing a new token via the STS.¶
This specification defines:¶
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.¶
The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "NOT RECOMMENDED", "MAY", and "OPTIONAL" in this document are to be interpreted as described in BCP 14 [RFC2119] [RFC8174] when, and only when, they appear in all capitals, as shown here.¶
IPC is designed for identity propagation in multi-hop service chains within a single trust domain or platform. It is NOT suitable for:¶
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.¶
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.¶
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.¶
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.¶
IPC intentionally carries identity and delegation context, 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.¶
IPC uses distinct claim names (entityId, issuer) rather than JWT registered
names (sub, iss) to avoid semantic confusion when IPCs coexist with
JWTs in the same request context, and because IPC's issuer semantics differ
from JWT's iss - in IPC, the issuer changes at every re-signing hop.¶
The IPC is a signed JSON object with the following claims:¶
| Claim | Type | Required | Description |
|---|---|---|---|
version
|
string | REQUIRED | IPC specification version (e.g., "1.0") |
entityId
|
string | REQUIRED | Identifier of the entity currently executing |
entityType
|
string | REQUIRED | AUTONOMOUS, SUPERVISED, UTILITY, HUMAN, or SYSTEM |
authorizedBy
|
string | REQUIRED | Identifier of the immediate delegating party |
delegationDepth
|
integer | REQUIRED | Number of delegation hops from root. MUST equal delegationChain.length - 1
|
delegationChain
|
array | REQUIRED | Ordered list of entity identifiers from root to current |
audience
|
string | REQUIRED (Level 2+), OPTIONAL (Level 1) | Intended terminal enforcement point service identifier. Verified only by the final service in the chain (not intermediate trust boundaries). Prevents cross-service replay attacks. |
sessionId
|
string | REQUIRED | 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. |
issuedAt
|
string | REQUIRED | ISO 8601 timestamp (MUST be UTC, trailing "Z", no fractional seconds). Format: e.g., 2026-07-17T08:00:00Z (exactly 20 characters) |
expiresAt
|
string | REQUIRED | ISO 8601 timestamp (MUST be UTC, trailing "Z", no fractional seconds). Format: e.g., 2026-07-17T08:00:00Z (exactly 20 characters) |
issuer
|
string | REQUIRED | Identifier of the service that signed this IPC |
trustLevel
|
integer | REQUIRED | 1 (unsigned), 2 (signed), or 3 (reserved for future use) |
alg
|
string | REQUIRED | Signing algorithm identifier from the IANA JOSE Algorithms registry: "EdDSA" (default for multi-hop chains; MUST use curve Ed25519 per RFC 8037), "ES256" (FIPS-compatible alternative for multi-hop chains; ECDSA with P-256 per RFC 7518 Section 3.4; see Section 2.6.1), "HS256" (single-hop only; MUST NOT be used when the IPC will cross more than one trust boundary), or "none" (Trust Level 1 only). Deployments MUST choose either EdDSA or ES256 for their asymmetric algorithm; mixing within a chain is prohibited. |
kid
|
string | REQUIRED (Level 2+) | Key identifier of the signing key. Enables key selection during rotation. The kid format is deployment-specific. The RECOMMENDED format is <issuer>/<key-version> (e.g., api-gateway/key-1). |
signature
|
string | REQUIRED (Level 2+) | Cryptographic signature over all other claims |
The entityType values are defined as follows:¶
| Type | Definition |
|---|---|
HUMAN
|
A natural person authenticated via an identity provider |
SYSTEM
|
Platform-initiated operation with no direct human trigger |
AUTONOMOUS
|
Agent operating independently on behalf of a human principal |
SUPERVISED
|
Agent requiring human approval for sensitive operations |
UTILITY
|
Stateless service component invoked by other entities |
The entityType is assigned at IPC creation time based on the authenticated
entity's registration in the platform. It MUST NOT change during re-signing.¶
The entityType values are case-sensitive. Verifiers MUST perform exact
string matching against the defined values (AUTONOMOUS, SUPERVISED,
UTILITY, HUMAN, SYSTEM).¶
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.¶
Timestamps MUST NOT include fractional seconds. The canonical format is
exactly YYYY-MM-DDTHH:MM:SSZ (20 characters). This ensures JCS
canonicalization produces identical bytes regardless of implementation,
preventing signature verification failures during re-signing.¶
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.¶
delegationDepth MUST equal delegationChain.length - 1. If mismatched,
the IPC MUST be rejected.¶
Rationale for carrying both: delegationDepth enables fast policy rejection
(single integer comparison against maxDepth) without requiring the verifier
to parse and compute the length of the delegationChain array. The redundancy
also serves as an integrity check - a mismatch indicates tampering or
corruption.
- authorizedBy MUST equal delegationChain[delegationChain.length - 2]
when the chain has two or more entries. When delegationChain has
exactly one entry (no delegation), authorizedBy MUST equal entityId.
If mismatched, the IPC MUST be rejected.¶
Rationale for carrying authorizedBy separately: it enables fast
authorization policy lookups ("who authorized this entity?") without
requiring the verifier to parse and index the delegationChain array.
The redundancy also serves as an integrity check.¶
The delegationChain 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.¶
audience is present, it identifies the final enforcement point in the chain, not each intermediate hop. Trust boundary services that re-sign and forward the IPC MUST NOT verify audience 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 audience value. If it does not match, the IPC MUST be rejected.¶
For Trust Level 2+ IPCs, audience MUST be a non-empty string
identifying the terminal enforcement point. An empty string value
for audience 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 audience value). Fan-out IPC creation MUST NOT be
performed on incoming HS256-signed IPCs. If the incoming IPC has alg
of "HS256", 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 alg will therefore always be "EdDSA" 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 delegationChain, entityId, entityType,
authorizedBy, delegationDepth, version, alg, and trustLevel
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 issuedAt, expiresAt, audience,
sessionId, issuer, kid, and signature differ in each fan-out
IPC.
The issuedAt 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 expiresAt MUST be set to min(now() + config.maxTTL, original.expiresAt)
to maintain expiry attenuation (the fan-out IPC cannot outlive the
original). The audience, sessionId, issuer, kid, and signature
differ in each fan-out IPC. Each fan-out IPC MUST have a unique
sessionId to avoid replay cache conflicts at shared downstream
infrastructure.¶
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 sessionId to its own replay cache upon verification.
Each fan-out IPC carries a new, independent sessionId that is
consumed by downstream enforcement points' replay caches. The original
IPC's replay detection and each downstream path's replay detection
operate independently.¶
Fan-out security constraints:
- The fan-out service MUST be configured with an explicit allowlist
of downstream audience 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.¶
Rationale for regenerating issuedAt: If the original issuedAt were
preserved, the maxTokenAge 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 maxTokenAge checks at downstream
services despite being freshly created. Regenerating issuedAt gives
each fan-out IPC a fresh lifetime budget while expiresAt attenuation
ensures it cannot outlive the original authority.¶
A single IPC cannot target
multiple terminal enforcement points.
- The first entry in delegationChain 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., "1.x"),
a verifier implementing version "1.0" MUST accept tokens with version
"1.0" or any later minor version (e.g., "1.1", "1.2"), ignoring
unrecognized claims per the extension mechanism above. This enables
rolling upgrades without fleet-wide flag-day deployments.¶
Minor version increments (e.g., "1.0" to "1.1") 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., "1.x" to "2.0"). This ensures that a "1.0" verifier can safely process any "1.x" token without creating a security gap where newer security properties are silently dropped.¶
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 version field indicates the minimum set of
claims a verifier must understand. Unrecognized claims MUST NOT be
included in authorization decisions.¶
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., x-<org>-<claim>) to avoid
collision with future standard claims or other deployments' extensions.¶
entityId, entries in delegationChain, authorizedBy,
issuer, audience) MUST conform to the following constraints:¶
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.¶
Administrative (enforced by deployment policy, not by verifiers):
- MUST be unique within the deployment's trust domain
- RECOMMENDED format: URI (e.g., mailto:operator@example.com,
urn:service:api-gateway), email address, or deployment-scoped
identifier with a namespace prefix (e.g., agent:energy-optimizer)
- Using email-format identifiers for HUMAN entities (e.g.,
operator@example.com) and namespace-prefixed identifiers for agents
(e.g., agent:energy-optimizer, service:api-gateway) is RECOMMENDED
for human readability in audit logs.¶
{
"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(...)"
}
¶
The signature is computed over a canonical serialization of all claims EXCLUDING
the signature field itself. The process is:¶
signature.¶
alg claim.¶
signature claim.¶
The issuer field IS included in the signature input. This means that when a
trust boundary re-signs the IPC:
1. The issuer field is updated to the re-signing service's identifier.
2. A new signature is computed over all claims (including the new issuer)
using the re-signing service's private key.
3. The new signature replaces the old signature value.¶
This ensures that each signature cryptographically binds the issuer to the IPC content, proving which service validated and re-signed it.¶
The signature input includes the current issuer and kid values at
the time of signing. When a trust boundary re-signs, it updates both
issuer and kid 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.¶
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.¶
The integer claims delegationDepth and trustLevel MUST be serialized
as JSON integers without decimal points (e.g., 2, not 2.0).
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.¶
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.¶
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.¶
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:¶
Deployments that begin with single-hop HMAC and later add trust boundaries MUST migrate to EdDSA before enabling re-signing.¶
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.¶
When ES256 is used:
- The alg claim MUST be set to "ES256"
- Key discovery endpoints MUST serve keys with "kty": "EC",
"crv": "P-256" 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)¶
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.¶
The verification algorithm (Section 3.2, Step 5) MUST accept "ES256" as a valid algorithm value when the deployment is configured for the FIPS profile. Step 6g dispatches to ECDSA-P256.Verify for "ES256" tokens.¶
Services configured as Trust Boundaries (i.e., services that re-sign IPCs)
MUST have allowHMAC = false. Implementations MUST reject configurations
where a service is assigned the Trust Boundary role (Section 11.3) and
allowHMAC = true. 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.¶
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:¶
JWS Compact Serialization (RFC 7515 Section 7.1):¶
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 issuer and kid 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¶
IPC's approach requires:
1. Parse the JSON object
2. Modify issuer and kid
3. Compute JCS canonical bytes (excluding signature)
4. Sign and set the signature field¶
The difference is not in operation count but in verification determinism: 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.¶
JWS JSON Serialization (RFC 7515 Section 7.2):¶
JWS JSON Serialization could in principle support IPC's re-signing model
by placing mutable fields (issuer, kid) in the unprotected header.
However, this approach has practical drawbacks:¶
Unprotected header semantics vary across implementations. 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.¶
Base64url-within-base64url nesting. 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.¶
Single-object manipulation model. IPC middleware operates on one
JSON object throughout the verify-modify-sign cycle. JWS JSON
Serialization requires managing separate protected, header,
payload, and signature fields with different encoding rules,
increasing the surface area for implementation bugs in the re-signing
path.¶
The core design principle: 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.¶
Tradeoffs acknowledged:¶
A service receiving an IPC MUST execute the following steps in order. The service MUST reject at the first step that fails.¶
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.
¶
A trust boundary that forwards requests downstream MUST re-sign the IPC after successful verification. The re-signing algorithm is:¶
Trust boundaries MUST NOT re-sign IPCs with alg set to "HS256".
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).¶
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>
¶
The IPC payload (all claims except issuer, kid, and signature) MUST NOT be
modified during re-signing. Only the issuer, kid, and signature fields change during re-signing.¶
Each trust boundary service maintains: - Its own signing key (private) - The public key of the upstream issuer (for verification)¶
A key discovery endpoint (similar to OIDC .well-known/jwks.json) is
RECOMMENDED for dynamic key retrieval.¶
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:¶
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.¶
The key discovery endpoint MUST be available at:¶
GET /.well-known/ipc-keys¶
The response MUST be a JSON object:¶
{
"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
}
]
}
¶
serviceId (string, REQUIRED): This service's IPC issuer identifier.
The serviceId value in the key discovery response MUST exactly match
the issuer 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).¶
cacheMaxAge (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.¶
keys (array, REQUIRED): JWK objects with IPC metadata.¶
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 validUntil has passed.¶
Key material validation depends on the algorithm:
- For Ed25519 keys ("kty": "OKP", "crv": "Ed25519"): the x field
MUST be exactly 43 characters of base64url (representing 32 bytes of
public key material, without padding). Verifiers MUST reject keys
where x does not decode to exactly 32 bytes.
- For ES256 keys ("kty": "EC", "crv": "P-256"): the x and y
fields MUST each be exactly 43 characters of base64url (representing
32 bytes each). Both coordinates are REQUIRED. Verifiers MUST reject
keys where x or y does not decode to exactly 32 bytes.¶
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.¶
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.¶
When IPC verification or enforcement fails, the error response MUST use the following JSON structure:¶
{
"error": "signature_invalid",
"errorDescription": "IPC signature verification failed",
"sessionId": "0191a0b0-c8f4-7b2e-9a1c-3d4e5f6a7b8c",
"entityId": "cell-configurator",
"issuer": "api-gateway"
}
¶
Fields:¶
error (string, REQUIRED): Machine-readable error code. MUST be one of:
malformed_ipc, unsupported_version, unsigned_token,
signature_invalid, issued_in_future, token_expired,
token_replayed, unknown_key, token_too_old, empty_chain,
depth_mismatch, chain_consistency_error, depth_exceeded,
circular_delegation, audience_mismatch, entity_revoked,
algorithm_not_permitted, unsupported_algorithm,
replay_cache_unavailable, issuer_caller_mismatch.¶
errorDescription (string, REQUIRED): Human-readable description.¶
sessionId (string, OPTIONAL): From the IPC if parseable.¶
entityId (string, OPTIONAL): From the IPC if parseable.¶
issuer (string, OPTIONAL): From the IPC if parseable.¶
IPC error responses use camelCase field names (e.g., errorDescription)
to align with the IPC claims naming convention (entityId, sessionId).
This intentionally differs from OAuth 2.0's error_description convention.¶
The sessionId, entityId, and issuer 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.¶
When IPC verification fails at a trust boundary, the service MUST:¶
Return an appropriate error to the caller:¶
delegationDepth > maxDepth, expired, audience mismatch).¶
Trust Boundaries and Enforcement Points MUST log the following for every
IPC verification (successful or failed):
- sessionId of the IPC
- entityId of the current executor
- Verification result (accept/reject)
- If rejected: the error code from Section 3.7
- Timestamp of the verification event¶
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.¶
During migration periods, deployments MAY configure enforcement points to operate in "permissive mode" where IPC failures are logged but not enforced. This MUST be a temporary configuration and MUST NOT be used in production enforcement.¶
The behavior when an IPC header is absent from a request is deployment-specific:¶
An IPC is created at the platform trust boundary (e.g., an API gateway) when:¶
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.¶
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.¶
The following algorithm defines how an IPC Creator constructs a new IPC:¶
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)
¶
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.¶
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.¶
Each deployment MUST configure a maximum delegation depth (maxDepth). An IPC
with delegationDepth > maxDepth MUST be rejected.¶
Deployments SHOULD configure consistent maxDepth 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.¶
Every entry in delegationChain MUST be traceable to an accountable root (the
first entry). The root SHOULD be either:¶
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.¶
Deployments MAY restrict roots to human principals only via policy. An IPC
where the first entry in delegationChain does not match the deployment's
allowed root types MUST be rejected.¶
The root entity's type is determined by the IPC creator (trust boundary) at
issuance time based on the authenticated credential. The entityType claim
in the IPC reflects the current 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).¶
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 (entityType). Enforcement points that
require root-type validation MUST resolve the root identity's type
through external policy.¶
Expiry attenuation applies at IPC creation time, not during re-signing.¶
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):¶
child.expiresAt <= parent.expiresAt¶
A child IPC MUST NOT have a longer lifetime than the parent delegation that authorized it. This ensures authority shrinks over time, never grows.¶
This rule does NOT apply during re-signing (Section 3.3). Re-signing
preserves the original expiresAt unchanged - the immutability rule
(Section 3.4) prevents any modification of expiresAt during re-signing.¶
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 maxTokenAge check (verification Step 8f) provides a
deployment-wide hard cap independent of attenuation.¶
If any identifier appears more than once in the delegationChain, the IPC
MUST be rejected. This prevents circular delegation.¶
When a parent entity is revoked:¶
delegationChain MUST reject the IPC as specified
by the revocation scope rules in Section 5.5.1.¶
The revocation event is a JSON object published to a well-known topic
(e.g., ipc-revocation):¶
{
"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(...)"
}
¶
Field definitions:¶
| Field | Type | Required | Description |
|---|---|---|---|
version
|
string | REQUIRED | Revocation event schema version (e.g., "1.0"). Major/minor semantics per Section 2.2 versioning rules. Receivers MUST reject events with an unsupported major version. |
eventType
|
string | REQUIRED | MUST be "entity.revoked" |
eventId
|
string | REQUIRED | Unique event identifier (UUID v7 RECOMMENDED) |
entityId
|
string | REQUIRED | Identifier of the revoked entity |
revokedAt
|
string | REQUIRED | ISO 8601 UTC timestamp of revocation |
reason
|
string | REQUIRED | One of: "compromised", "policy_violation", "operator_request", "expired", "rotation" |
scope
|
string | REQUIRED | "cascade" or "single" |
issuedBy
|
string | REQUIRED | Identifier of the revoking authority |
alg
|
string | REQUIRED (production) | Signing algorithm identifier ("EdDSA" or "ES256"). Enables algorithm-agnostic verification. |
kid
|
string | REQUIRED (production) | Key identifier for the revoking authority's signing key. Enables key selection during rotation. |
signature
|
string | REQUIRED (production), OPTIONAL (development) | Signature over all other fields (JCS-canonical, excluding signature itself) using the algorithm specified in alg. |
Revocation event signing keys are discovered via the same key discovery
mechanism as IPC signing keys (Section 3.6). The issuedBy field
corresponds to the serviceId in the key discovery endpoint. If no
key discovery endpoint is available for the revoking authority, signature
verification is deployment-specific.¶
Processing rules:¶
scope is cascade, enforcement points MUST reject any IPC where
the revoked entityId appears anywhere in the delegationChain.¶
scope is single, only IPCs where the revoked entity is the
entityId (current executor) are rejected.¶
signature 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.¶
eventId values MUST be ignored (idempotent processing).
Enforcement points MUST maintain deduplication state for eventId
values for at least 24 hours after first receipt. Revocation events
with revokedAt 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.¶
revokedAt.¶
Enforcement points SHOULD garbage-collect revocation state entries whose
revokedAt 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).¶
Guidance on scope selection:
- Use cascade when the entity is compromised or its key material
may be leaked - all downstream authority derived from this entity
is suspect.
- Use single 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 single, the entity MAY still
appear as an intermediate in other IPCs' delegationChain without
causing rejection.¶
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: <= 5 minutes; deployment-configurable) provides a hard upper bound on exposure regardless of revocation propagation timing.¶
During network partitions where revocation events cannot be delivered,
enforcement points MUST continue to validate IPC expiry (expiresAt).
The short TTL ensures that even without revocation delivery, compromised
IPCs expire naturally within the configured window.¶
Enforcement points that support revocation MUST implement one of the following revocation checking mechanisms:¶
Option A: Event-Driven (Push)
- Subscribe to the ipc-revocation topic (or equivalent event stream)
- Maintain a local revocation set in memory or shared cache
- Check every IPC's delegationChain against the local revocation set
- Reject if any entity in the chain appears in the revocation set¶
Option B: Polling (Pull)
- Query a revocation status endpoint:
GET /ipc/v1/revocation-status/{entityId}
- Response: {"revoked": true/false, "revokedAt": "...", "scope": "..."}
- Cache responses for no longer than the deployment's configured
revocation cache TTL (RECOMMENDED default: 30 seconds)
- Check every IPC's delegationChain against cached status¶
Option C: Expiry-Only (No Infrastructure)
- If no revocation infrastructure is deployed, enforcement points
MUST rely solely on IPC expiry (expiresAt) for revocation
- This is acceptable only when TTLs are short (RECOMMENDED default:
<= 5 minutes)
- Deployments using this option MUST document the maximum exposure
window in their security assessment¶
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.¶
When a revocation check determines that an entity is revoked, the
enforcement point MUST reject the IPC with entity_revoked regardless
of other validity checks.¶
This section defines the configuration parameters referenced throughout this specification, with normative defaults and constraints to ensure interoperability between independent implementations.¶
| Parameter | Type | Default | Normative Constraint | Description |
|---|---|---|---|---|
maxDepth
|
integer | 5 | MUST be >= 1, MUST NOT exceed 10 | Maximum delegation chain depth |
maxTTL
|
duration | 300s (5 min) | MUST be >= 30s, MUST NOT exceed 600s (10 min) | Maximum IPC lifetime |
clockSkew
|
duration | 30s | MUST be >= 5s, MUST NOT exceed 60s, MUST be less than maxTTL
|
Clock skew tolerance for temporal checks. The constraint clockSkew < maxTTL ensures that the skew tolerance does not exceed the token lifetime, which would undermine short-TTL replay protection. |
maxTokenAge
|
duration | 0 (disabled) | If > 0, MUST be >= maxTTL
|
Maximum age from issuedAt (0 = disabled). The constraint maxTokenAge >= maxTTL prevents false rejections: since a token's lifetime can be up to maxTTL, setting maxTokenAge below maxTTL would cause valid tokens to be rejected before their expiresAt. The maxTokenAge check provides value as a deployment-wide hard cap when verifiers and creators have different maxTTL configurations, or as defense against clock manipulation at the creator. |
replayCacheTTL
|
duration | derived | MUST be >= maxTTL + clockSkew
|
Minimum replay cache entry lifetime |
keyDiscoveryCacheTTL
|
duration | 300s | MUST be >= 30s, MUST NOT exceed 3600s | Key discovery response cache duration |
allowUnsignedForAudit
|
boolean | false | N/A | Whether Trust Level 1 IPCs are accepted |
allowHMAC
|
boolean | false | N/A | Whether HS256 (HMAC-SHA-256) is permitted |
isTerminalEnforcementPoint
|
boolean | (required) | N/A | 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). |
isEnforcementPoint
|
boolean | (required) | N/A | Whether this service makes authorization decisions |
serviceId
|
string | (required) | MUST conform to identifier constraints (Section 2.2) | This service's IPC issuer identifier |
Configuration consistency constraints:¶
isTerminalEnforcementPoint is true, then isEnforcementPoint
MUST also be true. A terminal enforcement point is by definition an
enforcement point (it makes the final authorization decision).
Implementations MUST reject configurations where
isTerminalEnforcementPoint = true and isEnforcementPoint = false.¶
allowHMAC MUST be false. Implementations
MUST reject configurations where a Trust Boundary service has
allowHMAC = true (Section 2.6).¶
Normative requirements:¶
clockSkew is
greater than or equal to maxTTL. A clock skew tolerance that
equals or exceeds the token lifetime effectively disables temporal
validity as a security control.¶
maxTTL 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.¶
maxDepth exceeds 10.
Deeper chains indicate an architectural issue that should be resolved
by restructuring the service topology.¶
replayCacheTTL MUST be at least maxTTL + clockSkew to
ensure replay detection covers the full validity window of any IPC.
Implementations SHOULD derive this automatically from the configured
maxTTL and clockSkew values rather than requiring manual configuration.¶
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.¶
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.¶
The IPC is carried in the HTTP header:¶
Identity-Propagation-Context: <base64url-encoded IPC>¶
Note: The X- prefix is not used, per RFC 6648 which deprecates custom header
prefixes for new headers.¶
The IPC is carried in gRPC metadata:¶
identity-propagation-context: <base64url-encoded IPC>¶
The IPC is carried in a record header:¶
Header key: identity-propagation-context Header value: <base64url-encoded IPC>¶
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:¶
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 Identity-Propagation-Context header.¶
Audience semantics apply at the terminal enforcement point regardless of which protocol delivered the IPC.¶
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.¶
The Identity-Propagation-Context header value is defined by the
following ABNF [RFC5234]:¶
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.¶
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 Identity-Propagation-Context headers,
the verifier MUST reject with malformed_ipc.¶
Base64url encoding MUST be used without padding (per RFC 4648 Section 5).¶
In event-streaming systems (e.g., Kafka) where records are retained
beyond the IPC's expiresAt, consumers processing historical records
will encounter expired IPCs. The following guidance applies:¶
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.¶
| Level | Name | Description | Use Case |
|---|---|---|---|
| 1 | Unsigned | No signature, audit trail only | Development, logging |
| 2 | Signed | HS256 or EdDSA (Ed25519) signature | Authorization, enforcement |
| 3 | Reserved | Reserved for future extension | N/A |
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 trustLevel 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.¶
For Trust Level 1 (unsigned) IPCs:
- The alg claim MUST be set to the string "none"
- The kid claim MUST be absent or empty string
- The signature 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¶
IPCs SHOULD have short TTLs (RECOMMENDED default: <= 5 minutes;
deployments MUST configure a maximum TTL appropriate to their threat model)
to limit replay windows. The sessionId claim provides request-level
uniqueness.¶
Enforcement points MUST maintain a replay cache keyed by sessionId for
at least the deployment's maximum configured IPC TTL. If a sessionId
has been seen before within the cache window, the IPC MUST be rejected
with token_replayed.¶
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.¶
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.¶
For Trust Level 2 deployments, the combination of REQUIRED audience and
sessionId replay cache provides defense-in-depth against both cross-service
and same-service replay attacks.¶
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.¶
Each IPC creation produces a unique sessionId. When an entity
sub-delegates (creating a new IPC with an extended chain), the child
IPC receives its own unique sessionId. The parent IPC's sessionId
and the child IPC's sessionId 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.¶
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.¶
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
token_replayed. 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).¶
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.¶
An unsigned IPC (Trust Level 1) MUST NOT be used for enforcement decisions. Only signed (Level 2+) IPCs may be used for authorization.¶
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.¶
IPC uses a "trust your immediate upstream" model. At any given hop, only the most recent signer's signature is present - previous signatures are replaced during re-signing. This means:¶
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.¶
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.¶
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).¶
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.¶
IPC size grows linearly with delegation chain depth due to the
delegationChain array. Deployments MUST consider transport-level limits:¶
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.¶
The configured maxDepth serves as the primary mitigation for IPC size
growth. Deployments SHOULD set maxDepth to the minimum value that
accommodates legitimate delegation patterns (RECOMMENDED: <= 5 for most
deployments).¶
IPC identifies the following threat categories:¶
| ID | Threat | Mitigation |
|---|---|---|
| T1 | Token Replay | Short TTL (<= 5 min), sessionId uniqueness, TLS-only transport |
| T2 | Key Compromise at Trust Boundary | Key rotation (Section 8.8), short TTL limits exposure, revocation events |
| T3 | Delegation Chain Injection | Signature covers all claims; payload immutable during re-sign |
| T4 | Circular Delegation | Explicit duplicate detection in delegationChain (Section 5.4) |
| T5 | Depth Escalation | Configurable maxDepth enforcement at every trust boundary |
| T6 | Revocation Delay | Dual-layer: event-streaming (sub-5s target) + short TTL hard bound |
| T7 | Cross-Service Replay | Audience claim binding; REQUIRED for Trust Level 2+ |
| T8 | Intermediate Service Compromise | Single-signer model tradeoff (Section 8.5); mitigated by short TTL + revocation |
| T9 | IPC Stripping | Fail-closed enforcement (Section 3.9); requests without IPC are rejected |
| T10 | Downgrade to Trust Level 1 | Enforcement points MUST reject unsigned IPCs (Section 8.10) |
| T11 | Clock Skew Exploitation | Clock synchronization requirements (Section 8.9) |
| T12 | Denial of Service via Large Chains | Token size limits (Section 8.6); maxDepth enforcement |
| T13 | Confused Deputy at Trust Boundary | 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. |
| T14 | Compromised IPC Creator | IPC Creator requires highest security posture: HSM-protected signing keys, restricted access, audit logging of all creation events, maxTokenAge provides deployment-wide hard cap |
| T15 | Token theft from memory/logs | 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 |
| T16 | Colluding IPC Creator and Intermediate | 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); maxTokenAge caps temporal exploitation; audit logging (Section 3.8) provides forensic detection. This threat is inherent to the single-trust-domain model. |
| T17 | Time-of-Check-to-Time-of-Use (TOCTOU) | Between VERIFY-IPC 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. |
| T18 | Replay Cache Race Condition | In horizontally-scaled enforcement points, two requests with the same sessionId 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 < 10ms for Redis); audience binding prevents cross-service exploitation; shared cache with atomic check-and-set operations (e.g., Redis SETNX) eliminates the window entirely. Deployments SHOULD use atomic cache operations for replay detection. |
| T19 | Compromised Key Discovery Endpoint | An attacker who can serve forged key material at /.well-known/ipc-keys 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. |
| T20 | Cross-Cluster Replay | In deployments where a logical service (same audience value) spans multiple clusters with independent replay caches, an attacker can replay the same IPC at both clusters within the TTL window. The audience 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., backend.cluster-a.example.com) 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 maxTTL. |
| T21 | Compromised Service Mesh Sidecar | 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. |
When a trust boundary service rotates its signing key:¶
status: "active" and validFrom set to the current time.
The old key remains status: "active" during the transition.¶
cacheMaxAge (from the key discovery response, or
the deployment's configured keyDiscoveryCacheTTL) to ensure all
downstream verifiers have fetched the new key.¶
"retired" and set validUntil to
the current time.¶
validUntil plus the maximum configured IPC TTL
(maxTTL) plus clockSkew has elapsed.¶
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 validUntil plus the maximum configured IPC TTL
(Section 5.6 maxTTL) has elapsed.¶
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.¶
If a key is suspected compromised:¶
"compromised" at the discovery endpoint.¶
reason: "rotation" for any
IPCs that may have been signed by the compromised key.¶
IPC's temporal validity checks (issuedAt, expiresAt) require synchronized clocks across trust boundary services. The following requirements apply:¶
issuedAt
and expiresAt. Deployments with tighter synchronization guarantees
MAY use a lower tolerance.¶
issuedAt derived from
an unsynchronized clock. If NTP synchronization is lost, the service
SHOULD reject incoming requests rather than issuing IPCs with
unreliable timestamps.¶
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 (> 10 seconds) SHOULD increase their clock skew tolerance or reduce their IPC TTL to compensate.¶
An attacker may attempt to downgrade IPC security by:¶
Stripping the IPC header entirely: Mitigated by fail-closed enforcement (Section 3.9). Enforcement points MUST reject requests without IPC in production.¶
Replacing a signed IPC with an unsigned one (Trust Level 1): 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.¶
Replaying an expired IPC from a weaker configuration: Temporal validity checks reject expired IPCs regardless of other claims.¶
Presenting an IPC signed by an unknown issuer: Services MUST
verify the IPC was signed by a known upstream issuer. Unknown issuers
MUST be rejected with signature_invalid.¶
Forcing fallback to permissive mode: 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).¶
IPC introduces the following DoS attack surfaces:¶
Verification cost: 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.¶
Key discovery amplification: 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.¶
Revocation event flooding: 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.¶
Large delegation chains: Token size grows with chain depth. Mitigations: maxDepth enforcement (RECOMMENDED <= 5), transport-layer header size limits provide a natural cap.¶
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.¶
This is mitigated by:
1. Short TTLs limiting the replay window
2. The audience 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¶
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.¶
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.¶
Enforcement points MUST verify that the authenticated identity established
by the bearer token (e.g., the token's sub, azp, or client_id claim)
is consistent with the IPC's issuer 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:¶
azp (authorized party) claim from the bearer token.
If azp is absent, fall back to client_id.¶
issuer field.¶
Deployments that use a different bearer token claim for binding
verification (e.g., a custom service_id 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.¶
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.¶
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 issuer 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 issuer — not between the original client and the IPC creator.¶
For opaque bearer tokens (where claims are not directly available),
enforcement points MUST use token introspection (RFC 7662) to obtain
the client_id or equivalent identifier for binding verification,
or rely on transport-level identity (mTLS) instead.¶
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
issuer 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.¶
IPCs contain identity information (entity IDs, delegation chains) that reveals organizational structure, delegation relationships, and operational patterns. The following privacy requirements apply:¶
IPCs MUST only be transmitted over encrypted channels (TLS). Unencrypted transmission exposes the full delegation chain to passive observers.¶
The delegationChain 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¶
Deployments SHOULD consider using opaque or pseudonymous identifiers
rather than human-readable names in delegationChain entries when IPCs
may be exposed beyond the immediate trust domain (e.g., captured in
shared logging infrastructure, debugging tools, or error responses).¶
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.¶
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:¶
sessionId, entityId, verification result,
and timestamp — not the full IPC payload.¶
delegationChain) MUST NOT be logged
unless required for active security investigation.¶
Deployments SHOULD minimize the identity information included in IPCs based on the principle of data minimization:¶
delegationChain 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.¶
The sessionId enables correlation of a single request across multiple
services. Deployments MUST NOT use sessionId for cross-request user
tracking. The RECOMMENDED use of UUID v7 (which contains a timestamp
component) means that sessionId values reveal approximate creation
time even without access to the issuedAt claim.¶
As noted in Section 2.1, using the same value for sessionId and
externally-visible trace identifiers (e.g., W3C Trace Context) is
NOT RECOMMENDED when trace context propagates beyond the trust domain
boundary.¶
Registration request for a new HTTP header field:¶
Identity-Propagation-Context¶
Note: gRPC metadata keys and Kafka record header keys do not have IANA
registries. The metadata key identity-propagation-context (Section 6.2) and
Kafka record header key identity-propagation-context (Section 6.3) are
defined by this specification and SHOULD be treated as reserved by
implementations that support IPC.¶
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 "JSON Web Token Claims" registry. For this version, the claims are defined locally within this specification.¶
| Claim Name | Description | Change Controller | Specification |
|---|---|---|---|
entityId
|
Identity of executing entity | IESG | This document, Section 2.1 |
entityType
|
Classification (AUTONOMOUS/SUPERVISED/UTILITY/HUMAN/SYSTEM) | IESG | This document, Section 2.1 |
authorizedBy
|
Immediate delegating party | IESG | This document, Section 2.1 |
delegationDepth
|
Hops from root | IESG | This document, Section 2.1 |
delegationChain
|
Ordered provenance chain | IESG | This document, Section 2.1 |
trustLevel
|
Signature assurance level: 1 (unsigned), 2 (signed), 3 (reserved) | IESG | This document, Section 2.1 |
Registration request for a new well-known URI suffix per RFC 8615:¶
This specification establishes the "IPC Entity Type" registry under the "Identity Propagation Context (IPC)" heading. The registration policy is "Specification Required" per RFC 8126.¶
Initial registry contents:¶
| Value | Description | Change Controller | Reference |
|---|---|---|---|
HUMAN
|
A natural person authenticated via an identity provider | IESG | This document, Section 2.1 |
SYSTEM
|
Platform-initiated operation with no direct human trigger | IESG | This document, Section 2.1 |
AUTONOMOUS
|
Agent operating independently on behalf of a human principal | IESG | This document, Section 2.1 |
SUPERVISED
|
Agent requiring human approval for sensitive operations | IESG | This document, Section 2.1 |
UTILITY
|
Stateless service component invoked by other entities | IESG | This document, Section 2.1 |
Registration requirements for new entity types:
- Value MUST be an uppercase ASCII string matching ^[A-Z][A-Z0-9_]{1,31}$
- A specification document MUST define the semantics and intended use
- New values MUST NOT conflict semantically with existing registered types¶
| Specification | Relationship |
|---|---|
| RFC 8693 (Token Exchange) | Complementary - used at first hop; IPC carries identity through remaining chain |
| Transaction Tokens [I-D.draft-ietf-oauth-transaction-tokens] | 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 "what is this transaction authorized to do?"; IPC answers "who initiated this request and through what delegation chain?" Both may coexist in the same request: the Txn-Token in the Txn-Token header and the IPC in the Identity-Propagation-Context header. |
| RFC 6750 (Bearer Tokens) | IPC extends bearer token concepts with delegation chain and re-signing |
| RFC 7519 (JWT) | 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. |
| Agent Identity Protocol (AIP) [I-D.draft-singla-agent-identity-protocol] | 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. |
| WIMSE AI Agent Identity [I-D.draft-ni-wimse-ai-agent-identity] | Complementary - WIMSE defines agent identity bootstrapping and attestation; IPC defines how the resulting verified identity propagates through service chains after credential issuance. |
| AgentID Protocol [I-D.draft-gudlab-agentid-protocol] | Shared principle - both require cryptographic traceability to a human/organization root. IPC adds per-hop re-signing and cross-protocol propagation. |
| W3C Baggage | Complementary - Baggage for observability, IPC for enforcement |
| RFC 9449 (DPoP) | 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). |
| SPIFFE/SPIRE | Complementary - SPIFFE identifies workloads (pod-to-pod), IPC identifies the initiating entity and its delegation chain through those workloads |
| AI Agent Authentication Framework [I-D.draft-klrc-aiagent-auth] | 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 "Delegation (downstream)" 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 act claim is informational; IPC makes it enforceable with per-hop re-signing. |
| Google Cloud Agent Identity | 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. |
The Agent Identity Protocol (AIP) is the most comprehensive existing specification for AI agent identity. IPC and AIP operate at different architectural layers:¶
| Aspect | AIP | IPC |
|---|---|---|
| Purpose | Agent proves its identity to a relying party | Platform carries verified identity through its internal chain |
| Who creates the artifact? | Agent self-issues Credential Tokens | Platform trust boundary creates IPC |
| Agent involvement | Active - agent implements the protocol | None - agent is not aware of IPC |
| Re-signing at hops | Not supported - agent-signed once | Re-signed at every trust boundary |
| Protocol support | HTTP only (Bearer/DPoP) | HTTP, gRPC, Kafka |
| Verification model | Registry lookup | Locally-verifiable - no per-request STS calls |
| Registry dependency | Required for Tier 2+ | No registry needed |
Both support delegation chains, depth limits, expiry attenuation, cascade revocation, and circular detection - confirming shared problem understanding with different architectural solutions.¶
When to use which:¶
This specification defines three conformance classes:¶
IPC Creator (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 audience 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¶
Trust Boundary (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 issuer, kid, and signature
- 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)¶
Enforcement Point (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 sessionId (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¶
An implementation MAY fulfill multiple conformance classes simultaneously (e.g., a service that both re-signs and makes enforcement decisions).¶
The authors thank the reviewers who provided feedback on early drafts of this specification.¶