# Teaching Script — Module B5: Identity and Permission Design

**Course**: Course 2B — Securing & Attacking Harnesses and LLMs
**Module**: B5 — Identity and Permission Design
**Duration**: ~35 minutes (spoken at ~140 wpm)
**Format**: Verbatim transcript with `[SLIDE N]` cues. Read aloud or use as speaker notes.

---

[SLIDE 1 — Title]

Welcome to Module B5. Identity and Permission Design. This is the depth upgrade the course design conversation flagged as underrepresented — and it is central in real enterprise agent deployments. An agent is not a human. Giving an agent a human's credentials means the agent acts with the human's full privilege. And an injected agent with admin credentials is an admin compromise. This module builds the identity layer that every other defense in this course rests on. The agent never holds a long-lived secret. The harness does.

[SLIDE 2 — What B2 and B4 deferred]

If you came from B2 and B4, you have the injection defense and the tool-trust gate. B2 built the five-layer defense against prompt injection. B4 built the signed-manifest and provenance verification for tools. Both modules deferred one question: who is the agent, and what is it allowed to do? B2's Layer 5 — capability minimization — and B4's gate both depend on a credential substrate that neither module constructed. That substrate is this module. The identity layer is the blast-radius floor. An agent with no credentials cannot exfiltrate even if it is fully injected. An agent with an admin's credentials can exfiltrate the entire account the moment injection succeeds. Everything else in the course is built on top of this floor.

[SLIDE 3 — Two OWASP risks]

OWASP's Agentic Top 10 names the two risks this module owns. ASI03, Excessive Agency: the agent has more permission than its task requires. Over-provisioned non-human identities, broad scopes. The credential permits more than the task needs. ASI10, Broken Access Control: the agent acts on behalf of an under-authenticated principal, or a scope-check gap lets it exceed its intended privilege. Privilege-escalation chains. Both are identity-layer failures. And here is the uncomfortable truth about real deployments: they do not fail on injection sophistication. They fail on the engineer who pasted their personal AWS keys into the agent's environment variable to get it working, forgot to scope them, and shipped to production. This module is how you stop that from being the story of your deployment.

[SLIDE 4 — B5.1 The identity problem]

Sub-section one. The identity problem and non-human identities. Why borrowing human credentials is the failure, and the NHI model that fixes it.

[SLIDE 5 — The borrowed-credential problem]

The default — the thing engineers do when they are prototyping — is give the agent their own credentials. A developer's OAuth token. A personal AWS access key. A Slack bot token. The reasoning is pragmatic: I have the credentials, the agent needs to call the API, I will paste my key into the environment variable. This works. It also creates the most common and most dangerous identity failure in deployed agents. The problem is not that the agent has credentials. The problem is whose credentials it has, and what privilege they carry. The agent inherits the human's full privilege — including everything the human can do that the task does not require. An agent that needs to read one S3 bucket, given keys that can read every bucket in the account, can now exfiltrate every bucket in the account. The blast radius is the credential's scope, not the task's requirement. Now add injection. B2 showed that indirect injection succeeds roughly fifty percent of the time against undefended agents. An injected agent that holds an admin's credentials is an admin compromise. And it looks legitimate in the access logs, because the credential was never stolen — it was handed over. The compromise is invisible.

[SLIDE 6 — The NHI model]

The fix is that the agent gets its own identity. Not a human's credentials, borrowed and de-scoped — a non-human identity, an NHI, minted for the agent, scoped to the agent's task, and revocable independently of any human. An NHI is any identity that authenticates a workload or a process rather than a person. Service accounts in AWS, Google, Azure, Kubernetes. GitHub Apps. The agent is an NHI, not a human, and its identity should reflect that. The NHI model has three properties that borrowed human credentials lack. One: scoped to the workload. The NHI is minted with exactly the permissions the workload requires. Two: independently revocable. The NHI can be revoked without disabling the human. Three: auditable as non-human. Access logs distinguish the agent did this from the human did this. Non-human identities are the fastest-growing identity category in cloud security. Enterprise identity teams that spent a decade on human identity management are now confronting an NHI population that outnumbers humans ten to one — and every agent you deploy adds to it.

[SLIDE 7 — B5.2 Least privilege and token exchange]

Sub-section two. Least privilege, delegated auth, and token exchange. The agent never holds a long-lived secret.

[SLIDE 8 — Per-task scoped credentials]

Least privilege is the oldest principle in security. For agents it takes a specific form: an agent should have exactly the permissions its current task requires, no more. Not the permissions the agent's role might need across a day of work — the permissions this task needs. The distinction matters because agents are task-scoped. A support agent looking up an order and the same support agent issuing a refund are two different tasks with two different privilege requirements. If the agent holds both privileges at all times, an injection during the lookup task can trigger a refund. If the agent holds only lookup privileges during the lookup task, the injection cannot escalate — the credential does not permit it. The operational realization is per-task scoped credentials via token exchange. RFC 8693, OAuth 2.0 Token Exchange. The agent holds a session token — short-lived, NHI-scoped. For each action, the harness exchanges that token for an action token — narrower, shorter-lived, audience-specific. The agent never holds a long-lived secret. The session token is short-lived and exchangeable. The action token is even shorter and narrower. An injected agent that captures the action token gains, at most, the ability to perform one action against one audience for the token's remaining lifetime — seconds. It cannot pivot to other APIs, other scopes, or other principals. This is least privilege enforced at the credential layer, not the prompt layer.

[SLIDE 9 — Workload identity]

