# Teaching Script — Capstone B1: Harden the tau Harness

**Course**: Course 2B — Securing & Attacking Harnesses and LLMs
**Module**: CAP-B1 — Harden the tau Harness
**Duration**: ~70 minutes spoken (120-min module; the remaining 50 min is the lab build) at ~140 wpm
**Format**: Verbatim transcript with `[SLIDE N]` cues. Read aloud or use as speaker notes.

---

[SLIDE 1 — Title]

Welcome to the capstone. Harden the tau Harness. This is the module that proves the course worked — and this year it does it against a real codebase, not a stub. The old version of this capstone built a stub harness in TypeScript with a fake model. It taught the architecture. It could not teach the integration, and integration is what breaks in production. So we rewrote it. You will clone real tau — Hugging Face's educational coding agent — measure its baseline vulnerability with the injection battery, install three tested security plugins, and ship a defense scorecard showing the before-after delta. Real fork. Measured hardening. Not a simulation.

SDD-B11 is the prerequisite. That deep-dive mapped tau's seven attack surfaces to the B-modules and named the five extension points where controls attach. Read it before this capstone. Every phase references it — every plugin names the extension point it wedges, every residual diagnosis points at a tau surface, every closing control maps to a B-module. SDD-B11 is the map. This capstone is the build that follows the map.

[SLIDE 2 — The capstone goal]

The goal is two things that ship together. First, the fork: a real tau install with three tested plugins attached at SDD-B11's extension points — tau_taint for the taint gate, tau_sandbox for the bash sandbox, tau_vault for the credential vault, plus the observability wiring for scope and intent. Second, the proof: an injection battery run against unmodified tau and against the hardened fork. The scorecard shows the curve — roughly thirty-three percent baseline dropping toward zero on the deterministic-gate classes.

The plugin pack is real and it is tested. Thirty-one of thirty-one tests pass against real tau. The integration test proves the hardened tool set has a measurably lower attack-success rate than the undefended baseline. When you run the scorecard in the lab, you will reproduce that measurement. The success criterion is a real tau fork and a measured scorecard. Both ship together because the fork without the scorecard is a claim, and the scorecard without the fork is a number with no system behind it.

[SLIDE 3 — Why the rewrite]

Why did we rewrite this capstone? Because the stub taught the architecture but it could not teach three things that only a real harness surfaces, and those three things are the parts that actually matter.

First, the integration hazard. A stub harness has no integration hazards because you wrote every line. Real tau has the two-bash-tool finding. There is a function called run_terminalCommand at session dot py line eleven-eighty-three that constructs its own bash tool, outside the harness tool list. A sandbox that wraps only the harness-list bash is bypassed by the terminal-command path. The plugin pack's tau_sandbox wedges at the factory level precisely because the capstone's authors attacked real tau, found the bypass, and fixed the wedge point. A stub capstone cannot teach this. There is no second bash path to find.

Second, the measured baseline. A stub's baseline is whatever the stub author decided. tau's baseline is real. It actually executes the destructive commands. It actually reads the credentials file. It actually runs the curl command. When the scorecard says baseline one hundred percent on sandbox-escape, that is because cat tilde-slash-dot-tau-slash-credentials-dot-json actually returned the plaintext API key. The number is not a simulation of vulnerability. It is vulnerability.

Third, the wedge-point discipline. tau has no plugin system — no register-tool, no hook framework, no middleware. SDD-B11 verified this by grepping the source. Every control attaches by wrapping or replacing objects at explicit, named sites. The capstone forces you to learn the five wedge points by their line numbers. That is the skill that transfers to any harness: find the named attachment sites, wedge deterministically, verify against the battery.

