draft-durga-wimse-ctx-isolation

Source files on GitHub

In one line: every tool call from an AI agent carries a signed taint attestation, and a relying party rejects state-changing calls whose context was not trusted unless an independent approval receipt is attached.

What this is

Autonomous AI agents that use the Model Context Protocol (MCP) or Agent-to-Agent (A2A) transports take untrusted data such as emails, web documents and retrieved RAG chunks into the same inference context that authorizes privileged tool calls (Abstract). Section 1 gives third-party documents, webhooks and unverified peer agent outputs as examples of such input. The draft says that existing workload identity and token standards (OAuth 2.1, GNAP, WIMSE Wit/Wpt tokens) bind client identity and authorization scope (Abstract), but that neither OAuth 2.0 Token Exchange (RFC 8693) nor WIMSE Workload Identity Tokens carry a cryptographic commitment to the taint of the context window at the moment of tool selection (Section 1). It proposes a Context Taint Attestation (CTA), a JSON object with a taint level, a tool effect class and a Merkle root over the context, signed with Ed25519 (Section 3). The CTA travels in an Agent-Ctx-Isolation header or a _meta.ctxIsolation object in JSON-RPC/MCP (Section 3). A relying party must fail closed on state-mutating or privileged calls that are not marked TRUSTED_ONLY, unless a valid step-up receipt from an independent policy decision engine or human is attached (Section 4). The draft is a five-page individual submission, not adopted by any working group on the Datatracker, and is still at the stage of a problem statement plus a skeleton wire format.

How it works

Diagram
sequenceDiagram
    participant R as Agent runtime
    participant G as Relying party gateway
    participant P as Policy engine or human
    R->>R: Ingest external data and raise taint level
    opt Tainted context and state-changing tool
        R->>P: Request approval (flow not defined)
        P-->>R: Signed step-up receipt
    end
    R->>R: Build CTA, canonicalize with JCS, sign with Ed25519
    R->>G: Tool call with Agent-Ctx-Isolation header or MCP _meta
    G->>G: Verify signature, clock skew and digests
    G->>G: Check effect class, taint level and receipt
    alt Tainted state-changing call without valid receipt
        G-->>R: Reject with ERR_TAINTED_CONTROL_PLANE
    else Clean context, or valid receipt attached
        G->>G: Release the side effect
    end
  1. The runtime tracks the taint level of the active context. Once any byte from an unverified external origin is ingested, the runtime MUST move the level from TRUSTED_ONLY to MIXED_TAINT or UNTRUSTED_INGESTED for the remainder of that context session unless a cryptographic context-reset boundary is executed (Section 4, rule 1).
  2. For each control-plane tool invocation the runtime builds the CTA with cta_version, invocation_id, parent_trace_ref, system_prompt_digest, data_plane_merkle_root, taint_lattice_level, tool_effect_class, step_up_receipt_hash, iat, exp and a signature member (Section 3). The draft does not mark any field optional. The example pairs a step_up_receipt_hash value with UNTRUSTED_INGESTED and STATE_MUTATING; the draft does not say whether the example is meant to pass rule 2.
  3. The Merkle root MUST be computed over the ordered RFC 8785 JCS-canonicalized chunks of all retrieved documents and tool outputs present in the active context window (Section 4, rule 3).
  4. The CTA is serialized with RFC 8785 JCS or RFC 8949 Deterministic CBOR (Section 3), but Section 5 requires JCS canonicalization before Ed25519 signing or verification. The signed CTA is attached to the call.
  5. The relying party verifies the signature. Clock skew above 60 seconds, a digest mismatch or a missing cryptographic binding MUST make it fail closed and emit an auditable security rejection receipt (Section 5).
  6. If tool_effect_class is STATE_MUTATING or PRIVILEGED_ACTUATE and taint_lattice_level != TRUSTED_ONLY, the relying party MUST reject the call with ERR_TAINTED_CONTROL_PLANE unless it carries a valid, unexpired step_up_receipt_hash signed by an independent policy decision engine or human verifier (Section 4, rule 2).

