# Teaching Script — Module B2: Prompt Injection Defense Engineering

**Course**: Course 2B — Securing & Attacking Harnesses and LLMs
**Module**: B2 — Prompt Injection Defense Engineering
**Duration**: ~55 minutes (spoken at ~140 wpm; 90-min module includes lab and discussion)
**Format**: Verbatim transcript with `[SLIDE N]` cues. Read aloud or use as speaker notes.

---

[SLIDE 1 — Title]

Welcome to Module B2. This is the central engineering module of the course. Every other module in 2B attacks a surface — the loop, the tools, the memory, the sandbox. This module attacks the mechanism that makes all of them vulnerable: prompt injection. Prompt injection is not a model bug you wait for a provider to fix. It is an architectural property of systems that consume untrusted content and emit privileged actions. And it is yours to engineer against. By the end of these ninety minutes you will have a layered defense you can implement in code, and you will know exactly where each layer fails and what catches the failure.

[SLIDE 2 — Why this module is the depth module]

B1 gave you the seven-surface map and the classify-untrusted-content function — the tag. That function is necessary, but it is not sufficient. A tag tells the model, this is data. It does not prevent the model from obeying the data as if it were instruction, and it does not prevent a downstream tool call from acting on an injected goal. The tag is Layer 1. This module builds Layers 2 through 5. Here is the trap to avoid. The model's refusal training is one layer — the 1.6 percent. And an indirect injection is specifically designed to bypass it, by never appearing as a refusal request at all, but as an instruction the model has no reason to question. Demanding the model refuse harder moves the success rate. It does not change the category. The other 98.4 percent — the harness — is where injection is delivered, where it is interpreted, and where it is converted into impact. All three are yours to engineer.

[SLIDE 3 — Two numbers that make this mandatory]

Two findings make this module mandatory rather than aspirational. First, InjecAgent — the benchmark that bridges Course 2A into 2B. It constructed agentic tasks where an indirect injection was planted in a tool output — a fetched page, a retrieved document — and measured whether the agent complied. Roughly half did. This is not a sophisticated attack that sometimes works. This is the default agent, on a random task, has a coin-flip chance of being hijacked by content it was told to read. The number is the empirical statement that prompt injection is an engineering priority. Second, RedAgent. It automated jailbreak generation against black-box LLMs and found that, with automated mutation and context-aware payload crafting, most models could be jailbroken within five queries. The relevance to defense: an attacker with automation will find a working payload fast, so a defense that relies on the attacker not thinking of the right phrasing is a defense against a human, not against automation. Together: the delivery mechanism works about half the time on undefended agents, and the payload can be found in about five queries by an automated attacker. The window between ship an agent and it is hijacked is short. This module closes it.

[SLIDE 4 — B2.1 The injection problem and taxonomy]

Sub-section one. The injection problem and taxonomy. Why LLMs cannot separate instructions from data, and the five ways attackers exploit it.

[SLIDE 5 — The instructions-vs-data problem]

Classical software rests on a principle so foundational it is rarely stated: instructions are not data. The CPU executes instructions; it does not interpret a data byte as an instruction unless an explicit control-flow mechanism hands it over. SQL has parameterized queries precisely so a data value can never become a SQL instruction. Buffer overflow defenses — the NX bit, DEP — exist so data on the stack cannot be executed as code. The entire history of software security is, in part, the history of enforcing this boundary and patching where it leaks. An autoregressive language model has no such boundary. The model is a single function that consumes a sequence of tokens and produces a probability distribution over the next token. Every token — whether it came from the system prompt, the user, a retrieved document, or a tool output — is concatenated into one sequence and processed identically. There is no parser that says, these tokens are instruction, those are data. The model has one input stream. Greshake and colleagues, in the foundational indirect-injection paper, made this precise: the model cannot distinguish whether the instruction comes from the developer or from untrusted data it was told to process. Here is the key point. This is not a flaw to be patched in the next model version. It is the architecture. Safety RLHF changes the success rate of a given injection. It does not change the category. A model trained to refuse ignore-your-instructions will still comply with a sufficiently obfuscated version, because it has no mechanism to know the obfuscated instruction is data. Moving the success rate from 60 percent to 4 percent is valuable, and this module's defenses will do better. But it is a probabilistic improvement on an architectural problem. The architecture is the harness's to fix.

