Diagrams — Module B5: Identity and Permission Design

Module: B5 — Identity and Permission Design Diagram count: 5 Tool: Mermaid (primary). Each diagram validated in Mermaid Live Editor.


Diagram 1 — The Borrowed-Credential Problem

Type: Comparison / blast-radius Purpose: The single most important visual in the module. An agent given a human's credentials acts with the human's full privilege. The blast radius is the credential's scope, not the task's requirement — and an injected agent with admin credentials is an admin compromise. The NHI column (scoped to the task) is strictly better on every security axis. Reading the diagram: Two parallel paths from the same task. The left path (borrowed credential) inherits the human's full privilege; the right path (NHI) is scoped to the task. The red callouts mark where injection turns each into a compromise — and how different the blast radius is.

flowchart TB
  TASK["A TASK: look up one customer's order<br/>(requires orders:read, one customer)"]

  subgraph BORROWED["PATH A — borrowed human credential"]
    direction TB
    B1["Developer's AWS key<br/>long-lived · read-write · all customers"]
    B2["Agent holds the key in its env<br/>privilege = developer's full access"]
    B3["INJECTED AGENT<br/>exfiltrates or uses the key"]:::danger
    B4["BLAST RADIUS: all orders<br/>read AND write · entire account"]:::danger
    B1 --> B2 --> B3 --> B4
  end

  subgraph NHI["PATH B — non-human identity (scoped)"]
    direction TB
    N1["NHI role: order-lookup-agent<br/>short-lived · orders:read · one customer_id"]
    N2["Harness mints action token per call<br/>agent never holds the raw credential"]
    N3["INJECTED AGENT<br/>token has orders:read only"]:::good
    N4["BLAST RADIUS: one customer<br/>read-only · token expires in seconds"]:::good
    N1 --> N2 --> N3 --> N4
  end

  TASK --> BORROWED
  TASK --> NHI

  classDef danger fill:#14141f,stroke:#f08080,stroke-width:1.5px,color:#f08080
  classDef good fill:#14141f,stroke:#82e0aa,stroke-width:1.5px,color:#82e0aa
  style TASK fill:#14141f,stroke:#5eead4,stroke-width:1.5px,color:#e4e4e8
  style B1 fill:#101018,stroke:#f08080,color:#e4e4e8
  style B2 fill:#101018,stroke:#f08080,color:#e4e4e8
  style N1 fill:#101018,stroke:#82e0aa,color:#e4e4e8
  style N2 fill:#101018,stroke:#82e0aa,color:#e4e4e8
  style BORROWED fill:#0c0c14,stroke:#f08080,stroke-width:1px,color:#9494a0
  style NHI fill:#0c0c14,stroke:#82e0aa,stroke-width:1px,color:#9494a0

Note: The task is identical on both paths — "look up one order." The difference is entirely in the identity layer. The borrowed credential makes the blast radius the human's privilege; the NHI makes the blast radius the task's requirement. When injection hits (B2 showed ~50% success against undefended agents), Path A is an account-wide compromise; Path B is a one-customer, read-only, seconds-long window.


Diagram 2 — NHI vs. Human Identity

Type: Comparison matrix Purpose: Why a non-human identity is the correct model for agents, not a borrowed human credential. Three properties NHIs have that borrowed human credentials lack: scoped to the workload, independently revocable, and auditable as non-human. The NHI is the fastest-growing identity category in cloud security. Reading the diagram: Left column = the borrowed human credential; right = the NHI. Each row is a property. The right column is strictly better on every axis. The bottom row is the audit distinction that matters for incident response.