Use cases

  • A mail-reading agent holds a valid workload credential and is asked to summarize an inbox. A message contains a hidden instruction to forward files. The gateway in front of the file-sharing tool sees UNTRUSTED_INGESTED with STATE_MUTATING and rejects the call.
  • A deployment pipeline agent calls a privileged actuation API. The operator wants it to proceed only when the context was clean or a policy engine issued an approval receipt (Section 4, rule 2).
  • An MCP server operator wants to log, for each tool call, the taint level and the digest of the context that induced it, for incident review. The draft only asks for this on rejection: Section 5 requires an auditable security rejection receipt, and says nothing about logging accepted calls.
  • A gateway between peer agents over A2A needs to decide how far to trust a tool call that was triggered by the output of another agent. Section 1 lists unverified peer agent outputs as untrusted input.
  • A WIMSE deployer evaluates whether taint evidence could sit next to the Workload Identity Token and Workload Proof Token. The draft names them ("WIMSE Wit/Wpt tokens" in the Abstract, Workload Identity Tokens in Section 1) but defines no composition with them.

Why This Matters Now

  • Trigger: Revision 00 was posted on 29 October 2026. The author announced it and the companion draft-durga-agenttrust-trace-verification-00 on the WIMSE list on 1 November 2026, asking for review and for a 5-minute lightning slot if WIMSE agenda time remains at IETF 127 (San Francisco, 14-20 November 2026). The message had no replies when checked.
  • Source evidence: The announcement says that, without a cryptographic taint-lineage witness, indirect prompt injection turns a validly authenticated WIMSE workload into a confused deputy. It asks for feedback on "the ctx_isolation_witness structure and Policy-Epoch AST binding (policy_manifest_sha256)". Neither name appears in revision 00 of this draft or of the companion draft. The mail also points to a benchmark dataset on Hugging Face (DOI 10.57967/hf/11352, 100 rows covering OWASP ASI01 to ASI10), which neither draft cites by that DOI.
  • Expected impact: The draft is not a WG document. If the WG takes interest, it would need to decide whether taint reporting belongs in WIMSE at all. Section 3 makes the agent runtime attach the signed CTA and names no other signer, so as written the claim reads as self-asserted. The draft does not show how it adds assurance beyond what a compromised runtime would sign.

What Changed

  • Spec/document change summary: This is revision 00, so there is no previous revision to compare with. It introduces the CTA object (Section 3), three verification invariants TAINT_LATTICE_MONOTONICITY, CONFUSED_DEPUTY_FAIL_CLOSED and CONTEXT_MERKLE_BINDING (Section 4), a 60-second skew rule (Section 5), and IANA requests for a media type, a taint level registry and two header fields (Section 6).
  • Compatibility or migration concerns: Conforming runtimes MUST attach the header to every control-plane tool invocation (Section 3), so existing MCP clients and servers would all need changes. The draft gives no negotiation and no rollout path for relying parties that do not understand the header. For a call with no header, the nearest rule is the Section 5 requirement to fail closed on a "missing cryptographic binding", which does not say whether it applies to every effect class.

