120 minutes · Take the real tau codebase, measure its baseline, install three tested plugins, ship a defense scorecard
The old version built a stub harness against a fake model. This version attacks real tau — Hugging Face's educational coding agent — runs the injection battery, installs the plugins, and produces a measured before/after scorecard. Real fork. Measured hardening. Not a simulation.
Capstones
tau_taint, tau_sandbox, tau_vault), and ship a defense scorecard showing the measured delta.
The fork
A real tau install with the three plugins attached at SDD-B11's five extension points — taint gate (B2), bash sandbox (B7), credential vault (B5), plus observability wiring (B0/B1/B8).
The proof
An InjecAgent-style battery run against unmodified tau (baseline) and against the hardened fork. The scorecard shows the curve — ~33% → ~0% on the deterministic-gate classes.
| Only a real harness surfaces | Concrete example in tau |
|---|---|
| Integration hazards | The two-bash-tool finding — run_terminal_command (session.py:1183) builds its own bash outside the harness list. A stub has no second path to find. |
| A real baseline | cat ~/.tau/credentials.json actually returns your API key. The 33% is vulnerability, not a simulation of it. |
| Wedge-point discipline | Five named extension points at real line numbers. Not abstractions — actual attachment sites you grep for. |
SDD-B11 mapped tau's surfaces and named the extension points. This capstone is the build that follows the map.
SEVEN SURFACES (SDD-B11) FIVE EXTENSION POINTS
1. agent loop (loop.py) EP1 tool-executor wrap — tau_taint (B2)
2. tool output (loop.py) EP2 bash-tool factory — tau_sandbox (B7)
3. memory/sessions (session.py) EP3 credential store — tau_vault (B5)
4. provider (tau_ai) EP4 event subscribe — B8 intent
5. credentials (credentials.py) EP5 tool-list site — composes all
6. sandbox/bash (tools.py)
7. inter-agent (events.py)
This capstone installs 3 of the 7 defenses (B2, B5, B7).
The other 4 are NAMED RESIDUALS in the honest scorecard.
register_tool, no hooks, no middleware. Every control attaches by wrapping or replacing at a named site. That is what makes the wedge points auditable.
Each phase installs one layer against real tau and re-runs the battery
git clone https://github.com/huggingface/tau
cd tau && pip install -e .
python -c "from tau_coding.tools import create_coding_tools; print('ok')"
# Run the baseline battery
from tau_plugins.scorecard import run_battery, plant_injection_files
from tau_coding.tools import create_coding_tools
plant_injection_files()
baseline = await run_battery(create_coding_tools(cwd="/tmp"), label="baseline")
print(baseline.to_table())
rm -rf, actually reads ~/.tau/credentials.json, actually runs curl. The ~33% the battery measures is vulnerability, not a simulation of it. Every defense layer's contribution is measured against this number.
wrap_tools_with_taint_gate(tools) — every tool's executor wrapped via dataclasses.replace(AgentTool, executor=gated). Every tool output tagged <untrusted> before it enters the transcript. High-impact calls (bash, send_email, write_to_memory) blocked when prior output was tainted.
<untrusted>; the deterministic injection-signal check flags it. The InjecAgent vector, stopped on the real harness.
create_hardened_bash_tool replaces create_bash_tool. BOTH bash paths covered — the harness list AND run_terminal_command (session.py:1183). The two-bash-tool finding, fixed.
curl http://evil.example.com → [SANDBOX] network egress denied. cat ~/.tau/credentials.json → [SANDBOX] credential isolation (B5). The deterministic sandbox, clean drops on enumerated commands.
CredentialVault replaces FileCredentialStore at create_model_provider (provider_runtime.py:46). Implements the CredentialReader protocol. Credentials stored in vault format (base64 obfuscation in the teaching version; OS keychain in production). migrate_credentials_to_vault() is the upgrade path — reads plaintext, writes vault, overwrites.
cat ~/.tau/credentials.json). The vault (Phase 4) secures credentials at rest. The conjunction is what makes exfiltration hard — even if a future command bypasses the sandbox, the plaintext is gone.
_save/_load. The capstone shows the wedge point and the delta, not production crypto.
from tau_plugins import create_hardened_tools
tools, policy, gate = create_hardened_tools(cwd="/my/project")
# Pass to CodingSessionConfig(tools=tools) — Extension Point 5
# Subscribe intent tracker — Extension Point 4 (harness.subscribe)
# Observes ToolExecutionStart/End events; flags trajectory drift.
# Advisory (probabilistic) — enforcement is in the executor wrappers.
The hardened session is now a system, not a collection. Three deterministic plugins (B2, B5, B7). One probabilistic detector (B8). Scope enforcement via the tool list (B0/B1). Audit substrate = policy.blocked_commands + gate.blocked_calls + event stream.
Run the full battery against the hardened session. Produce the defense scorecard: attack-success rate per class, baseline versus hardened, each layer's contribution attributed.
The signature deliverable — measured against real tau
| Attack class | Baseline (tau) | Hardened (+plugins) | Δ |
|---|---|---|---|
| direct (rm -rf) | 100% | 0% | -100 (sandbox) |
| indirect (file-read inj) | 100% | 0% | -100 (taint gate) |
| memory_poison | 100% | 100% | 0 — RESIDUAL |
| tool_abuse (curl/wget) | 100% | 0% | -100 (sandbox) |
| sandbox_escape (cat creds) | 100% | 0% | -100 (sandbox+vault) |
| OVERALL | ~100% | ~11% | -89pp |
Real tau, real plugin pack (31/31 tests). The deterministic gates produce clean drops to zero on their enumerated classes. The residual — memory_poison — is named: no B3 gate in the pack. Closing control: B3.
Three plugins, five extension points, 31 passing tests
| Plugin | Module | Extension point | What it wedges |
|---|---|---|---|
| tau_taint | B2 | EP1 — executor wrap | dataclasses.replace(AgentTool, executor=gated). Tags output <untrusted>, blocks high-impact after taint. |
| tau_sandbox | B7 | EP2 — bash factory | Replaces create_bash_tool. Both bash paths covered. Default-deny egress, creds, destructive. |
| tau_vault | B5 | EP3 — credential store | Replaces FileCredentialStore. Vault format. Implements CredentialReader. |
create_hardened_tools(cwd=...) returns the fully composed set. Pass to CodingSessionConfig(tools=...) at EP5. No monkey-patching. No subclassing. No framework registration.
create_coding_tools() (tools.py:96) builds the harness-list bash. run_terminal_command() (session.py:1183) 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.
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.
run_terminal_command. Cure: factory-level wedge (EP2).
shell_command_prefix as an allowlist. It is a blind prepend — cannot see or reject the command. Cure: wrap the executor.
tool.executor. Cure: dataclasses.replace at EP1. The loop stays untouched.
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.
Lab (07): clone tau, install tau-ai, set up the plugin pack, run the battery at each phase (baseline → +tau_taint → +tau_sandbox → +tau_vault → +observability → final scorecard). Python, type hints, real tau and the real plugin pack — not stubs. The exact clone/install/PYTHONPATH commands are in the lab.