flowchart LR
  subgraph HUMAN["BORROWED HUMAN CREDENTIAL"]
    direction TB
    H1["SCOPED TO THE WORKLOAD? — NO<br/>privilege = the human's full access"]
    H2["INDEPENDENTLY REVOCABLE? — NO<br/>disabling it disrupts the human"]
    H3["AUDITABLE AS NON-HUMAN? — NO<br/>logs say 'engineer@corp did X'"]
    H4["LIFETIME — long-lived<br/>months until rotation"]
  end
  subgraph NHI["NON-HUMAN IDENTITY (NHI)"]
    direction TB
    N1["SCOPED TO THE WORKLOAD? — YES<br/>privilege = the task's requirement"]:::good
    N2["INDEPENDENTLY REVOCABLE? — YES<br/>rotate the NHI, human unaffected"]:::good
    N3["AUDITABLE AS NON-HUMAN? — YES<br/>logs say 'order-lookup-agent did X'"]:::good
    N4["LIFETIME — short-lived<br/>seconds to minutes, per-task"]:::good
  end

  H1 -.->|"the fix"| N1
  H2 -.->|"the fix"| N2
  H3 -.->|"the fix"| N3
  H4 -.->|"the fix"| N4

  classDef good fill:#101018,stroke:#82e0aa,stroke-width:1.5px,color:#82e0aa
  style HUMAN fill:#0c0c14,stroke:#f08080,stroke-width:1px,color:#9494a0
  style NHI fill:#0c0c14,stroke:#82e0aa,stroke-width:1px,color:#9494a0
  style H1 fill:#101018,stroke:#f08080,color:#e4e4e8
  style H2 fill:#101018,stroke:#f08080,color:#e4e4e8
  style H3 fill:#101018,stroke:#f08080,color:#e4e4e8
  style H4 fill:#101018,stroke:#f08080,color:#e4e4e8

Note: The NHI is not a "more secure version" of the borrowed credential — it is a different identity model. The human credential was designed for a human who reasons about scope and is accountable; the NHI is designed for a process that does neither. Enterprise identity teams that spent a decade on human IAM are now confronting an NHI population that outnumbers humans 10:1 — and every agent you deploy adds to it.


Diagram 3 — Per-Task Scoped Credential Flow (Token Exchange, RFC 8693)

Type: Sequence / flow Purpose: The technical anchor. How an agent goes from "I need to call the orders API" to "I have a scoped action token that permits exactly that and nothing else" — without the agent ever holding a long-lived secret. RFC 8693 token exchange is the mechanism; the harness performs the exchange, not the agent. Reading the diagram: Top-to-bottom as a sequence. The agent holds a session token (short-lived, NHI-scoped). For each action, the harness exchanges it for an action token (narrower, shorter-lived, audience-specific). The agent never sees the action token's raw value; the harness makes the call and returns only the result.

flowchart TB
  AGENT["AGENT PROCESS<br/>holds session token (NHI-scoped, TTL: minutes)"]
  HARNESS["HARNESS<br/>intercepts the tool call<br/>(B2 Layer 3 / B4 gate — same interception point)"]
  VAULT["CREDENTIAL VAULT<br/>session token lives here<br/>agent process has NO reference"]
  AUTH["AUTHORIZATION SERVER<br/>RFC 8693 token exchange endpoint"]
  ACTION["ACTION TOKEN<br/>scope: orders:read<br/>audience: orders-api<br/>TTL: 60 seconds"]
  API["TARGET API (orders-api)<br/>verifies token scope on its end"]
  RESULT["RESULT returned to agent<br/>the token is NOT in the return value"]

  AGENT -->|"agent requests lookup_order(customer_id)"| HARNESS
  HARNESS -->|"retrieve session token"| VAULT
  VAULT -->|"session token (never exposed to agent)"| HARNESS
  HARNESS -->|"POST token-exchange<br/>grant_type=token-exchange<br/>scope=orders:read<br/>audience=orders-api"| AUTH
  AUTH -->|"issue action token"| HARNESS
  HARNESS -->|"scope_check(token, orders:read, orders-api)<br/>deterministic gate"| ACTION
  ACTION -->|"authenticated call"| API
  API -->|"result"| HARNESS
  HARNESS -->|"result only — no token"| RESULT

  classDef danger fill:#14141f,stroke:#f08080,stroke-width:1.5px,color:#f08080
  style AGENT fill:#14141f,stroke:#5eead4,stroke-width:1.5px,color:#e4e4e8
  style HARNESS fill:#14141f,stroke:#5eead4,stroke-width:1.5px,color:#5eead4
  style VAULT fill:#14141f,stroke:#f0a868,stroke-width:1.5px,color:#f0a868
  style AUTH fill:#14141f,stroke:#5eead4,color:#e4e4e8
  style ACTION fill:#101018,stroke:#82e0aa,color:#82e0aa
  style API fill:#14141f,stroke:#5eead4,color:#e4e4e8
  style RESULT fill:#101018,stroke:#82e0aa,color:#82e0aa