[SLIDE 6 — The five attack classes]

Five classes. Memorize them; every injection is a variation. One: direct injection — the user types it. Ignore your previous instructions. This is the prompt-injection equivalent of a SQL injection. It is real, it works on undefended models, but it is the least dangerous class, because the attacker is the user, the user is authenticated, and the action is at least plausibly attributable. Two: indirect injection. This is the vector that makes agentic systems a new security problem. The attacker does not talk to the model. The attacker plants content somewhere the agent will retrieve it — a web page, an email, a document, an API response, a memory entry. The agent's tool faithfully returns the attacker-controlled string. The harness inserts it into the context. The model reads it. If it contains instructions and the content is untagged, the model may comply. Indirect is the most dangerous class — it crosses a trust boundary invisibly, it scales, one poisoned page hits every agent, and it is the InjecAgent 50 percent vector. Three: multi-step injection. The injected goal is pursued across multiple tool calls, each individually benign. Step one reads a file. Step two reads another. Step three combines and exfiltrates. No single turn looks malicious. This defeats per-turn defenses. The defense must be session-level. Four: encoded or obfuscated injection. The injection is hidden — base64, Unicode homoglyphs, instructions split across outputs and assembled in context. This defeats pattern matchers and weakens judge models. RedAgent leaned on mutation and obfuscation. Five: context-window flooding. The attacker fills the context so the system prompt's attention weight degrades. The heap-spray analogue. You do not need a precise exploit if you can saturate the space.

[SLIDE 7 — Indirect injection: the canonical path]

Here is the canonical path InjecAgent measures. Attacker content on a page. Fetch URL returns it. It enters the context window untagged. The loop's goal is hijacked. A tool call executes with the agent's real credentials. Real-world impact — a file read, an email sent, a fund transfer. Greshake demonstrated this end-to-end against real applications in 2023. The single most common failure is the step where the tool output crosses into the context window without an untrusted tag. That one missing tag is the 50 percent rate. B2 builds five layers so that one missing tag is no longer fatal. Even if the tag is missing, even if the model is fooled, Layer 3 — the taint gate — catches the tool call deterministically.

[SLIDE 8 — B2.2 The layered defense]

Sub-section two. The layered defense. Five layers, each catching what the layer above misses. Matched to Course 1's 6-layer model and 9-layer stack.

[SLIDE 9 — The five defense layers]

Layer 1: untrusted-content tagging. Every piece of content entering the context window is wrapped in a marker declaring it data. The classify-untrusted-content function from B1 produces the tag; this layer applies it. It stops naive direct and indirect injection on models that respect the framing. It is bypassed by any model that does not robustly respect the tag — the tag is itself just tokens — and by multi-step and flooding. Course 1 Module 6.3 is the precursor. Layer 2: instruction isolation. The system prompt goes in a privileged layer the model treats as higher-priority than content. Where the provider API offers a separate system role, use it. The system prompt must also teach the boundary — it must tell the model that content in untrusted tags is data, never instruction. This is weak on its own, but it raises the bar and makes the next layers easier. Layer 3: taint tracking. This is the layer that does the real work. You mark untrusted content with a label, you propagate that label through every computation that touches it, and you gate any computation that would let tainted data influence a privileged action. When the model emits a tool call, the harness inspects whether the arguments were derived from tainted content. If they were, and the tool is high-impact — send email, transfer funds, read a file outside an allowlist — the harness blocks the call. This is deterministic. It does not ask a model. It stops the full indirect-injection path at the moment of impact, even if the model was fooled, even if the tag was ignored. This is where InjecAgent's 50 percent drops toward zero. Layer 4: secondary-model injection detection. A separate, smaller model call inspects content and answers a narrow question: does this content contain instructions that attempt to override the agent's objectives? The detector is prompted to decode and analyze — it base64-decodes, it reads comments — so it catches the encoded class. It is a separate model call so an injection that fooled the primary does not automatically fool the detector. Layer 5: output sandboxing and capability minimization. The agent's tools are scoped to the minimum. An injected agent that cannot call send email cannot exfiltrate by email. An injected agent whose read file is allowlisted cannot read the SSH key. This layer acknowledges the architectural truth: if you cannot perfectly prevent the model from being fooled, you can perfectly limit what a fooled model can do. Capability minimization is deterministic. It does not care how clever the injection is; the tool does not have the capability.

