Module: B5 — Identity and Permission Design Diagram count: 5 Tool: Mermaid (primary). Each diagram validated in Mermaid Live Editor.
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.
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.
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 toorders:deletehits the scope check: the action token was minted fororders:read, the check fails, the call is blocked. Deterministic, no model in the loop.
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.
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.