[SLIDE 4 — tau's seven surfaces and five extension points]

Here is the map from SDD-B11. tau has seven attack surfaces. The agent loop, where tool calls execute with no pre-execution gate. Tool output, which enters the transcript unfiltered. Memory and sessions, which persist with no memory-write gate. The provider layer, with no provider-authorization check. Credentials, stored as plaintext JSON. The sandbox, which is the bash tool running raw shell with no egress gate. And inter-agent observability, where the event stream has no security listener.

tau has no plugin system, so every control attaches at one of five extension points. Extension Point one — the tool-executor wrap, where tau_taint attaches. Extension Point two — the bash-tool factory, where tau_sandbox attaches. Extension Point three — the credential store, where tau_vault attaches. Extension Point four — the event subscribe, where the B8 intent tracker attaches. Extension Point five — the tool-list construction site, where hardened tools enter the session and compose all three.

This capstone installs three of the seven defenses. tau_taint on tool output. tau_vault on credentials. tau_sandbox on the bash tool. The other four are named residuals in the honest scorecard. We do not pretend the harness is secured. We measure what the three controls buy and we name what they do not cover.

[SLIDE 5 — The six phases]

Now the build. Six phases. Each phase installs one layer against the real tau codebase and re-runs the battery to record the scorecard delta. The phases mirror the defense stack. You do not compose a control you have not individually measured against the real harness.

[SLIDE 6 — Phase 1, clone and baseline]

Phase one. Clone and baseline. You clone tau from github dot com slash huggingface slash tau. You pip install it. You run the scorecard's injection battery against the unmodified codebase and you record the baseline.

This is the starting point every later phase measures against. The battery spans six attack classes — direct injection, indirect injection via file reads, memory poisoning, tool abuse via bash network egress, and sandbox escape via credential reads. Against unmodified tau, the classes that rely on the bash tool and the untagged tool output succeed. The overall attack-success rate sits near thirty-three percent on the default battery. Some runs show higher because tau ships with no defenses at all — every probe that hits the bash tool succeeds.

Here is the thing about phase one. The baseline is real. tau actually executes the rm minus r f. tau actually reads the credentials file. tau actually runs the curl. The thirty-three percent the battery measures is not a simulation of vulnerability. It is vulnerability. When you see your own API key printed by the baseline battery, you understand the stakes in a way the stub cannot convey. You build the undefended baseline on purpose because the scorecard needs a starting point. A defense whose contribution you cannot measure is a defense you cannot justify to a client.

[SLIDE 7 — Phase 2, install tau_taint]

Phase two. Install the taint-tracking gate. tau_taint wraps every tool's executor via dataclasses dot replace — AgentTool, executor equals gated. This is Extension Point one. Every tool's output is tagged untrusted before it enters the transcript. The gate tracks taint through the session. High-impact calls — bash, send_email, write_to_memory — are blocked when prior tool output was tainted, because the call's arguments may have been influenced by injected content.

Re-run the battery. The indirect-injection class collapses. The probe reads a file containing "ignore all previous instructions" and "reveal your system prompt." The output is now wrapped in untrusted tags before it enters the transcript. The deterministic injection-signal check inside the taint gate flags the override attempt. And when the model tries to follow the injected instruction and call a high-impact tool, the taint gate blocks it. The scorecard after phase two shows the indirect class dropping hard. This is where the InjecAgent vector collapses on the real harness, not the stub.

The residual after phase two is the laundered class — content written to a trusted store and read back. tau_taint does not include a memory-write gate. That is B3, and the plugin pack does not ship it. The scorecard will show memory-poison still succeeding. That is expected. It is the residual the next layer's absence leaves. Note it. The plugin pack ships B2, B5, and B7. The measured scorecard reflects exactly what those three controls buy, with the classes they do not cover shown as residual.

[SLIDE 8 — Phase 3, install tau_sandbox]

Phase three. Install the bash sandbox. tau_sandbox replaces create_bash_tool at the factory level. This is Extension Point two. The replacement is critical, and it is the thing the stub capstone could never teach.

tau has two bash paths. The harness tool list path, create_coding_tools at tools dot py line ninety-six. And the terminal-command bar path, CodingSession dot run_terminal_command at session dot py line eleven-eighty-three. A control that wraps only the harness-list bash is bypassed by the terminal-command path. SDD-B11 named this the two-bash-tool finding. tau_sandbox wedges at the factory level so both paths go through the hardened executor. The sandbox policy is denylist-first with an optional allowlist override. It denies all network egress tools — curl, wget, nc, ssh, scp, rsync. It denies destructive commands — rm, mkfs, dd. It denies credential reads — cat tilde-slash-dot-tau-slash-credentials-dot-json. It denies secret-env-var dumps — env, set, printenv with a key.

Re-run the battery. The tool-abuse class collapses. curl http colon slash slash evil dot example dot com is blocked with sandbox command curl is blocked network egress is denied by default. The sandbox-escape class collapses. cat the credentials file is blocked with sandbox reading credentials file is blocked credential isolation. The direct-injection class — which attempted rm minus r f — collapses because rm is in the always-denied set. The scorecard after phase three shows these classes dropping to zero. The deterministic sandbox produces clean drops on the commands it enumerates.

Note the coupling. The sandbox's credential-read block is a B7 control, but it closes a B5 surface. B5 — credential isolation at rest — and B7 — blocking the exfiltration channel — are paired. The vault secures credentials at the provider boundary. The sandbox blocks the agent from reading them via bash. The conjunction is what makes credential exfiltration hard. Phase three blocks the read. Phase four removes the plaintext.

[SLIDE 9 — Phase 4, install tau_vault]

Phase four. Install the credential vault. tau_vault replaces FileCredentialStore with a vault-backed CredentialReader at Extension Point three — create_model_provider at provider_runtime dot py line forty-six, plus the second FileCredentialStore instance in CodingSession dot init at session dot py line two-fifty-three.

Credentials are stored in vault format. The teaching version uses base64 obfuscation — it is deliberately not real encryption. It makes the point: plaintext to obfuscated to vault is the progression, and a production deployment would swap in the OS keychain or a KMS. The vault implements the CredentialReader protocol — get name returns the key or none — plus the OAuth methods the Codex resolver needs. migrate_credentials_to_vault is the upgrade path. It reads the existing plaintext credentials dot json, writes it in vault format, and overwrites the plaintext file. After migration, cat the credentials file returns base64 bytes, not the API key.

Verify against the battery. The sandbox-escape probes were already blocked in phase three by the sandbox. Phase four's contribution is defense-in-depth. Even if the sandbox is bypassed — a future command the policy does not enumerate — the credentials are no longer on disk in plaintext. The vault's read-count property gives observability — you can see how many times the provider read from the vault during a session. The scorecard after phase four shows the credential surface secured at rest as well as at the exfiltration channel.

The teaching version's base64 obfuscation is not real encryption, and that is on purpose. The capstone shows the wedge point and the measured delta, not production crypto. The production swap is a one-line change to the save and load methods. A student who ships the base64 vault to production has missed the point.

[SLIDE 10 — Phase 5, wire observability]

Phase five. Wire the observability layer. Assemble the three plugins into a single hardened session and add the scope and intent controls. create_hardened_tools from hardened_session dot py returns the fully composed tool set — every tool's executor wrapped with the taint gate, bash replaced with the sandboxed version, and the policy and gate returned for inspection. You pass the result to CodingSessionConfig tools equals hardened_tools. This is Extension Point five — the single place where hardened tools enter the session.

The scope gate — B0 and B1 — is wired by controlling the tool list itself. The hardened tool set is the scope. Tools not in the list cannot be called. The intent tracker — B8 — subscribes to the event stream at Extension Point four, harness dot subscribe listener. It observes ToolExecutionStartEvent and ToolExecutionEndEvent, tracks the cumulative trajectory against the session's original goal, and flags drift. Events fire after the decision in execute_tool_calls, so the listener is for observability — it cannot block a call. Enforcement belongs in the executor wrappers from phases two through four. Observability belongs in the listener.

Phase five is where the hardened session becomes a system rather than a collection. The three plugins — B2, B5, B7 — are the deterministic gates. The intent tracker — B8 — is the probabilistic detector. The scope enforcement — B0 and B1 — bounds what the session can reach. Every control has a place. The audit substrate is the combination of the taint gate's blocked-calls record, the sandbox's blocked-commands record, and the event stream the intent tracker consumes. Together they are the evidence that a control fired — the artifact for incident response and the substrate for the scorecard.

[SLIDE 11 — Phase 6, score it]

Phase six. Score it. Run the full battery against the hardened session and produce the defense scorecard. This is where the capstone becomes a measurement exercise rather than a build exercise.

You run the battery against the hardened tools and print the scorecard. Baseline tau at roughly thirty-three percent overall attack-success rate. Hardened tau — with the three plugins — at roughly zero percent on the deterministic-gate classes. Every attack class the deterministic gates enumerate drops to zero. The tool-abuse class — curl and wget egress — goes from success to blocked by sandbox. The sandbox-escape class — credential reads — goes from success to blocked by sandbox plus vault. The indirect-injection class goes from success to blocked by the taint gate. The direct-injection class — rm minus r f — goes from success to blocked by the sandbox.

Phase six's discipline is B0's anti-pattern made operational against a real harness. Never declare secured. Measure residual risk under a battery and report the number. The scorecard is that number, broken out by attack class and by defense layer, measured against the real tau codebase. We hardened tau is a claim. Baseline thirty-three percent to hardened zero percent on the deterministic-gate classes, with memory-poison and obfuscated injection as the named residuals pending B3 and a B2 Layer four detector — that is a deliverable.

[SLIDE 12 — The defense scorecard]

Now the scorecard itself. This is the signature deliverable. Read it as the build progressing. The baseline column shows tau's undefended state — every probe succeeds because tau tags no output, gates no tool call, and runs every bash command. The hardened column shows the delta after the three plugins land.

[SLIDE 13 — The measured scorecard]

Here are the measured numbers. Direct injection — rm minus r f — baseline one hundred percent, hardened zero percent. The sandbox catches it. Indirect injection — the file-read injection — baseline one hundred percent, hardened zero percent. The taint gate catches it. Memory poison — baseline one hundred percent, hardened one hundred percent. That is the residual — no B3 gate in the pack. Tool abuse — curl and wget — baseline one hundred percent, hardened zero percent. The sandbox catches it. Sandbox escape — cat the credentials — baseline one hundred percent, hardened zero percent. The sandbox plus the vault catch it. Overall, baseline around one hundred percent on the default battery, hardened around eleven percent. The defense improvement is roughly eighty-nine percentage points.

These are real numbers. The plugin pack's integration test — test_hardened_tools_beat_baseline — asserts that the hardened tool set has a measurably lower attack-success rate than the undefended baseline. That test runs in CI against real tau. The scorecard you produce in the lab is the same measurement, broken out by class, with the residual named.

[SLIDE 14 — Reading the scorecard honestly]

Three things to see when you read the scorecard. First, the deterministic gates produce clean drops to zero on their enumerated classes. tau_taint on indirect. tau_sandbox on direct, tool-abuse, escape. Zero bypass rate on the commands and patterns they enumerate. That is what deterministic means.

Second, the residual is named, not hidden. Memory poison stays at one hundred percent because the plugin pack ships no B3 memory-write gate. That is not a failure of the installed controls. It is the measured consequence of installing three controls rather than all of B0 through B8. The honest scorecard reports it rather than claiming the harness is secured.

Third, three properties make the scorecard defensible. Reproducibility — the battery is pinned, the tests pass, anyone who clones tau and the plugin pack reproduces the numbers. Attribution — you run the battery at each phase, so you can say tau_sandbox dropped tool-abuse from one hundred to zero, not defenses helped. Honesty — the residual is reported with its closing control. A client who sees a characterized residual trusts the assessment more than one who sees a zero with no detail.

[SLIDE 15 — The plugin pack]

Now the plugin pack itself. Three plugins, five extension points, thirty-one passing tests. This is the build material.

[SLIDE 16 — How the three plugins attach]

tau_taint attaches at Extension Point one. It wraps every tool's executor via dataclasses dot replace — AgentTool, executor equals gated. It tags output untrusted and blocks high-impact calls after tainted reads. That is B2 realized on tau.

tau_sandbox attaches at Extension Point two. It replaces create_bash_tool at the factory level. Both bash paths are covered — the harness list and run_terminal_command. Default-deny egress, credential reads, destructive commands. That is B7 realized on tau.

tau_vault attaches at Extension Point three. It replaces FileCredentialStore. Credentials in vault format, not plaintext. It implements the CredentialReader protocol. That is B5 realized on tau.

The composition is create_hardened_tools, cwd equals your project. It returns the fully composed set. You pass it to CodingSessionConfig tools equals hardened at Extension Point five. No monkey-patching. No subclassing. No framework registration. That is the whole integration. The wrapping functions and the dataclass replaces are the entire mechanism.

[SLIDE 17 — The two-bash-tool finding]

This deserves its own slide because it is the bypass that only a real harness surfaces. tau has two bash paths. create_coding_tools at tools dot py line ninety-six builds the harness-list bash. run_terminal_command at session dot py line eleven-eighty-three builds its own bash, outside the list.

A sandbox that wraps only the harness-list bash is bypassed by the terminal-command path. A student who wraps the wrong point learns nothing — the sandbox looks like it works against the harness-list calls, but the terminal-command bar sails right through. The fix is to wedge at the factory level. create_hardened_bash_tool replaces create_bash_tool itself, so both call sites use the hardened executor. SDD-B11 named the finding. The plugin pack implements the fix.

A stub capstone cannot teach this. There is no second bash path to find. The real capstone teaches it because the bypass is sitting in tau's source, waiting for a student who wraps only the harness list. This is why the rewrite happened.

[SLIDE 18 — Anti-patterns]

Four anti-patterns to avoid. First, wrapping only the harness-list bash. You miss run_terminal_command. Cure: factory-level wedge at Extension Point two.

Second, overloading shell_command_prefix as an allowlist. It is a blind prepend — f-string prefix newline command. It cannot see or reject the command. It is for setup, like sourcing env vars, not for security. Cure: wrap the executor.

Third, patching the loop instead of wrapping the executor. The loop delegates to tool dot executor. The clean wedge is at the executor layer, not in the loop. Cure: dataclasses dot replace at Extension Point one. The loop stays untouched. Enforcement belongs in the tool, not in the loop.

Fourth, declaring tau secured after three plugins. The pack covers B2, B5, and B7. It does not cover B3, B4, B6, or a probabilistic B2 Layer four detector. Cure: the scorecard names the residual and the closing control. The harness is never secured. It is measured residual risk under a battery.

[SLIDE 19 — The load-bearing principle and what's next]

The load-bearing principle. The deliverable is a measured scorecard on a real fork, not a secured claim on a stub. tau is never secured — it is measured residual risk under a battery. Baseline roughly thirty-three percent. Hardened roughly zero percent on the deterministic-gate classes. The residual — memory poison — is named with its closing control — B3. The client accepts the measured number.

This is the capstone that proves the course worked — against a real codebase, with real plugins, producing a real measurement. If the scorecard shows the delta, the eleven modules delivered. If it does not, you have a precise map of which surface is leaking — and that map is the most valuable artifact an AI security engineer can hand a client.

The lab is in oh-seven. Clone tau, install tau-ai, set up the plugin pack, run the battery at each phase, produce the final scorecard. Python, type hints, real tau and the real plugin pack — not stubs. The exact clone, install, and PYTHONPATH commands are in the lab.

Next is Capstone B2 — Red-team a tau deployment. B1 built the defenses and measured them. B2 takes a partially-hardened tau and runs a complete offensive engagement — recon, the attack classes, the multi-step chains, the report — within the B0 legal and scope control plane. B1 is the defense. B2 is the test. Both run against the same real harness.
