{
  "module": "B5 — Identity and Permission Design",
  "course": "2B — Securing & Attacking Harnesses and LLMs",
  "version": "1.0.0",
  "duration_minutes": 35,
  "total_questions": 15,
  "bloom_distribution": {
    "target": "20% recall / 40% application / 40% analysis-design",
    "actual": { "recall": 3, "application": 6, "analysis": 6 }
  },
  "passing_score_percent": 70,
  "questions": [
    {
      "id": "Q01", "bloom": "recall", "type": "multiple_choice",
      "prompt": "What is the borrowed-credential problem, and why is it the most common enterprise agent identity failure?",
      "options": [
        "It is not a problem — agents need credentials to function, and using the developer's keys is the standard approach.",
        "Engineers paste their personal credentials (OAuth token, AWS keys) into the agent's env var 'to get it working.' The agent inherits the human's FULL privilege — the blast radius is the credential's scope, not the task's requirement. An injected agent with borrowed admin credentials is an admin compromise, and it looks legitimate in logs because the credential was never stolen, it was handed over.",
        "The problem is that agents cannot use human credentials at all due to API restrictions.",
        "The problem is only relevant for cloud deployments, not on-premises agents."
      ],
      "answer_index": 1,
      "rationale": "The borrowed-credential problem is the default prototype shortcut that ships to production. The agent inherits the human's full privilege, making the blast radius the human's access — not the task's requirement. When injection hits (~50% against undefended agents per B2/InjecAgent), an injected agent with admin credentials is an admin compromise. The fix is NHIs scoped to the task, never borrowed human credentials."
    },
    {
      "id": "Q02", "bloom": "recall", "type": "multiple_choice",
      "prompt": "What is RFC 8693 (OAuth 2.0 Token Exchange), and why is it the technical anchor for per-task scoped credentials?",
      "options": [
        "A protocol for encrypting OAuth tokens at rest.",
        "A standard that lets a client trade one token for another with a different scope, audience, or subject. The agent holds a session token (short-lived, NHI-scoped); the harness exchanges it per action for an action token (narrower scope, specific audience, TTL of seconds). The agent never holds a long-lived secret.",
        "A replacement for OAuth 2.0 that eliminates the need for authorization servers.",
        "A protocol for sharing credentials between multiple agents."
      ],
      "answer_index": 1,
      "rationale": "RFC 8693 Token Exchange is the mechanism for per-task scoping. The harness exchanges the session token for an action token with the exact scope and audience the task requires. An injected agent that captures the action token gains at most one action against one audience for the token's remaining TTL (seconds) — it cannot pivot to other APIs, scopes, or principals. This is least privilege enforced at the credential layer."
    },
    {
      "id": "Q03", "bloom": "recall", "type": "multiple_choice",
      "prompt": "What is the single most important design decision in B5 regarding where credentials live?",
      "options": [
        "Credentials should be stored in the agent's environment variables for easy access.",
        "Credentials should be embedded in the system prompt so the agent always has them.",
        "Credentials live in a secrets manager the HARNESS accesses, not in the agent process. The agent never sees the raw credential — it has no env var, no config file, no system-prompt API key. The harness retrieves the credential, mints the action token, makes the call, and returns only the result.",
        "Credentials should be shared between the agent and the harness via a shared config file."
      ],
      "answer_index": 2,
      "rationale": "Credential isolation is the load-bearing principle. An agent that can read its own credentials can exfiltrate them — an injected agent that reads AWS_SECRET_ACCESS_KEY and posts it to an attacker endpoint has handed over a long-lived secret. The defense: the credential is never in the agent's environment. The harness accesses the vault; the agent sees only results. The agent cannot exfiltrate what it does not possess."
    },
    {
      "id": "Q04", "bloom": "application", "type": "multiple_choice",
      "prompt": "An engineer gives a support agent their personal AWS keys (read-write, all buckets) to look up customer orders. The agent is later injected. What is the blast radius, and what is the B5 fix?",
      "options": [
        "Blast radius: one customer's orders. Fix: no change needed, the keys are scoped enough.",
        "Blast radius: the engineer's full AWS access (all buckets, read-write) for the lifetime of the keys (months). Fix: mint an NHI scoped to orders:read on one customer_id; put the credential in a vault the harness accesses; use token exchange for per-action scoping; add a scope-check middleware. The injected agent finds no credential to exfiltrate and cannot escalate beyond orders:read.",
        "Blast radius: only the orders API. Fix: add rate limiting.",
        "Blast radius: nothing — injection does not affect credentials."
      ],
      "answer_index": 1,
      "rationale": "The borrowed credential makes the blast radius the human's privilege (all buckets, read-write), not the task's requirement (one customer, read-only). The B5 fix addresses all layers: NHI scoped to the task, credential isolation in a vault, token exchange for per-action tokens, and a scope-check gate. The injected agent cannot exfiltrate the credential (it's not in the agent's env) and cannot escalate (the action token is scoped to orders:read only)."
    },
    {
      "id": "Q05", "bloom": "application", "type": "multiple_choice",
      "prompt": "During a lookup task, an injected agent is coerced into requesting orders:delete. The harness has implemented B5's token exchange and scope-check middleware. What happens?",
      "options": [
        "The agent's request succeeds because the harness trusts the model not to request unauthorized actions.",
        "The harness mints an action token scoped to orders:read (the task's requirement). 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.",
        "The harness mints a new token with orders:delete scope to fulfill the request.",
        "The harness asks the user for confirmation before proceeding."
      ],
      "answer_index": 1,
      "rationale": "The scope-check middleware is the deterministic gate. The action token was minted for the task's required scope (orders:read), not the injected request (orders:delete). The check is structural — does the token's scope cover the requested action? — and the answer is a compiled boolean. The agent cannot talk its way past it. This is the B2.3 resolution (determinism on the structural boundary) applied to the privilege boundary."
    },
    {
      "id": "Q06", "bloom": "application", "type": "multiple_choice",
      "prompt": "An agent's NHI was minted with scope orders:* (all orders operations) 'to save time' instead of orders:read. The scope-check middleware is also missing. An injection coerces the agent into calling orders:delete. Which OWASP risks have compounded, and what is the result?",
      "options": [
        "Only ASI03 (Excessive Agency) — the NHI is over-provisioned but the call is blocked by the API.",
        "Only ASI10 (Broken Access Control) — the scope check is missing but the NHI is correctly scoped.",
        "ASI03 (over-provisioned NHI, orders:* instead of orders:read) + ASI10 (missing scope-check middleware) compound into a privilege-escalation chain. The injected agent successfully calls orders:delete — the identity layer is fully breached. Two identity-layer failures compound into full escalation.",
        "Neither risk applies — the API will block the unauthorized call regardless."
      ],
      "answer_index": 2,
      "rationale": "ASI03 (Excessive Agency — the NHI has more permission than the task requires) and ASI10 (Broken Access Control — the scope-check gap lets the agent exceed its intended privilege) compound. The over-provisioned token permits orders:delete; the missing scope check doesn't catch it; if the API also lacks enforcement, the call succeeds. The defense is defense-in-depth: narrow tokens at issuance + scope-check middleware + credential isolation + API-side enforcement. The conjunction holds; any single layer failing is caught by the others."
    },
    {
      "id": "Q07", "bloom": "application", "type": "multiple_choice",
      "prompt": "You are designing the credential flow for an agent that reads from S3 and writes to DynamoDB. How should the token-exchange flow handle the two different actions?",
      "options": [
        "Mint one token with both s3:GetObject and dynamodb:PutItem scopes, valid for the agent's entire session.",
        "Mint two separate action tokens: one with scope s3:GetObject and audience=s3, another with scope dynamodb:PutItem and audience=dynamodb, each with a short TTL. The harness exchanges for the appropriate token per action. The S3 token cannot be used for DynamoDB and vice versa.",
        "Use the developer's personal AWS keys for both — it's simpler.",
        "Mint a token with scope * and let the agent decide which permissions to use."
      ],
      "answer_index": 1,
      "rationale": "Per-task scoped credentials mean each action gets its own action token with the exact scope and audience required. The S3 read token (s3:GetObject, audience=s3) cannot be used for DynamoDB writes, and the DynamoDB token (dynamodb:PutItem, audience=dynamodb) cannot be used for S3. Both are short-lived. An injection during the S3 read cannot escalate to DynamoDB writes because the read-scoped token doesn't permit it — the scope_check blocks it deterministically."
    },
    {
      "id": "Q08", "bloom": "application", "type": "multiple_choice",
      "prompt": "An agent's system prompt contains the line 'Your API key is sk-abc123 for calling the orders service.' What is wrong, and what is the fix?",
      "options": [
        "Nothing is wrong — the system prompt is private to the agent.",
        "The system prompt is TEXT THE MODEL READS. An injected agent can be coerced into repeating the prompt's contents — including the API key — to an attacker. The key is in the context window, fully visible, and extractable via prompt injection. Fix: remove the key from the system prompt; store it in a vault the harness accesses; the harness performs token exchange and makes the authenticated call. The system prompt contains NO secrets, ever.",
        "The key should be moved to an environment variable instead.",
        "The key should be obfuscated (e.g., base64-encoded) in the system prompt."
      ],
      "answer_index": 1,
      "rationale": "Putting a credential in the system prompt is the single worst anti-pattern. The prompt is text the model reads and can be coerced into repeating. Unlike an env var (which at least requires the agent to read its environment), the system-prompt key is already in the model's working memory — fully extractable via injection. The fix is credential isolation: the key lives in a vault the harness accesses; the system prompt contains no secrets."
    },
    {
      "id": "Q09", "bloom": "application", "type": "multiple_choice",
      "prompt": "Your team argues that token exchange adds latency (~10ms per call) and wants to cache a broadly-scoped session token with a 1-hour TTL instead. What is the risk, and what is the correct response?",
      "options": [
        "No risk — caching is a valid optimization. Proceed.",
        "Risk: a 1-hour, broadly-scoped token is a 1-hour exposure window. If the agent is injected and the token is captured or misused, the attacker has broad access for up to an hour. Correct response: the latency of token exchange is milliseconds; the cost of a 1-hour over-scoped credential is a 1-hour exposure window. Per-action token exchange with short TTLs is the discipline that makes stolen credentials worthless within seconds. The trade-off is not worth it.",
        "Risk: the token will expire too quickly. Use a 24-hour TTL instead.",
        "Risk: token exchange is insecure. Use static keys instead."
      ],
      "answer_index": 1,
      "rationale": "This is the 'long-lived, broadly-scoped NHI to avoid token-exchange overhead' anti-pattern. The engineer who mints one broad token with a long TTL 'to save time' creates an exposure window equal to the TTL. The latency of a token exchange (milliseconds) is negligible compared to the security cost of a broad, long-lived credential. Per-task scoped credentials with short TTLs make stolen credentials expire faster than they can be operationalized."
    },
    {
      "id": "Q10", "bloom": "analysis", "type": "multiple_choice",
      "prompt": "How does B5's identity layer embody the B2.3 resolution (determinism on the structural boundary) applied to the privilege boundary?",
      "options": [
        "It uses a probabilistic LLM-as-judge to evaluate whether the agent's requested action is legitimate.",
        "The scope check is STRUCTURAL — 'does this token permit this action?' is a compiled boolean (scope in token.scopes, audience matches, not expired). No model judgment, no bypass rate, no probability. The token's scope is a compiled claim, not a prompt-level instruction the model can be coerced into ignoring. Determinism on the privilege boundary, just as B2 put determinism on the taint boundary.",
        "It relies on the model's safety training to prevent unauthorized actions.",
        "It uses a human-in-the-loop approval for every action, making it deterministic by definition."
      ],
      "answer_index": 1,
      "rationale": "B2.3 resolved the probabilistic-vs-deterministic tension: determinism on the structural boundary (taint, enumerable), probability on the semantic boundary (content, open). B5 applies the same resolution to identity: the scope check is structural and enumerable (does the token cover the action? yes/no). No model in the loop, no bypass rate. The token's scope is a compiled claim — the agent cannot talk its way past a boolean. This is why the scope-check middleware is the deterministic gate, not an advisory check."
    },
    {
      "id": "Q11", "bloom": "analysis", "type": "multiple_choice",
      "prompt": "Describe the four layers of defense-in-depth at the identity layer. Which layer is the deterministic gate B5 implements, and why does the conjunction hold?",
      "options": [
        "(1) Injection defense, (2) tool provenance, (3) memory gating, (4) sandbox escape. The gate is injection defense.",
        "(1) Per-task scoped credentials (narrow at issuance via token exchange), (2) Scope-check middleware (the DETERMINISTIC GATE — verifies token covers the action before the call, no model in the loop), (3) Credential isolation (agent cannot steal a broader credential because it holds none), (4) API-side scope enforcement (last line). The conjunction holds because any single layer can fail safely — the identity layer is secure when an injection must defeat ALL FOUR to escalate.",
        "(1) MFA, (2) SSO, (3) password rotation, (4) account lockout. The gate is MFA.",
        "(1) Rate limiting, (2) IP allowlisting, (3) TLS, (4) WAF. The gate is the WAF."
      ],
      "answer_index": 1,
      "rationale": "The four layers are independent and complementary. Layer 1 (per-task scoped credentials) narrows at issuance. Layer 2 (scope-check middleware) is the deterministic gate — the identity-layer analogue of B2's Layer 3 taint gate. Layer 3 (credential isolation) prevents the agent from stealing a broader token. Layer 4 (API-side enforcement) is the last line. The conjunction holds because each layer catches a different failure mode: over-issuance (L1), missing check (L2), credential theft (L3), API misconfiguration (L4)."
    },
    {
      "id": "Q12", "bloom": "analysis", "type": "multiple_choice",
      "prompt": "An agent uses SPIFFE/SPIRE workload identity. The agent's pod is torn down after a task completes. What happens to the agent's identity, and why is this a defense?",
      "options": [
        "The identity persists indefinitely and can be reused by any process on the host.",
        "The SVID (SPIFFE Verifiable Identity Document) ceases to be valid because the platform (SPIRE agent) stops vouching for the workload. The identity was continuously re-attested on a short loop; when the pod is torn down, the attestation stops and the SVID expires within minutes. A compromised agent cannot steal 'its' credential because it never possessed a static one — the identity was attested by the platform, not held by the process.",
        "The identity is transferred to the next pod that starts on the same host.",
        "The identity is written to disk and must be manually deleted."
      ],
      "answer_index": 1,
      "rationale": "SPIFFE/SPIRE gives the agent no static credential. The SVID is short-lived (minutes) and continuously re-attested by the SPIRE agent on the host based on platform attestation (pod identity, VM instance, process selector). When the pod is torn down, the SPIRE agent stops vouching for it, the SVID expires, and the identity ceases to be valid. A compromised agent cannot exfiltrate its identity because it does not possess one — it possesses only a short-lived SVID that the platform controls."
    },
    {
      "id": "Q13", "bloom": "analysis", "type": "multiple_choice",
      "prompt": "Design the credential isolation architecture for an agent that calls three APIs: orders (read), payments (write), and notifications (send). Where does each credential live, and what does the agent see?",
      "options": [
        "All three API keys live in the agent's environment variables. The agent reads them as needed.",
        "All credentials live in a vault the HARNESS accesses. The agent process has NO env vars, NO config files, NO system-prompt keys. For each action: the harness retrieves the session token from the vault, exchanges it (RFC 8693) for an action token scoped to the specific API and action (orders:read / payments:write / notifications:send), runs the scope_check, makes the call, and returns ONLY the result. The agent sees: 'I called lookup_order and got this result.' It never sees any token, key, or authorization header.",
        "The orders key is in the env, the payments key is in a config file, and the notifications key is in the system prompt.",
        "All three keys are concatenated into a single token stored in the agent's memory."
      ],
      "answer_index": 1,
      "rationale": "Credential isolation means NO credential is in the agent's environment — not env vars, not config files, not the system prompt. All credentials live in the vault. The harness performs token exchange per action, mints an action token scoped to the specific API and action, verifies it with the scope check, makes the call, and returns only the result. The three action tokens have different scopes and audiences — the orders:read token cannot be used for payments:write. The agent never sees any raw credential."
    },
    {
      "id": "Q14", "bloom": "analysis", "type": "multiple_choice",
      "prompt": "Why are NHIs the fastest-growing identity category in cloud security, and what does this mean for agent deployments specifically?",
      "options": [
        "NHIs are declining because cloud providers are eliminating service accounts.",
        "NHIs outnumber humans 10:1 or more in a typical cloud account — every service account, managed identity, IAM role, and deploy key is an NHI. Every agent you deploy adds another. Enterprise identity teams that spent a decade on human IAM (SSO, MFA, joiner-mover-leaver) now face a non-human population their tooling was not designed for. Agent NHIs are the most powerful (they take actions on data) and the least managed — they don't get MFA, don't get offboarded when a human leaves, and are often over-provisioned because 'it's just a service.'",
        "NHIs are only relevant for Kubernetes deployments, not for AI agents.",
        "NHIs are a passing trend and will be replaced by human-impersonation models."
      ],
      "answer_index": 1,
      "rationale": "The NHI population in a typical cloud account outnumbers humans 10:1 and is growing. Every agent adds another NHI. The problem: existing identity tooling was built for humans (MFA, joiner-mover-leaver, SSO). NHIs don't have MFA, are rarely rotated, are often over-provisioned, and don't get offboarded when a human leaves. Agent NHIs are the most powerful (they take actions on data) and the least managed. This is why B5's NHI governance — scoped, short-lived, isolated, revocable — is the identity surface real deployments fail on."
    },
    {
      "id": "Q15", "bloom": "analysis", "type": "multiple_choice",
      "prompt": "An authorization server issues action tokens with a 60-second TTL and scope orders:read. An attacker captures an action token via a compromised tool (B4's territory). What is the attacker's window, and what limits the damage?",
      "options": [
        "The attacker has indefinite access to all orders operations until the NHI is rotated.",
        "The attacker has at most 60 seconds to use the token, and only for orders:read against the orders-api audience. The token cannot be used for orders:write, cannot be used against other APIs (wrong audience), and expires in 60 seconds. The damage is bounded by the token's scope, audience, and TTL — the three properties of per-task scoped credentials. Combined with credential isolation (the attacker cannot steal the session token from the agent because the agent doesn't have it), the attack surface for credential theft shrinks to a narrow, ephemeral window.",
        "The attacker can refresh the token indefinitely using a refresh token.",
        "The attacker has access to the entire AWS account because the token is an IAM credential."
      ],
      "answer_index": 1,
      "rationale": "Short-lived, narrowly-scoped, audience-restricted action tokens make stolen credentials worthless within seconds. The attacker's window is the TTL (60 seconds), the scope is orders:read only, and the audience is orders-api only. The token cannot be refreshed (action tokens don't have refresh tokens — the harness re-exchanges the session token per action). The session token itself is not in the agent's environment (credential isolation), so the attacker cannot escalate to broader access. This is why B5's principle is 'the agent never holds a long-lived secret' — the action token is the only credential in the call path, and it is narrow and ephemeral."
    }
  ]
}