Technical Analysis

  • Threat model implications: The stated threat is indirect prompt injection that turns an authenticated workload into a confused deputy (Abstract; Section 1 states the gap without naming the attack). The draft does not define a trust model for the signer. Section 3 has the runtime that decides the taint level attach the signed CTA and does not say who holds the signing key; if it is the runtime, a compromised or buggy runtime can sign TRUSTED_ONLY. The Merkle root commits to context chunks but nothing in the draft lets a verifier check it without the chunks, and the rejection rule in Section 4 does not use it. The draft never mentions replay or nonces. Only iat, exp and the invocation_id UUID in the Section 3 example bear on it, and nothing says that verifiers must track invocation_id.
  • Protocol flow implications: The relying party needs the signer's public key, identified in the Section 3 example by a kid of the form sha256:<hex>. The draft does not say how keys are distributed, how kid is derived, or how the key is bound to a WIMSE workload identity. The step-up path is thin: the CTA carries step_up_receipt_hash, and IANA registration is requested for an Agent-Step-Up-Receipt header (Section 6), but the receipt format, its signature algorithm and its binding to a specific invocation are not defined. Rule 2 only says the receipt must be valid, unexpired and signed by an independent party (Section 4, rule 2).
  • Interop concerns: The Abstract says the profile is COSE_Sign1 plus JCS, but Section 3 shows a JSON object with its own signature member and no COSE structure. Section 3 allows JCS or Deterministic CBOR, while Section 5 requires JCS. The header encoding is not given. The sig placeholder base64url(Ed25519(JCS(payload))) does not say whether the payload excludes the signature member. The example digests are placeholders: data_plane_merkle_root is the SHA-256 of empty input (an empty context, although the same example sets UNTRUSTED_INGESTED) and parent_trace_ref the SHA-256 of test. The taint level QUARANTINED is registered in Section 6 but no rule in Section 4 uses it. The tool effect classes other than STATE_MUTATING and PRIVILEGED_ACTUATE are not listed.

Open Questions

  • Who is allowed to sign, and why should a relying party believe the taint level? The draft does not say how the runtime is attested or how the key is tied to a WIMSE identity (Sections 3, 5).
  • How does a CTA compose with the Workload Identity Token and Workload Proof Token, which the Abstract names (Section 1 names only the Workload Identity Token)? Nothing in Sections 3 to 5 says so.
  • What counts as "any byte from an unverified external origin" and what is a "cryptographic context-reset boundary" (Section 4, rule 1)? Neither is defined.
  • How is the Merkle tree built (leaf encoding, ordering, empty context), and how could a verifier use it (Section 4, rule 3)?
  • What is the format of the step-up receipt, and how does it bind to one invocation? What does "independent" mean (Section 4, rule 2)?
  • How is the CTA encoded in an HTTP header, and how is ERR_TAINTED_CONTROL_PLANE mapped to HTTP status or JSON-RPC errors (Sections 3, 4)?
  • Is the COSE_Sign1 claim in the Abstract meant to apply, given Section 3 shows a plain JSON signature object?
  • Are the names from the mail (ctx_isolation_witness, policy_manifest_sha256) planned for a later revision? They are absent from revision 00 of both drafts.
  • The normative references (Section 7) omit documents the text relies on, such as RFC 8949 (Section 3), RFC 8693 and draft-durga-agenttrust-trace-verification-00 (Section 1).
  • Which working group, if any, would take this up? The header says "WIMSE / OAuth Working Group" and "Intended status: Standards Track", but the Datatracker lists it as an individual submission with no stream, no WG state and no intended status.

Experiments

  • Lab 1: Write a small verifier from Sections 3 to 5. Sign the example CTA with Ed25519 over JCS output, then test three variants of the signing input (with and without the signature member, with different key orders). Record which variants two independent implementations agree on.
  • Lab 2: Put a gateway in front of a toy MCP tool server. Feed it combinations of taint_lattice_level and tool_effect_class, with and without a step-up receipt, and check that only the cases in Section 4, rule 2 are rejected with ERR_TAINTED_CONTROL_PLANE. Include QUARANTINED and an effect class not named in the draft to see what the text leaves open.
  • Lab 3: Run an agent against a prompt-injection test document. Check whether a runtime that sets the taint level itself raises it at the right time, and try a modified runtime that omits the transition to see that the verifier cannot detect it.

Sources

Context

Trigger

Revision 00 was posted on 29 October 2026 and announced to the WIMSE mailing list on 1 November 2026, with a request for review and for a 5-minute lightning slot at IETF 127 if agenda time remains. The announcement also covers a companion draft, draft-durga-agenttrust-trace-verification-00 (Informational, also an individual submission). The Datatracker lists this draft as an individual submission with no stream and no WG state, although its header names the "WIMSE / OAuth Working Group".

Why This Matters

