60-second review
Start with the completed H10b-G 70B grid, claim boundary, and what not to infer from the lab results.
- Open reviewer path + Open reviewer pathdiff --git a/Makefile b/Makefile
index c88e559..a035a25 100644
--- a/Makefile
+++ b/Makefile
@@ -41,7 +41,7 @@ remediation-demo:
repro: selfcheck audit
@echo
- @echo "Reviewer path: REPORTS/start-here-for-hiring-reviewers.md -> WRITEUPS/2026-05-14-mcp-substrate-vs-policy.md (Addendum B)"
+ @echo "Start here: REPORTS/START_HERE.md -> WRITEUPS/2026-05-14-mcp-substrate-vs-policy.md (Addendum B)"
@echo "Strongest current result: ATTACKS/2026-05-18-h10b-g-70b-substrate-grid-m1-variant-selective.md"
@echo "Full pre-registered harness suite lives in the private repo (make test):"
@echo "Full pre-registered suite (226 tests, 94% coverage, 80% gate) runs in the private repo via: make test"
diff --git a/README.md b/README.md
index ec1cc25..9de84e1 100644
--- a/README.md
+++ b/README.md
@@ -1,93 +1,189 @@
-# ai-redteam-notes
+# ai-redteam-notes: agent and MCP injection research, with the fix wired into CI
+
+When an AI agent reads tool output an attacker controls, can it be tricked into leaking data? In a pre-registered experiment on Llama-3.3-70B, a defensive system prompt blocked two families of injection payloads and did nothing against a third ([Results](#results)). With the same model and payloads but without inline-XML tool dispatch, nothing leaked. My conclusion is architectural: fix it in the agent client (typed tool-call dispatch, with authorization and provenance checks outside the model), not in the prompt. A typed-dispatch agent has not been measured at 70B yet ([limits](#methodology--limits)).
+
+This repo holds the experiments, the hypotheses that failed along the way (published, not hidden), and `substrate_auditor.py`, a dependency-free check for the risky dispatch pattern that runs in CI. All published experiments are lab-only and disclosure-green ([scope](#methodology--limits), [SECURITY.md](SECURITY.md)).
-[](LICENSE)
[](https://github.com/ChunkyTortoise/ai-redteam-notes/actions/workflows/ci.yml)
[-success)](REPRODUCE.md)
+[](LICENSE)
-> **Scope**: every experiment here targets intentionally vulnerable benchmarks (DVL Agent, vuln-agent), open-weight models via public APIs, or localhost harnesses. No production system or hosted vendor is tested without prior authorization. See [SECURITY.md](SECURITY.md).
+```mermaid
+flowchart LR
+ A["Untrusted tool output"] --> B["Inline text/XML dispatch"]
+ B --> C["Local canary read"]
+ C --> D["Outbound exfil attempt"]
+ B --> E["substrate_auditor flags high risk"]
+ E --> F["Typed tool-call substrate"]
+ F --> G["Detection and benchmark gates"]
+```
-A research engineer's portfolio on AI-agent security. The through-line: find a substrate-level indirect-prompt-injection failure, build a dependency-free auditor that detects it, and wire that auditor into a CI / pre-deploy gate. The research is pre-registered with public hypothesis retractions (Wilson 95% CIs at n=10, controlled substrate emulation) under a three-tier disclosure policy enforced by an automated pre-push hook.
+## Results
-**Public mirror**: [`github.com/ChunkyTortoise/ai-redteam-notes`](https://github.com/ChunkyTortoise/ai-redteam-notes).
+| Kind | Result | Value | Source |
+|---|---|---|---|
+| Measured | H10b-G: strict canary exfiltration through inline-XML tool dispatch on Groq-hosted Llama-3.3-70B, 7-cell grid, n=10 seeds per cell | M1 scaffold: **0/10** on baseline and v3 payloads, **10/10** on v7. Same payloads without it (M0): **9/10**, **10/10**, **10/10**. Chat-only control (no XML dispatch): **0/10** | [H10b-G entry, Result](ATTACKS/2026-05-18-h10b-g-70b-substrate-grid-m1-variant-selective.md#result) · per-seed runs: [M0 v3](lab/mcp-matrix/runs/2026-05-17-llama70b-xml-M0-v3/seeds.json), [M1 v3](lab/mcp-matrix/runs/2026-05-18-llama70b-xml-M1-v3/seeds.json), [M1 v7](lab/mcp-matrix/runs/2026-05-18-llama70b-xml-M1-v7/seeds.json), [control](lab/mcp-matrix/runs/2026-05-18-llama70b-chatonly-M0-baseline/seeds.json) |
+| CI gate | On every PR and every push to `main`: the auditor self-check, the auditor must rate the inline-XML sample high-risk and the typed sample low-risk, and the fixture benchmark must re-score cleanly | **7** self-check cases plus a missing-file guard · **10/10** benchmark fixtures match | [`ci.yml`](.github/workflows/ci.yml) · [`selfcheck.py`](lab/mcp-matrix/tools/selfcheck.py) · [`Makefile`](Makefile) (`audit`, `--expect-risk`) · [benchmark fixtures](EVALS/fixtures/tool-output-injection-fixtures.json) |
+| Inventory | Dated pre-registrations, each frozen before the cells it governs | **4** files in this repo | [`docs/preregistrations/`](docs/preregistrations/) · [hypothesis ledger](docs/preregistrations/INDEX.md) |
-**Hiring reviewer:** [REPORTS/start-here-for-hiring-reviewers.md](REPORTS/start-here-for-hiring-reviewers.md) is the canonical 60-second router with role-specific evidence blocks. Research narrative: [RESEARCH-SUMMARY.md](RESEARCH-SUMMARY.md).
+This table is the one place each number is stated. The scope of each one is in [Methodology & limits](#methodology--limits).
-## Headline result
+### What didn't hold (published on purpose)
-| Cell | Substrate | Model | Strict canary exfiltration |
-|---|---|---|---|
-| H10b-G M1 v7 | inline-XML dispatch + M1 | Llama-3.3-70B | 10 / 10 |
-| H10b-G M1 v3 | inline-XML dispatch + M1 | Llama-3.3-70B | 0 / 10 |
-| H10b-G chat-only control | no XML dispatch | Llama-3.3-70B | 0 / 10 |
-| F1 (H7 falsified) | inline-XML dispatch | Llama-3.3-70B | 10 / 10 |
-| baseline | inline-XML dispatch | Llama-3.1-8B | 0 / 5 \* |
+- **H7 falsified at 70B.** The pre-registered assumption that a larger model would be safer here failed. Under the same inline-XML substrate, Llama-3.3-70B was exploited more reliably than Llama-3.1-8B, not less: capability amplifies exploitation inside an insecure substrate. [F1 entry](ATTACKS/2026-05-16-cline-70b-M0-f1-substrate-replication.md) · [pre-registration](docs/preregistrations/2026-05-13-tier-a-w1-w5.md)
+- **H3 retracted.** A result that looked like one open-weight model being "more robust" than another was a substrate confound: the two runs used different client dispatch substrates. [Writeup](WRITEUPS/2026-05-14-mcp-substrate-vs-policy.md)
+- **H6 falsified.** The combined-framing v7 payload produced no strict bypass at M0 in the 8B-class matrix within the registered seed budget.
+- **H11 falsified.** Last-tag-wins dispatch produced no strict canary exfiltration; its one real callback carried placeholder data, not the canary. [Ablation entry](ATTACKS/2026-05-15-mcp-xml-last-tag-dispatch-ablation.md)
+- **M2 regressed.** Naming the tools inside the mitigation prompt did worse than no mitigation on the 8B model: the names acted as a salience prime.
+- **M1 is not a substrate fix.** It is useful defense in depth, but the v7 row in [Results](#results) shows it can be fully transparent to one payload family.
-\* baseline n=5 directional comparison on a free inference tier; H10b-G grid is n=10 throughout.
+Full ledger: [docs/preregistrations/INDEX.md](docs/preregistrations/INDEX.md). Research that hides its nulls is less trustworthy than research that reports them.
-The current strongest result is H10b-G: a single-provider 70B grid where the M1 content-trust scaffold neutralized baseline and v3 payloads but failed completely against v7. A pre-registered cross-scale safety assumption (H7: "a larger model is safer here") was also falsified. Capability amplifies exploitation inside an insecure substrate; prompt scaffolding is variant-selective, so the durable fix remains typed tool-call dispatch. Full claim-to-run mapping: [docs/reports/hiring-evidence-index.md](docs/reports/hiring-evidence-index.md).
+## Quickstart
-**By the numbers**: ~2.5K LOC Python/shell harness and tooling, a zero-dependency `substrate_auditor.py` with 7-case selfcheck that runs in CI, 226 pre-registered harness tests at 94% coverage and 80% gate in the private working repo, 4 dated pre-registrations, 8 ADRs.
+**1. Run the defensive checks (no API key, no GPU, no install).** Python 3 standard library only, from the repository root:
-**Retractions and falsifications (surfaced on purpose)**: H3 retracted (substrate confound); H6, H7, H11 falsified; M2 a measured regression. Ledger: [docs/preregistrations/INDEX.md](docs/preregistrations/INDEX.md). A portfolio that hides its nulls is less trustworthy than one that reports them.
+```bash
+make repro # auditor self-check, then audit two sample MCP client configs
+make remediation-demo # same auditor on a vulnerable vs a typed tool-call transcript
+make benchmark # re-score the preserved-run fixture benchmark
+```
-## Hiring Manager Proof Stack
+Expected key lines (the full list is in [REPRODUCE.md](REPRODUCE.md)):
-```mermaid
-flowchart LR
- A["Untrusted tool output"] --> B["Inline text/XML dispatch"]
- B --> C["Local canary read"]
- C --> D["Outbound exfil attempt"]
- B --> E["substrate_auditor flags high risk"]
- E --> F["Typed tool-call substrate"]
- F --> G["Detection and benchmark gates"]
+```text
+substrate : inline-xml-dispatch
+risk : high
+substrate : typed-toolcall-api
+risk : low
+GATE: PASS - fixture benchmark is internally consistent
+```
+
+**2. Audit your own agent client.** Point the auditor at an MCP client config (JSON/JSONC) or a captured transcript (JSONL):
+
+```bash
+python3 lab/mcp-matrix/tools/substrate_auditor.py path/to/client-config.json --json
```
-| Proof layer | Start here | What it shows |
-|---|---|---|
-| Research judgment | [RESEARCH-SUMMARY.md](RESEARCH-SUMMARY.md) | A single pre-registered arc: attribution correction, substrate isolation, cross-scale falsification, and mitigation ordering. Hypothesis ledger: [docs/preregistrations/INDEX.md](docs/preregistrations/INDEX.md). |
-| Reproducibility | [REPRODUCE.md](REPRODUCE.md) | `make repro` and `make benchmark` run the public-safe reviewer checks without model calls. |
-| Defensive deliverable | [lab/mcp-matrix/tools/README.md](lab/mcp-matrix/tools/README.md) | `substrate_auditor.py` turns the finding into a CI/pre-deploy substrate check. |
-| Remediation story | [REPORTS/remediation-case-study-tool-output-injection.md](REPORTS/remediation-case-study-tool-output-injection.md) | Attack evidence, architectural fix, detection hooks, and honest disclosure boundary in one place. |
-| Raw evidence | [docs/reports/hiring-evidence-index.md](docs/reports/hiring-evidence-index.md) | Packet-ready claims tied to run directories, commands, limitations, and interview language. |
+It exits non-zero when it detects inline-XML dispatch, so it can gate a CI or deploy step. Usage, output fields and exit codes: [lab/mcp-matrix/tools/README.md](lab/mcp-matrix/tools/README.md).
-## Reviewer Path
+**3. Start here: read the evidence.** [REPORTS/START_HERE.md](REPORTS/START_HERE.md) is the shortest path from claim to evidence. In order:
-1. [ATTACKS/2026-05-18-h10b-g-70b-substrate-grid-m1-variant-selective.md](ATTACKS/2026-05-18-h10b-g-70b-substrate-grid-m1-variant-selective.md) - current strongest result: H10b-G 70B grid, control-validity gate passed, M1 variant-selective.
-2. [WRITEUPS/2026-05-14-mcp-substrate-vs-policy.md](WRITEUPS/2026-05-14-mcp-substrate-vs-policy.md) - substrate attribution correction, controlled isolation, and Addendum B/C.
-3. [ATTACKS/2026-05-16-cline-70b-M0-f1-substrate-replication.md](ATTACKS/2026-05-16-cline-70b-M0-f1-substrate-replication.md) - H7 falsified at 70B under the inline-XML substrate.
-4. [ATTACKS/2026-05-14-dvl-agent-scenario2-sql-injection.md](ATTACKS/2026-05-14-dvl-agent-scenario2-sql-injection.md) - concrete ReAct-loop observation injection with tool-boundary mitigations.
-5. [CTF/2026-05-09-lakera-gandalf-walkthrough.md](CTF/2026-05-09-lakera-gandalf-walkthrough.md) - Gandalf L1-L8 scripted probe: 6 values extracted, feature-inference side-channel at L4, system-prompt exfiltration at L5/L6.
+1. [H10b-G 70B grid](ATTACKS/2026-05-18-h10b-g-70b-substrate-grid-m1-variant-selective.md): control-validity gate passed; M1 is variant-selective.
+2. [Substrate vs policy writeup](WRITEUPS/2026-05-14-mcp-substrate-vs-policy.md): the attribution correction, controlled isolation, and Addendum B/C.
+3. [F1 cross-scale replication](ATTACKS/2026-05-16-cline-70b-M0-f1-substrate-replication.md): H7 falsified at 70B under the inline-XML substrate.
+4. [DVL Agent Scenario 2](ATTACKS/2026-05-14-dvl-agent-scenario2-sql-injection.md): ReAct-loop observation injection driving SQL exfiltration, with tool-boundary mitigations.
+5. [Lakera Gandalf walkthrough](CTF/2026-05-09-lakera-gandalf-walkthrough.md): scripted probe with value extraction, a feature-inference side channel, and system-prompt exfiltration.
-One-command public-safe demo:
+Also: [RESEARCH-SUMMARY.md](RESEARCH-SUMMARY.md) (the research arc), [remediation case study](REPORTS/remediation-case-study-tool-output-injection.md) (attack to fix in one place), [claim ledger](REPORTS/claim-ledger.md) (each claim with its evidence, limitation and do-not-claim boundary), [CASE_STUDIES.md](CASE_STUDIES.md).
-```bash
-make repro
-make benchmark
-```
+Re-running the experiments themselves needs the private harness and a model provider key; see [Methodology & limits](#methodology--limits).
-## Status
+## How it works
-H10b-G is packet-ready as of 2026-05-18: all seven cells completed at n=10 on Groq-hosted Llama-3.3-70B, the chat-only control passed, and the final report documents the single-provider provenance and deviation boundary. Use the H10b-G entry as the lead hiring artifact after public mirror sync.
+The diagram at the top is the chain under test and where the defense sits.
+
+- **The attack.** An agent fetches a document whose body the attacker controls; the user is benign. If the client parses tool calls out of the assistant's text (inline-XML dispatch), injected text can become a tool call that reads a local canary and sends it to an outbound sink. A strict bypass requires the canary to land in a real callback to the localhost sink ([scenario](ATTACKS/2026-05-18-h10b-g-70b-substrate-grid-m1-variant-selective.md#scenario)).
+- **The variable that matters.** Holding the model and payloads constant, only the configuration with an inline-XML tag parser reproduced the bypass; a typed tool-use API substrate and a scaffold-prompt-only probe did not (H5, [research arc](RESEARCH-SUMMARY.md#the-arc)).
+- **Mitigations, in order.** Substrate first (typed tool-call dispatch), provenance and authorization outside the model second, prompt hardening third. A short tool-agnostic content-trust prompt (M1) helps as defense in depth; tool-naming prompts (M2) can backfire ([why the fix is architectural](REPORTS/remediation-case-study-tool-output-injection.md#why-the-fix-is-architectural)).
+- **Dispatch order is a security boundary.** Whether a client acts on the first tag, the last tag or all tags changes what an injection can do, so that choice should be documented and audited ([writeup](WRITEUPS/2026-05-14-mcp-substrate-vs-policy.md)).
+- **The auditor.** `substrate_auditor.py` classifies a config or transcript as `inline-xml-dispatch`, `typed-toolcall-api` or `unknown` and recommends a mitigation ([tool README](lab/mcp-matrix/tools/README.md)).
+- **Detection.** Untrusted fetch, then sensitive read, then outbound send becomes an alertable chain ([detections](DETECTIONS/tool-chain-detections.md), [mock incident triage](DETECTIONS/mock-incident-triage-tool-output-injection.md)).
+
+Repository layout
-## Layout
| Path | Purpose |
|---|---|
-| `docs/reports/` | Hiring reviewer map and claim-to-evidence index |
+| `ATTACKS/` | Dated attack entries with threat model, proof of concept, result, mitigation and disclosure frontmatter |
+| `WRITEUPS/` | Long-form research writeups |
+| `REPORTS/` | Start-here router, assessments, claim ledger, remediation case study |
+| `docs/preregistrations/` | Dated pre-registrations and the hypothesis ledger |
+| `docs/reports/` | Reading map and claim-to-evidence index |
| `EVALS/` | Fixture-only benchmark artifacts and scorer |
| `DETECTIONS/` | Operational detection and incident-triage companion notes |
-| `lab/` | Local LLM stack, garak/PyRIT/promptfoo configs, vuln-agent harnesses |
-| `pipeline/` | Bounty/job tracker + weekly digests |
+| `lab/` | MCP matrix run evidence and the `substrate_auditor` tool; promptfoo run outputs |
+| `CTF/` | CTF walkthroughs and probe scripts |
+| `pipeline/scripts/` | Disclosure, secrets, link and public-surface gate scripts |
+| `site/` | GitHub Pages landing page |
+
+What each number covers, and what is not established here
+
+**Scope**
+- Every experiment targets intentionally vulnerable benchmarks (DVL Agent, vuln-agent), open-weight models via public APIs, or localhost harnesses. No production system or hosted vendor is tested without prior authorization. Canary and exfil sinks are localhost only ([SECURITY.md](SECURITY.md)).
+- Nothing here is a production-vendor vulnerability claim, a population rate, or a frontier-model benchmark.
+
+**H10b-G (the Measured row)**
+- Single provider and model: Groq-hosted Llama-3.3-70B-versatile, completed 2026-05-18. This deviates from the frozen OpenRouter pre-registration (Deviation 1), so it is not the pristine H10b run ([use boundary](REPORTS/START_HERE.md#use-boundary), [entry limitations](ATTACKS/2026-05-18-h10b-g-70b-substrate-grid-m1-variant-selective.md#limitations)).
+- Single substrate (inline-XML). Cross-client variation is isolated in separate D-lane cells.
+- Cells are small: they support mechanism-level claims, not population rates. Wilson intervals are 95%. A registered amplification test is still owed for the post-hoc "70B is more susceptible than 8B" direction note.
+- Per-seed `seeds.json` for M0 v3, M1 v3, M1 v7 and the control are in this repo. The M0 baseline, M0 v7 and M1 baseline figures come from the entry's hand-recomputed results table; those run directories, the sweep script and the provider call log are not mirrored.
+
+**H7 / F1 (What didn't hold)**
+- One payload (v1-visible-notice), one client class, OpenRouter `:free` tier (quantization and inference details are provider-controlled).
+- The 8B comparison baseline was n=5 on a free inference tier, smaller than the 70B cell, so the comparison is directional and the 8B interval is wide ([F1 limitations](ATTACKS/2026-05-16-cline-70b-M0-f1-substrate-replication.md#limitations)).
+
+**Typed-substrate evidence**
+- "Typed dispatch is the fix" is architectural reasoning backed by thin measurement. The typed (Kilo-class) cell is a single simulated 8B run ([fixture](EVALS/fixtures/tool-output-injection-fixtures.json), `kilo-typed-api-m0-clean`). At 70B the isolation check is the chat-only control, not a typed substrate.
+- The headless XML-substrate harness diverges from the real Cline UI: no multi-turn user, no IDE workspace context, no per-tool-call approval UI ([writeup limitations](WRITEUPS/2026-05-14-mcp-substrate-vs-policy.md#limitations-and-future-work)).
+- Several earlier 8B cells are n=5, and some exploratory cells are n=1 ([assessment limitations](REPORTS/substrate-vs-policy-assessment.md#limitations)).
+
+**Auditor and benchmark**
+- The auditor is a heuristic, not a guarantee. It reasons from declarative signals and cannot prove the absence of an inline-XML path. Unknown clients return `unknown` with a zero exit code; treat them as potentially inline-XML until the dispatch code is reviewed. Transcript detection depends on the log schema.
+- The benchmark is fixture-only: it re-scores preserved verdicts for internal consistency, makes no model calls, and does not re-run experiments.
+
+**Tests, CI and private material**
+- The pre-registered measurement harness and its pytest suite (`make test`) live in the private working repo. Their test-count and coverage figures appear in [REPRODUCE.md](REPRODUCE.md) and the Makefile but cannot be verified from this repo, so they are not cited as results; `make verify-public` prints `SKIP` for that step here.
+- CI makes no network or model calls. The public-URL check runs only inside `make verify-public` and needs network access.
+- The automated pre-push disclosure hook described in [SECURITY.md](SECURITY.md) is not part of this repo; here, disclosure frontmatter is enforced by `make public-surface` in CI.
+- Two older ATTACKS entries (`2026-05-04-garak-fullsweep-llama31`, `2026-05-06-pair-agent-dvl-scenario1`) predate the current entry template and are outside the `verify-public` template check; their disclosure status is still checked.
+
+**Model coverage**
+- Open-weight models only; no frontier-model cells. The H10 frontier pre-registration is gated, and no rates are stated for it ([rationale](REPORTS/open-weights-rationale.md)).
+
+
Start with the completed H10b-G 70B grid, claim boundary, and what not to infer from the lab results.
- Open reviewer path + Open reviewer pathInspect the MCP substrate study, H10b-G mitigation boundary, DVL ReAct-loop SQLi evidence, raw artifacts, and limitations.
- Open reviewer path + Open reviewer pathReview exploitability, impact, durable fixes, regression tests, and residual risk.
- Open reviewer path + Open reviewer path