Note: The two load-bearing properties: (1) the harness performs the exchange — the agent process has no reference to the vault and never sees the raw action token; (2) the action token's scope is a compiled claim (orders:read), not a prompt-level instruction the model can be coerced into ignoring. An injected agent that tries to escalate to orders:delete hits the scope check: the action token was minted for orders:read, the check fails, the call is blocked. Deterministic, no model in the loop.


Diagram 4 — The Credential Isolation Architecture

Type: Architecture / boundary Purpose: The load-bearing principle made visual: credentials live in a vault the harness accesses, not in the agent process. The agent never sees the raw credential. This is the DD-20 IronCurtain credential-quarantine property (the agent holds only fake keys; the harness swaps for real ones at the boundary) realized via a vault + token-exchange flow without a MITM proxy. Reading the diagram: The dashed line is the trust boundary. Above it: the agent process — which has NO credentials in its env, config, or system prompt. Below it: the harness, the vault, the authorization server. The agent communicates intent (tool calls); the harness performs authenticated actions and returns only results. The credential never crosses the boundary upward.

flowchart TB
  subgraph AGENT_PROC["AGENT PROCESS — above the trust boundary"]
    direction TB
    A1["system prompt — NO API keys"]
    A2["environment — NO AWS_SECRET_ACCESS_KEY"]
    A3["config files — NO OAuth tokens"]
    A4["context window — receives RESULTS, never credentials"]
    A5["tool-call output — 'I called lookup_order<br/>and got this result'"]
  end

  BOUND["═══ TRUST BOUNDARY ═══<br/>credentials never cross upward<br/>the agent cannot exfiltrate what it does not possess"]

  subgraph HARNESS_PROC["HARNESS — below the trust boundary"]
    direction TB
    H1["tool-call interceptor<br/>(B2 Layer 3 / B4 gate)"]
    H2["scope determination<br/>(what does this task need?)"]
    H3["token exchange (RFC 8693)<br/>mint action token from session"]
    H4["scope_check middleware<br/>(deterministic gate)"]
    H5["authenticated API call<br/>uses the token, discards it"]
  end

  subgraph VAULT_PROC["CREDENTIAL VAULT — the harness accesses, not the agent"]
    direction TB
    V1["session tokens (NHI-scoped)"]
    V2["secrets manager<br/>(AWS Secrets Mgr / Vault / KMS)"]
  end

  A5 -.->|"agent requests a tool call"| H1
  H1 --> H2 --> H3
  H3 -.->|"retrieve session token"| V2
  V2 -.->|"session token"| H3
  H3 --> H4 --> H5
  H5 -.->|"result only (no token)"| A5

  classDef danger fill:#14141f,stroke:#f08080,color:#f08080
  style AGENT_PROC fill:#0c0c14,stroke:#5eead4,stroke-width:1.5px,color:#e4e4e8
  style BOUND fill:#08080c,stroke:#f0a868,stroke-width:2px,color:#f0a868
  style HARNESS_PROC fill:#0c0c14,stroke:#5eead4,stroke-width:1.5px,color:#e4e4e8
  style VAULT_PROC fill:#0c0c14,stroke:#f0a868,stroke-width:1.5px,color:#f0a868
  style A1 fill:#101018,stroke:#82e0aa,color:#82e0aa
  style A2 fill:#101018,stroke:#82e0aa,color:#82e0aa
  style A3 fill:#101018,stroke:#82e0aa,color:#82e0aa
  style A4 fill:#101018,stroke:#82e0aa,color:#82e0aa
  style A5 fill:#101018,stroke:#5eead4,color:#e4e4e8
  style H1 fill:#101018,stroke:#5eead4,color:#e4e4e8
  style H2 fill:#101018,stroke:#5eead4,color:#e4e4e8
  style H3 fill:#101018,stroke:#5eead4,color:#e4e4e8
  style H4 fill:#101018,stroke:#5eead4,color:#5eead4
  style H5 fill:#101018,stroke:#5eead4,color:#e4e4e8
  style V1 fill:#101018,stroke:#f0a868,color:#e4e4e8
  style V2 fill:#101018,stroke:#f0a868,color:#e4e4e8

Note: This is DD-20 IronCurtain's credential quarantine realized without a MITM proxy. IronCurtain's model: the agent holds fake keys, a proxy swaps for real ones. This module's model: the agent holds no keys, the harness retrieves real ones from a vault and performs token exchange. The property is identical — the agent cannot exfiltrate real credentials because it does not possess them. The worst an injected agent can do is request unauthorized tool calls, which the scope-check middleware blocks deterministically.