The draft addresses a gap that WIMSE token work does not cover: a workload can be correctly authenticated while the tool call it makes was shaped by untrusted content in an LLM context window (Section 1). It proposes to make that visible and enforceable at the relying party with a signed taint attestation. The idea is easy to state, but the evidence in revision 00 is thin: the draft names no signer other than the agent runtime, the key is not tied to a workload identity, and the step-up receipt is not defined. A reviewer should treat it as an early problem statement with a sketch of a wire format.

Open Questions

  • Does the WG consider taint reporting in scope, and where would it sit relative to the Workload Identity Token and Workload Proof Token (Abstract, Section 1)?
  • How can a relying party trust a self-asserted taint level (Sections 3, 4)?
  • Which fields mentioned in the announcement (ctx_isolation_witness, policy_manifest_sha256) will appear in a later revision? They are not in revision 00 of this draft or of the companion draft.
  • How is the COSE_Sign1 statement in the Abstract reconciled with the plain JSON signature object in Section 3?
  • Who, if anyone, would adopt the work: the header names both WIMSE and OAuth and says Standards Track, but no working group has adopted it.

Spec notes

References

Normative references listed in Section 7:

  • RFC 2119, Key words for use in RFCs to Indicate Requirement Levels (BCP 14).
  • RFC 8174, Ambiguity of Uppercase vs Lowercase in RFC 2119 Key Words (BCP 14).
  • RFC 8785, JSON Canonicalization Scheme (JCS).
  • RFC 8032, Edwards-Curve Digital Signature Algorithm (EdDSA).

Informative references: the draft has no informative reference section.

Cited in the text but not listed in Section 7:

  • RFC 8693, OAuth 2.0 Token Exchange (Section 1).
  • RFC 8949, Concise Binary Object Representation (CBOR), for its deterministic encoding (Section 3).
  • OAuth 2.1, GNAP and "WIMSE Wit/Wpt tokens" (Abstract), and WIMSE Workload Identity Tokens (Section 1), named without a reference.
  • COSE_Sign1 (Abstract), named without a reference.
  • draft-durga-agenttrust-trace-verification-00, described as the "foundational execution-trace verification architecture" this draft complements (Section 1).

Key Changes

This is revision 00, so there is no earlier revision and no diff.txt. What it introduces:

  • A Context Taint Attestation object with cta_version, invocation_id, parent_trace_ref, system_prompt_digest, data_plane_merkle_root, taint_lattice_level, tool_effect_class, step_up_receipt_hash, iat, exp and a signature member with alg, kid and sig (Section 3).
  • Carriage in an Agent-Ctx-Isolation header or _meta.ctxIsolation in JSON-RPC/MCP (Section 3).
  • Three invariants: TAINT_LATTICE_MONOTONICITY, CONFUSED_DEPUTY_FAIL_CLOSED with error ERR_TAINTED_CONTROL_PLANE, and CONTEXT_MERKLE_BINDING (Section 4).
  • Fail-closed handling of clock skew over 60 seconds, digest mismatch and missing binding, with an auditable rejection receipt (Section 5).
  • IANA requests: media type application/agent-ctx-isolation+json, a registry of taint levels (TRUSTED_ONLY, MIXED_TAINT, UNTRUSTED_INGESTED, QUARANTINED), and header fields Agent-Ctx-Isolation and Agent-Step-Up-Receipt (Section 6).

Gaps visible in the text: the Abstract says COSE_Sign1 but Section 3 shows a JSON signature object; the Merkle tree construction, step-up receipt format and header encoding are not defined; the example does not make clear what is signed. The example values are placeholders: the digests are SHA-256 of short strings (data_plane_merkle_root of empty input, parent_trace_ref of test, kid of test1), and exp is iat plus 60 seconds.