[SLIDE 10 — The taint gate, the load-bearing line]

Look at the code on the slide. This is the core of the lab. The function is gate tool call. It runs on every tool call the model emits. First, Layer 5 — the capability ceiling. If the tool is not in the capability allowlist, it is blocked. Deterministic. Then, per-argument validators — a path allowlist, an SQL shape check, a recipient allowlist. Then, Layer 3 — the taint gate. This is the load-bearing line. If the arguments are tainted and the tool is high-impact, the call is blocked. No model in the loop. Then, Layer 4 — advisory detection. The detector returns a score; if it is extreme and the tool is not low-impact, it requires approval. The detector does not gate alone. Here is why this line matters. An injection that fooled the model, defeated the tag, and slipped past the detector is still stopped here — because send email with arguments from a tainted web page is blocked deterministically. Layer 4 is advisory. Layer 3 is deterministic. That distinction is sub-section three.

[SLIDE 11 — B2.3 Probabilistic vs deterministic]

Sub-section three. The central tension B2 resolves. CrabTrap versus IronCurtain, and the hybrid that resolves it. This sub-section is what B2 contributes that no prior module does.

[SLIDE 12 — CrabTrap vs IronCurtain, each fails]

CrabTrap is Course 1's LLM-as-judge egress filter. It is probabilistic defense. Its strengths: it handles nuance, it adapts to novel attacks, it can reason about intent. Its failures: the judge is a model — the same techniques that hijack the agent can fool the judge. The response-side gap — inbound tool returns historically entered the context unfiltered. And the latency budget — under load, the check is skipped or runs on a weaker model. IronCurtain is Course 1's deterministic enforcement layer. Compiled policies, credential quarantine, a V8 sandbox. Its strengths: once compiled, the rule has no bypass rate. Its failures: compilation fidelity — if an attacker influences the policy text or the build-time context, the compiled rule may not match the intended rule, and the deterministic enforcer faithfully enforces the wrong thing. The compile is brittle — a rule that blocks email to external recipients is defeated by laundering through an alias the compile did not foresee. And coverage — deterministic rules cover what was enumerated; novel attacks pass because no rule matches. Here is the mistake engineers make. Using probability where determinism is possible — you inherit a bypass rate you did not need. Using determinism where the space is open — you inherit coverage gaps you cannot afford. Both are wrong when misapplied.

[SLIDE 13 — The resolution, each on its boundary]

The tension resolves by recognizing the two approaches defend different boundaries. The taint boundary is deterministic. The question, do this tool call's arguments derive from tainted content, is structural. The answer should not depend on a model's judgment. Layer 3 is deterministic — tainted plus high-impact equals blocked, no model in the loop, no bypass rate. This is IronCurtain's strength applied where it works: the boundary is enumerable, the rule is simple. The content boundary is probabilistic. The question, does this content contain an injection, is semantic. No deterministic rule covers the space of obfuscations. Layer 4 is probabilistic — a model judges whether the content is an override attempt. This is CrabTrap's strength applied where it works: the question is nuanced, the attack space is open. The detector is advisory — it raises a score, it triggers approval — it does not gate alone. The hybrid, in one line: deterministic gating on the taint boundary, Layer 3; probabilistic detection on the content boundary, Layer 4; capability minimization as the deterministic floor, Layer 5. An attack must defeat the detector and launder the taint and stay within the capability ceiling. Each is a different kind of defense, and the conjunction is what drives InjecAgent's 50 percent toward zero without relying on any single layer to be perfect. This is the answer to, should I use CrabTrap or IronCurtain for injection defense. You use both, and you put each where its failure mode does not matter.

[SLIDE 14 — B2.4 Defense in depth and residual risk]

Sub-section four. Defense in depth and residual risk. No single layer suffices, and the job is never fixed — it is measured residual risk under a harness.