Diagram 5 — Privilege Escalation Chain (ASI10)

Type: Attack chain / failure flow Purpose: How ASI03 (Excessive Agency) and ASI10 (Broken Access Control) compound into a privilege-escalation chain — and the defense-in-depth that stops it. Two identity-layer failures (over-provisioned NHI + missing scope enforcement) let an injected agent exceed its intended privilege. The defense is the conjunction of four layers, any one of which can fail safely. Reading the diagram: The red path (top) is the escalation chain — two failures compound. The green path (bottom) is the defense-in-depth: each layer is independent, and the injection must defeat all four to escalate. The scope-check middleware (layer 2) is the deterministic gate this module's code implements.

flowchart TB
  INJ["INJECTED AGENT<br/>coerced into calling orders:delete<br/>during a lookup task"]

  subgraph CHAIN["THE ESCALATION CHAIN (ASI03 + ASI10)"]
    direction TB
    F1["ASI03 — over-provisioned NHI<br/>token has orders:* instead of orders:read<br/>(engineer copied a broad policy 'to save time')"]:::danger
    F2["ASI10 — missing scope check<br/>harness assumes the model wouldn't<br/>request an unauthorized action"]:::danger
    F3["ASI10 — missing API-side enforcement<br/>the orders API doesn't verify scope"]:::danger
    F4["PRIVILEGE ESCALATION<br/>injected agent deletes orders<br/>identity layer fully breached"]:::danger
    F1 --> F2 --> F3 --> F4
  end

  subgraph DEFENSE["DEFENSE-IN-DEPTH (the conjunction)"]
    direction TB
    D1["1. Per-task scoped credential (B5.2)<br/>token minted with orders:read only<br/>at issuance — narrow"]:::good
    D2["2. Scope-check middleware (B5.3)<br/>deterministic gate before the call<br/>orders:read ≠ orders:delete → BLOCK"]:::good
    D3["3. Credential isolation (B5.3)<br/>agent cannot steal a broader token<br/>it holds no credential"]:::good
    D4["4. API-side scope enforcement<br/>the API verifies the token's scope<br/>last line of defense"]:::good
    D1 --> D2 --> D3 --> D4
  end

  INJ -->|"if all four fail"| CHAIN
  INJ -->|"must defeat all four"| DEFENSE

  classDef danger fill:#14141f,stroke:#f08080,stroke-width:1.5px,color:#f08080
  classDef good fill:#14141f,stroke:#82e0aa,stroke-width:1.5px,color:#82e0aa
  style INJ fill:#14141f,stroke:#f0a868,stroke-width:2px,color:#f0a868
  style CHAIN fill:#0c0c14,stroke:#f08080,stroke-width:1px,color:#9494a0
  style DEFENSE fill:#0c0c14,stroke:#82e0aa,stroke-width:1px,color:#9494a0
  style F1 fill:#101018,stroke:#f08080,color:#e4e4e8
  style F2 fill:#101018,stroke:#f08080,color:#e4e4e8
  style F3 fill:#101018,stroke:#f08080,color:#e4e4e8
  style F4 fill:#101018,stroke:#f08080,stroke-width:1.5px,color:#f08080
  style D1 fill:#101018,stroke:#82e0aa,color:#e4e4e8
  style D2 fill:#101018,stroke:#82e0aa,stroke-width:1.5px,color:#82e0aa
  style D3 fill:#101018,stroke:#82e0aa,color:#e4e4e8
  style D4 fill:#101018,stroke:#82e0aa,color:#e4e4e8

Note: The escalation chain requires three simultaneous failures (over-provisioned NHI, missing scope check, missing API enforcement). The defense-in-depth requires that the injection defeat four independent layers to escalate. The scope-check middleware (layer 2) is the deterministic gate this module's code implements — it is the identity-layer analogue of B2's Layer 3 taint gate. Structural, enumerable, no model in the loop, no bypass rate. This is the B2.3 resolution (determinism on the structural boundary) applied to the privilege boundary.

# Diagrams — Module B5: Identity and Permission Design