Impact Analysis

  • Upstream: Depends on JCS (RFC 8785) and EdDSA (RFC 8032) for canonicalization and signing, and on the agent runtime to track the taint level. It positions itself against RFC 8693 and WIMSE tokens but defines no binding to them. The companion draft draft-durga-agenttrust-trace-verification-00 is named in Section 1 but not listed as a reference. Neither draft says that parent_trace_ref points into it; the companion's own record field is parent_record_hash.
  • Downstream: Every MCP or A2A tool server or gateway that wants to enforce Section 4 would have to parse and verify the header, manage signer keys, and implement a step-up approval path. New IANA registrations would be needed for the media type, the taint registry and two headers.
  • Compatibility: Mandatory on every control-plane tool invocation (Section 3), with no negotiation or fallback described. Deterministic CBOR is allowed in Section 3 while Section 5 requires JCS, so implementations could diverge. The mail refers to ctx_isolation_witness and policy_manifest_sha256, which are not in revision 00 of either draft, so later revisions may change the structure.

Interop checklist

  • The signing input is stated unambiguously: confirm whether sig covers the JCS form of the object without the signature member, and that two implementations produce identical bytes (Sections 3, 5).
  • A verifier handles the Abstract's COSE_Sign1 versus the JSON signature object in Section 3 consistently, and rejects the form it does not support instead of guessing.
  • JCS and Deterministic CBOR (Section 3) versus JCS only (Section 5): check what a verifier does when it receives a CBOR-encoded CTA.
  • The Agent-Ctx-Isolation header encoding (for example base64url of the JSON) is agreed between sender and receiver, and the _meta.ctxIsolation form carries the same object (Section 3).
  • A call with no CTA, a malformed CTA, or an unknown cta_version fails closed. Section 5 requires that for a "missing cryptographic binding" but does not say whether it covers every effect class, so record what the verifier does for the others.
  • kid (sha256:<hex> in the example) resolves to a trusted Ed25519 key, and an unknown kid fails closed. Note that the key distribution is out of the draft.
  • A CTA whose clock skew exceeds 60 seconds is rejected and an auditable rejection receipt is emitted (Section 5). The draft does not say which field the skew is measured on; if it were exp, a fresh CTA with the example's 60-second exp minus iat window would hit the boundary almost at once, so agree on the field and test exactly 60 seconds.
  • A replayed CTA with the same invocation_id within its validity window is handled in a defined way. The draft does not require tracking, so record the actual behavior.
  • STATE_MUTATING and PRIVILEGED_ACTUATE with MIXED_TAINT or UNTRUSTED_INGESTED return ERR_TAINTED_CONTROL_PLANE when no step-up receipt is attached (Section 4, rule 2).
  • TRUSTED_ONLY with STATE_MUTATING is accepted, and the handling of a TRUSTED_ONLY claim paired with a non-empty data_plane_merkle_root is decided and documented.
  • QUARANTINED (registered in Section 6, unused in Section 4) is rejected or handled in a documented way, and so is any tool_effect_class value not named in the draft.
  • The Section 3 example (UNTRUSTED_INGESTED, STATE_MUTATING, with a step_up_receipt_hash) is accepted only when the receipt it names is present and valid. Its digests are placeholders, so build a real test vector.
  • A step-up receipt that is expired, signed by the agent runtime itself, or bound to another invocation_id is rejected. The draft does not define the format, so the test must state the assumed format.
  • step_up_receipt_hash matches the receipt carried in Agent-Step-Up-Receipt, and a mismatch fails closed. This is an assumption: Section 3 defines the field and Section 6 only requests the header's registration, but the draft does not link the two or say how the receipt is carried.
  • The Merkle root is reproducible: two implementations given the same ordered chunks produce the same data_plane_merkle_root, with the leaf encoding and tree shape written down because Section 4, rule 3 leaves them open.
  • system_prompt_digest and parent_trace_ref are checked or explicitly ignored by the verifier, since Section 4 states no rule for them.
  • The taint level never moves back from MIXED_TAINT or UNTRUSTED_INGESTED to TRUSTED_ONLY inside one context session except through a defined reset boundary (Section 4, rule 1).
  • The error ERR_TAINTED_CONTROL_PLANE is mapped to a concrete HTTP status and JSON-RPC error code in the test setup, since the draft gives none.
  • The media type application/agent-ctx-isolation+json and the two header names are used exactly as written in Section 6.

Hands-on log

Environment

  • Runtime:
  • Tooling:
  • Versions:

Steps

Results

Failure Modes

Next Actions