The most mature workload-identity standard is SPIFFE, with its reference implementation SPIRE. A SPIFFE identity is a cryptographically-attested workload identity. The agent process does not possess a key — it requests a signed identity document from the SPIRE agent on the host, which vouches for the workload based on platform attestation. The identity is short-lived and continuously re-attested. A compromised agent cannot steal its credential because it does not have one. The moment the platform stops vouching for it — the task ends, the pod is torn down — the identity ceases to be valid. Cloud-managed equivalents — AWS IAM Roles for Service Accounts, GCP Workload Identity, Azure Managed Identity — provide the same property with less operational overhead. SPIFFE is the open standard for environments that span clouds or need the control plane in-house.

[SLIDE 10 — B5.3 Credential lifecycle and isolation]

Sub-section three. The credential lifecycle and credential isolation. Where credentials live — and the load-bearing principle that the agent never sees the raw credential.

[SLIDE 11 — Where credentials live]

The single most important design decision in this module. Credentials live in a secrets manager the harness accesses, not in the agent process. The agent never sees the raw credential. Here is the failure mode this prevents. An agent that can read its own credentials — from an environment variable, a config file, or worst of all the system prompt — can exfiltrate them. An injected agent that reads AWS secret access key from its environment and posts it to an attacker endpoint has handed over a long-lived, broadly-scoped credential. The injection did not need to use the credential. It needed only to read it and exfiltrate it. Once exfiltrated, the credential is compromised regardless of the agent's subsequent behavior. The defense is credential isolation. The credential is never in the agent's environment. The agent process does not have the key in its env. It does not have the OAuth token in a config file. It does not have the API key in its system prompt — the single worst anti-pattern, because the prompt is text the model reads and can be coerced into repeating. The credential lives in a vault — AWS Secrets Manager, HashiCorp Vault, a cloud KMS — that the harness accesses at the moment it needs to make an authenticated call. The harness retrieves the credential, mints the action token, makes the call, and returns only the result to the agent. The agent sees: I called lookup order and got this result. It does not see: the token, the key, the secret, the authorization header.

[SLIDE 12 — DD-20 IronCurtain]

The reference architecture for this is DD-20 IronCurtain's credential quarantine. IronCurtain's model: the agent runs with only fake API keys. A TLS-terminating MITM proxy, controlled by the harness, hot-swaps the fake keys for real ones at the network boundary. The agent never holds a real credential. Even a fully-compromised agent cannot exfiltrate real credentials because it does not have them. The worst it can do is use the fake keys, which only work through IronCurtain's controlled proxy. You do not need to implement IronCurtain's exact proxy architecture to realize the property. The token-exchange-and-vault flow from this module achieves credential isolation without a proxy. But IronCurtain is the conceptual north star: the agent never holds real credentials. Whether you achieve that via a proxy, a vault, or a workload-identity attestation that requires no static credential at all — the property is the same. The agent cannot exfiltrate what it does not possess.

[SLIDE 13 — The privilege escalation chain]

Now the attack side. OWASP ASI10, Broken Access Control, is the identity-layer failure where the agent acts on behalf of an under-authenticated principal, or where a privilege-escalation chain lets the agent exceed its intended scope. The classic pattern. The agent holds a session token scoped to orders:read. An injection coerces the agent into calling a tool that performs orders:delete. The correct behavior: the scope check fails, the call is blocked. The ASI10 failure: the harness does not check the token's scope before making the call, the API's scope check is missing, or the token was over-scoped at issuance — orders:star instead of orders:read. The action succeeds. The agent has exceeded its intended privilege. The chain gets worse when combined with ASI03. If the agent's NHI was over-provisioned at issuance, and the scope check is also missing, and the API does not enforce scope — three failures compound into a full privilege escalation. The defense is defense-in-depth at the identity layer, the same way B2 is defense-in-depth at the injection layer. Layer one: per-task scoped credentials, narrow at issuance. Layer two: the scope-check middleware, the deterministic gate before the call. Layer three: credential isolation, the agent cannot steal a broader credential. Layer four: API-side scope enforcement, the last line. The conjunction is what holds. Any single layer can fail. The identity layer is secure when an injection must defeat all four to escalate.

[SLIDE 14 — The load-bearing code]

Here is the load-bearing code. The execute tool call function. Step one: the harness mints an action token via token exchange — the harness, not the agent, holds the session token. Step two: the scope check runs before the call — deterministic, no model in the loop. Step three: the harness makes the authenticated call, uses the token, discards it. Step four: the function returns the result — the token is not in the return value. Here is what happens when an injected agent requests orders:delete during a lookup task. The action token was minted with orders:read only. The scope check compares orders:read against the required orders:delete. They do not match. The check fails. The call is blocked. No model judgment, no bypass rate. This is the B2.3 resolution — determinism on the structural boundary — applied to the privilege boundary. The scope is a compiled claim, not a prompt-level instruction. The agent cannot talk its way past a boolean.

[SLIDE 15 — Lab and what's next]

The lab has you build the Credential Isolation Layer. Three pieces. First, a token-exchange flow that mints a scoped action token per task from a session token, following RFC 8693. Second, a scope-check middleware that verifies the action token covers the requested action before the call. Third, a credential vault the harness accesses — the agent process has no reference to the vault and never sees the raw credential. The lab includes an injection scenario: an injected agent attempts orders:delete during a lookup task, and the scope-check gate blocks it deterministically. Next: B6, Inter-Agent Trust and Communication Security. When agents talk to agents, the identity layer from this module becomes the trust substrate for inter-agent channels. B6 builds the authentication, authorization, and message-integrity controls for multi-agent systems. Let's build it.