**Module**: B5 — Identity and Permission Design
**Diagram count**: 5
**Tool**: Mermaid (primary). Each diagram validated in [Mermaid Live Editor](https://mermaid.live).

---

## Diagram 1 — The Borrowed-Credential Problem

**Type**: Comparison / blast-radius
**Purpose**: The single most important visual in the module. An agent given a human's credentials acts with the human's full privilege. The blast radius is the credential's scope, not the task's requirement — and an injected agent with admin credentials is an admin compromise. The NHI column (scoped to the task) is strictly better on every security axis.
**Reading the diagram**: Two parallel paths from the same task. The left path (borrowed credential) inherits the human's full privilege; the right path (NHI) is scoped to the task. The red callouts mark where injection turns each into a compromise — and how different the blast radius is.

```mermaid
flowchart TB
  TASK["A TASK: look up one customer's order<br/>(requires orders:read, one customer)"]

  subgraph BORROWED["PATH A — borrowed human credential"]
    direction TB
    B1["Developer's AWS key<br/>long-lived · read-write · all customers"]
    B2["Agent holds the key in its env<br/>privilege = developer's full access"]
    B3["INJECTED AGENT<br/>exfiltrates or uses the key"]:::danger
    B4["BLAST RADIUS: all orders<br/>read AND write · entire account"]:::danger
    B1 --> B2 --> B3 --> B4
  end

  subgraph NHI["PATH B — non-human identity (scoped)"]
    direction TB
    N1["NHI role: order-lookup-agent<br/>short-lived · orders:read · one customer_id"]
    N2["Harness mints action token per call<br/>agent never holds the raw credential"]
    N3["INJECTED AGENT<br/>token has orders:read only"]:::good
    N4["BLAST RADIUS: one customer<br/>read-only · token expires in seconds"]:::good
    N1 --> N2 --> N3 --> N4
  end

  TASK --> BORROWED
  TASK --> NHI

  classDef danger fill:#14141f,stroke:#f08080,stroke-width:1.5px,color:#f08080
  classDef good fill:#14141f,stroke:#82e0aa,stroke-width:1.5px,color:#82e0aa
  style TASK fill:#14141f,stroke:#5eead4,stroke-width:1.5px,color:#e4e4e8
  style B1 fill:#101018,stroke:#f08080,color:#e4e4e8
  style B2 fill:#101018,stroke:#f08080,color:#e4e4e8
  style N1 fill:#101018,stroke:#82e0aa,color:#e4e4e8
  style N2 fill:#101018,stroke:#82e0aa,color:#e4e4e8
  style BORROWED fill:#0c0c14,stroke:#f08080,stroke-width:1px,color:#9494a0
  style NHI fill:#0c0c14,stroke:#82e0aa,stroke-width:1px,color:#9494a0
```

> **Note**: The task is identical on both paths — "look up one order." The difference is entirely in the identity layer. The borrowed credential makes the blast radius the human's privilege; the NHI makes the blast radius the task's requirement. When injection hits (B2 showed ~50% success against undefended agents), Path A is an account-wide compromise; Path B is a one-customer, read-only, seconds-long window.

---

## Diagram 2 — NHI vs. Human Identity

**Type**: Comparison matrix
**Purpose**: Why a non-human identity is the correct model for agents, not a borrowed human credential. Three properties NHIs have that borrowed human credentials lack: scoped to the workload, independently revocable, and auditable as non-human. The NHI is the fastest-growing identity category in cloud security.
**Reading the diagram**: Left column = the borrowed human credential; right = the NHI. Each row is a property. The right column is strictly better on every axis. The bottom row is the audit distinction that matters for incident response.

```mermaid
flowchart LR
  subgraph HUMAN["BORROWED HUMAN CREDENTIAL"]
    direction TB
    H1["SCOPED TO THE WORKLOAD? — NO<br/>privilege = the human's full access"]
    H2["INDEPENDENTLY REVOCABLE? — NO<br/>disabling it disrupts the human"]
    H3["AUDITABLE AS NON-HUMAN? — NO<br/>logs say 'engineer@corp did X'"]
    H4["LIFETIME — long-lived<br/>months until rotation"]
  end
  subgraph NHI["NON-HUMAN IDENTITY (NHI)"]
    direction TB
    N1["SCOPED TO THE WORKLOAD? — YES<br/>privilege = the task's requirement"]:::good
    N2["INDEPENDENTLY REVOCABLE? — YES<br/>rotate the NHI, human unaffected"]:::good
    N3["AUDITABLE AS NON-HUMAN? — YES<br/>logs say 'order-lookup-agent did X'"]:::good
    N4["LIFETIME — short-lived<br/>seconds to minutes, per-task"]:::good
  end

  H1 -.->|"the fix"| N1
  H2 -.->|"the fix"| N2
  H3 -.->|"the fix"| N3
  H4 -.->|"the fix"| N4

  classDef good fill:#101018,stroke:#82e0aa,stroke-width:1.5px,color:#82e0aa
  style HUMAN fill:#0c0c14,stroke:#f08080,stroke-width:1px,color:#9494a0
  style NHI fill:#0c0c14,stroke:#82e0aa,stroke-width:1px,color:#9494a0
  style H1 fill:#101018,stroke:#f08080,color:#e4e4e8
  style H2 fill:#101018,stroke:#f08080,color:#e4e4e8
  style H3 fill:#101018,stroke:#f08080,color:#e4e4e8
  style H4 fill:#101018,stroke:#f08080,color:#e4e4e8
```

> **Note**: The NHI is not a "more secure version" of the borrowed credential — it is a different identity model. The human credential was designed for a human who reasons about scope and is accountable; the NHI is designed for a process that does neither. Enterprise identity teams that spent a decade on human IAM are now confronting an NHI population that outnumbers humans 10:1 — and every agent you deploy adds to it.

---

## Diagram 3 — Per-Task Scoped Credential Flow (Token Exchange, RFC 8693)

**Type**: Sequence / flow
**Purpose**: The technical anchor. How an agent goes from "I need to call the orders API" to "I have a scoped action token that permits exactly that and nothing else" — without the agent ever holding a long-lived secret. RFC 8693 token exchange is the mechanism; the harness performs the exchange, not the agent.
**Reading the diagram**: Top-to-bottom as a sequence. The agent holds a session token (short-lived, NHI-scoped). For each action, the harness exchanges it for an action token (narrower, shorter-lived, audience-specific). The agent never sees the action token's raw value; the harness makes the call and returns only the result.

```mermaid
flowchart TB
  AGENT["AGENT PROCESS<br/>holds session token (NHI-scoped, TTL: minutes)"]
  HARNESS["HARNESS<br/>intercepts the tool call<br/>(B2 Layer 3 / B4 gate — same interception point)"]
  VAULT["CREDENTIAL VAULT<br/>session token lives here<br/>agent process has NO reference"]
  AUTH["AUTHORIZATION SERVER<br/>RFC 8693 token exchange endpoint"]
  ACTION["ACTION TOKEN<br/>scope: orders:read<br/>audience: orders-api<br/>TTL: 60 seconds"]
  API["TARGET API (orders-api)<br/>verifies token scope on its end"]
  RESULT["RESULT returned to agent<br/>the token is NOT in the return value"]

  AGENT -->|"agent requests lookup_order(customer_id)"| HARNESS
  HARNESS -->|"retrieve session token"| VAULT
  VAULT -->|"session token (never exposed to agent)"| HARNESS
  HARNESS -->|"POST token-exchange<br/>grant_type=token-exchange<br/>scope=orders:read<br/>audience=orders-api"| AUTH
  AUTH -->|"issue action token"| HARNESS
  HARNESS -->|"scope_check(token, orders:read, orders-api)<br/>deterministic gate"| ACTION
  ACTION -->|"authenticated call"| API
  API -->|"result"| HARNESS
  HARNESS -->|"result only — no token"| RESULT

  classDef danger fill:#14141f,stroke:#f08080,stroke-width:1.5px,color:#f08080
  style AGENT fill:#14141f,stroke:#5eead4,stroke-width:1.5px,color:#e4e4e8
  style HARNESS fill:#14141f,stroke:#5eead4,stroke-width:1.5px,color:#5eead4
  style VAULT fill:#14141f,stroke:#f0a868,stroke-width:1.5px,color:#f0a868
  style AUTH fill:#14141f,stroke:#5eead4,color:#e4e4e8
  style ACTION fill:#101018,stroke:#82e0aa,color:#82e0aa
  style API fill:#14141f,stroke:#5eead4,color:#e4e4e8
  style RESULT fill:#101018,stroke:#82e0aa,color:#82e0aa
```

> **Note**: The two load-bearing properties: (1) the harness performs the exchange — the agent process has no reference to the vault and never sees the raw action token; (2) the action token's scope is a compiled claim (`orders:read`), not a prompt-level instruction the model can be coerced into ignoring. An injected agent that tries to escalate to `orders:delete` hits the scope check: the action token was minted for `orders:read`, the check fails, the call is blocked. Deterministic, no model in the loop.

---

## Diagram 4 — The Credential Isolation Architecture

**Type**: Architecture / boundary
**Purpose**: The load-bearing principle made visual: credentials live in a vault the *harness* accesses, not in the agent process. The agent never sees the raw credential. This is the DD-20 IronCurtain credential-quarantine property (the agent holds only fake keys; the harness swaps for real ones at the boundary) realized via a vault + token-exchange flow without a MITM proxy.
**Reading the diagram**: The dashed line is the trust boundary. Above it: the agent process — which has NO credentials in its env, config, or system prompt. Below it: the harness, the vault, the authorization server. The agent communicates intent (tool calls); the harness performs authenticated actions and returns only results. The credential never crosses the boundary upward.

```mermaid
flowchart TB
  subgraph AGENT_PROC["AGENT PROCESS — above the trust boundary"]
    direction TB
    A1["system prompt — NO API keys"]
    A2["environment — NO AWS_SECRET_ACCESS_KEY"]
    A3["config files — NO OAuth tokens"]
    A4["context window — receives RESULTS, never credentials"]
    A5["tool-call output — 'I called lookup_order<br/>and got this result'"]
  end

  BOUND["═══ TRUST BOUNDARY ═══<br/>credentials never cross upward<br/>the agent cannot exfiltrate what it does not possess"]

  subgraph HARNESS_PROC["HARNESS — below the trust boundary"]
    direction TB
    H1["tool-call interceptor<br/>(B2 Layer 3 / B4 gate)"]
    H2["scope determination<br/>(what does this task need?)"]
    H3["token exchange (RFC 8693)<br/>mint action token from session"]
    H4["scope_check middleware<br/>(deterministic gate)"]
    H5["authenticated API call<br/>uses the token, discards it"]
  end

  subgraph VAULT_PROC["CREDENTIAL VAULT — the harness accesses, not the agent"]
    direction TB
    V1["session tokens (NHI-scoped)"]
    V2["secrets manager<br/>(AWS Secrets Mgr / Vault / KMS)"]
  end

  A5 -.->|"agent requests a tool call"| H1
  H1 --> H2 --> H3
  H3 -.->|"retrieve session token"| V2
  V2 -.->|"session token"| H3
  H3 --> H4 --> H5
  H5 -.->|"result only (no token)"| A5

  classDef danger fill:#14141f,stroke:#f08080,color:#f08080
  style AGENT_PROC fill:#0c0c14,stroke:#5eead4,stroke-width:1.5px,color:#e4e4e8
  style BOUND fill:#08080c,stroke:#f0a868,stroke-width:2px,color:#f0a868
  style HARNESS_PROC fill:#0c0c14,stroke:#5eead4,stroke-width:1.5px,color:#e4e4e8
  style VAULT_PROC fill:#0c0c14,stroke:#f0a868,stroke-width:1.5px,color:#f0a868
  style A1 fill:#101018,stroke:#82e0aa,color:#82e0aa
  style A2 fill:#101018,stroke:#82e0aa,color:#82e0aa
  style A3 fill:#101018,stroke:#82e0aa,color:#82e0aa
  style A4 fill:#101018,stroke:#82e0aa,color:#82e0aa
  style A5 fill:#101018,stroke:#5eead4,color:#e4e4e8
  style H1 fill:#101018,stroke:#5eead4,color:#e4e4e8
  style H2 fill:#101018,stroke:#5eead4,color:#e4e4e8
  style H3 fill:#101018,stroke:#5eead4,color:#e4e4e8
  style H4 fill:#101018,stroke:#5eead4,color:#5eead4
  style H5 fill:#101018,stroke:#5eead4,color:#e4e4e8
  style V1 fill:#101018,stroke:#f0a868,color:#e4e4e8
  style V2 fill:#101018,stroke:#f0a868,color:#e4e4e8
```

> **Note**: This is DD-20 IronCurtain's credential quarantine realized without a MITM proxy. IronCurtain's model: the agent holds fake keys, a proxy swaps for real ones. This module's model: the agent holds *no* keys, the harness retrieves real ones from a vault and performs token exchange. The property is identical — the agent cannot exfiltrate real credentials because it does not possess them. The worst an injected agent can do is request unauthorized tool calls, which the scope-check middleware blocks deterministically.

---

## Diagram 5 — Privilege Escalation Chain (ASI10)

**Type**: Attack chain / failure flow
**Purpose**: How ASI03 (Excessive Agency) and ASI10 (Broken Access Control) compound into a privilege-escalation chain — and the defense-in-depth that stops it. Two identity-layer failures (over-provisioned NHI + missing scope enforcement) let an injected agent exceed its intended privilege. The defense is the conjunction of four layers, any one of which can fail safely.
**Reading the diagram**: The red path (top) is the escalation chain — two failures compound. The green path (bottom) is the defense-in-depth: each layer is independent, and the injection must defeat all four to escalate. The scope-check middleware (layer 2) is the deterministic gate this module's code implements.

```mermaid
flowchart TB
  INJ["INJECTED AGENT<br/>coerced into calling orders:delete<br/>during a lookup task"]

  subgraph CHAIN["THE ESCALATION CHAIN (ASI03 + ASI10)"]
    direction TB
    F1["ASI03 — over-provisioned NHI<br/>token has orders:* instead of orders:read<br/>(engineer copied a broad policy 'to save time')"]:::danger
    F2["ASI10 — missing scope check<br/>harness assumes the model wouldn't<br/>request an unauthorized action"]:::danger
    F3["ASI10 — missing API-side enforcement<br/>the orders API doesn't verify scope"]:::danger
    F4["PRIVILEGE ESCALATION<br/>injected agent deletes orders<br/>identity layer fully breached"]:::danger
    F1 --> F2 --> F3 --> F4
  end

  subgraph DEFENSE["DEFENSE-IN-DEPTH (the conjunction)"]
    direction TB
    D1["1. Per-task scoped credential (B5.2)<br/>token minted with orders:read only<br/>at issuance — narrow"]:::good
    D2["2. Scope-check middleware (B5.3)<br/>deterministic gate before the call<br/>orders:read ≠ orders:delete → BLOCK"]:::good
    D3["3. Credential isolation (B5.3)<br/>agent cannot steal a broader token<br/>it holds no credential"]:::good
    D4["4. API-side scope enforcement<br/>the API verifies the token's scope<br/>last line of defense"]:::good
    D1 --> D2 --> D3 --> D4
  end

  INJ -->|"if all four fail"| CHAIN
  INJ -->|"must defeat all four"| DEFENSE

  classDef danger fill:#14141f,stroke:#f08080,stroke-width:1.5px,color:#f08080
  classDef good fill:#14141f,stroke:#82e0aa,stroke-width:1.5px,color:#82e0aa
  style INJ fill:#14141f,stroke:#f0a868,stroke-width:2px,color:#f0a868
  style CHAIN fill:#0c0c14,stroke:#f08080,stroke-width:1px,color:#9494a0
  style DEFENSE fill:#0c0c14,stroke:#82e0aa,stroke-width:1px,color:#9494a0
  style F1 fill:#101018,stroke:#f08080,color:#e4e4e8
  style F2 fill:#101018,stroke:#f08080,color:#e4e4e8
  style F3 fill:#101018,stroke:#f08080,color:#e4e4e8
  style F4 fill:#101018,stroke:#f08080,stroke-width:1.5px,color:#f08080
  style D1 fill:#101018,stroke:#82e0aa,color:#e4e4e8
  style D2 fill:#101018,stroke:#82e0aa,stroke-width:1.5px,color:#82e0aa
  style D3 fill:#101018,stroke:#82e0aa,color:#e4e4e8
  style D4 fill:#101018,stroke:#82e0aa,color:#e4e4e8
```

> **Note**: The escalation chain requires *three* simultaneous failures (over-provisioned NHI, missing scope check, missing API enforcement). The defense-in-depth requires that the injection defeat *four* independent layers to escalate. The scope-check middleware (layer 2) is the deterministic gate this module's code implements — it is the identity-layer analogue of B2's Layer 3 taint gate. Structural, enumerable, no model in the loop, no bypass rate. This is the B2.3 resolution (determinism on the structural boundary) applied to the privilege boundary.