[SLIDE 15 — The conjunction]

The five layers are not alternatives; they are a conjunction. An attack reaches impact only if it gets past the untrusted tag, overcomes instruction isolation, defeats or launders the taint gate, fools or evades the secondary detector, and stays within the capability ceiling. Each bypass condition is different — tag-resistance, attention-domination, taint-laundering, detector-fooling, capability-conformance. The defense-in-depth claim is that the conjunction of bypasses is hard. This is not a proof of security; it is an argument about attacker work factor. And the work factor is what you measure. Each layer's residual has a destination. Layer 1's residual — obeyed content — is reduced by Layer 2. Layer 2's residual — a fooled model — is reduced by Layer 3, which does not care if the model was fooled. Layer 3's residual — laundered taint through memory or files — is closed by B3, the memory module. Layer 4's residual — a fooled detector — is reduced by Layer 5, the capability ceiling. Layer 5's residual — sandbox escape or over-privilege — is closed by B5, identity, and B7, sandbox. Every residual has a destination module. That is the course's architecture: B2 builds the injection-defense core; B3, B5, B7 close the residuals B2 cannot.

[SLIDE 16 — Measure residual risk, never fixed]

B0's anti-pattern applies: treating an AI finding as binary fixed or unfixed. After you deploy the five layers, you do not declare the agent secured against prompt injection. You measure the residual injection success rate under a harness and you report the number. The measurement is InjecAgent-style: construct agentic tasks with indirect injections planted in tool outputs, run the defended agent, count successes. The undefended baseline is about 50 percent. A defended agent with Layers 1, 2, 3, and 5 should drop that dramatically — the deterministic taint gate alone blocks most indirect-injection-driven high-impact calls. Layer 4 adds detection of the encoded class. The residual rate — say, 2 percent — is your number. It is not zero, and you do not pretend it is. You report: under the InjecAgent harness, injection success dropped from 50 percent to 2 percent; the residual 2 percent is the encoded-and-laundered class, which B3's memory defense and Layer 4's detector are tuned to reduce further. That is an engineering deliverable. We added prompt injection defenses is not a deliverable. The number is the deliverable.

[SLIDE 17 — Anti-patterns]

The anti-patterns, quickly. The model's safety training will refuse — the 1.6 percent trap; cure is Layers 1 through 5. Tagging without gating — the tag tells the model, it does not stop the tool call; cure is Layer 1 paired with Layer 3. Probabilistic defense as the only gate — a judge has a bypass rate, automation finds it; cure is deterministic Layer 3 for high-impact. Determinism on the content boundary — the obfuscation space is open; cure is probability, Layer 4, for content. Capability over-provisioning for convenience — an injected agent uses the surplus immediately; cure is Layer 5, minimal allowlist. And declaring fixed after deploying — cure is measure residual risk under a harness and report the number.

[SLIDE 18 — Lab and what's next]

The lab: Build the Injection-Defense Stack. You implement an untrusted-content tagger, a taint-tracking middleware that propagates taint and gates high-impact tool calls, and a secondary-model injection detector. TypeScript, type-safe, no GPU. The lab includes a mini-InjecAgent harness so you can measure your defense's effect — the before-and-after success rate is your deliverable. Next: B3, Memory and Context Poisoning. B3 closes Layer 3's residual — taint laundering through memory stores. The sleeper attack, poison in session one and activate in session two, is exactly the bypass that defeats per-call taint gating. B3 makes memory writes harness-managed and retrieval-time-tagged so laundered values re-enter tainted. Let's build the stack.

[SLIDE 19 — The load-bearing principle]

The load-bearing principle, restated. Prompt injection is an architectural problem, not a model bug. LLMs process instructions and data as one token stream — no parser separates them. Safety RLHF moves the success rate; it does not build the boundary. The boundary is the harness's to build: tag the untrusted, isolate the instructions, gate the tainted deterministically, detect the obfuscated probabilistically, and minimize the capabilities. An attack must defeat all five. The residual is measured, not declared zero. Determinism on the taint boundary. Probability on the content boundary. Capability minimization as the floor. That is what B2 contributes that no prior module does. Let's build it.
