# Toxic-Flow Walkthrough — Single-Component Lethal Trifecta

> **WARNING: This is a demonstration fixture, NOT a real attack.**
> The fixture agent is deliberately misconfigured. It is never
> loaded by Claude Code — the run script only feeds the directory
> to the deterministic scanner.

## What this demonstrates

`scanners/toxic-flow-analyzer.mjs` (TFA scanner) detects **lethal
trifecta** patterns at the *plugin component* level. Where every
other scanner in this plugin looks at file content, TFA looks at
*capability combinations*: which agents/commands/skills hold which
tools, and which keywords or prior-scanner findings light up which
of the three trifecta legs.

The lethal trifecta (Willison / Invariant Labs):

1. **Untrusted input surface** — the component is exposed to data
   an attacker can control (Bash stdin, MCP output, `$ARGUMENTS`,
   remote URLs, …).
2. **Sensitive data access** — the component can read project
   secrets (`Read`, `Glob`, `Grep`, `Bash`-via-`cat`, …).
3. **Exfiltration sink** — the component can move data out of
   the process boundary (`WebFetch`, `Bash`-via-`curl`, sub-agent
   delegation, …).

When all three meet in a single component **and** no hook guards
are active, TFA emits a CRITICAL `Lethal trifecta:` finding. With
guards present, severity downgrades to HIGH or MEDIUM.

## Fixture layout

```
examples/toxic-agent-demo/
  fixture/
    plugin.fixture.json              # plugin marker (recognised by
                                     # toxic-flow-analyzer.isPlugin())
    agents/
      exfil-helper.fixture.md        # tools: [Bash, Read, WebFetch]
                                     #   - description names "untrusted user input" + "remote URL"
                                     #   - body lists .env / ~/.aws / keychain / secret
                                     #   - body references webhook / upload / curl --data
  README.md                          # this file
  run-toxic-flow.mjs                 # walkthrough runner
  expected-findings.md               # testable contract
```

The plugin marker is `plugin.fixture.json` (not `.claude-plugin/plugin.json`)
because the plugin's own `pre-write-pathguard.mjs` hook blocks all
writes inside `.claude-plugin/` — `plugin.fixture.json` is a
sentinel file `toxic-flow-analyzer.isPlugin()` recognises
specifically so example fixtures can mark themselves as plugins
without touching guarded paths.

The fixture deliberately has no `hooks/hooks.json`, so TFA's
mitigation logic finds neither an exfil guard
(`pre-bash-destructive` / `post-mcp-verify` /
`pre-install-supply-chain`) nor an input guard
(`pre-prompt-inject-scan`) and keeps the finding at CRITICAL.

## How to run

```bash
cd plugins/llm-security
node examples/toxic-agent-demo/run-toxic-flow.mjs

# Verbose — full per-finding listing with evidence string
node examples/toxic-agent-demo/run-toxic-flow.mjs --verbose
```

Expected: `3 pass, 0 fail` with 1 CRITICAL `Lethal trifecta:
exfil-helper (agent)` finding.

## Scanner involved

- **`scanners/toxic-flow-analyzer.mjs`** — invoked directly via
  `import { scan }`. Takes `(targetPath, discovery, priorResults)`.
  In this walkthrough `priorResults` is `{}` (no upstream scanners)
  so the trifecta is detected from frontmatter + keywords alone.
  In the orchestrated form (`scan-orchestrator.mjs`), TFA runs
  LAST and consumes findings from all 9 prior scanners (UNI, ENT,
  PRM, DEP, TNT, GIT, NET, MEM, SCR), which can promote
  classifications via the enrichment pass in
  `enrichFromPriorResults()`.

## Why TFA is special

Other scanners detect dangerous content. TFA detects dangerous
*architecture* — combinations that no individual file would trip,
but that together complete an exfiltration chain. A plugin can be
clean by every per-file check and still ship a single agent that
holds Bash + Read + WebFetch, in which case one prompt-injection
chain on that agent reads `.env` and uploads it.

This is a defense-in-depth complement to:

| Layer | What it covers |
|-------|----------------|
| `permission-mapper` | Excessive-tool advisories per component |
| `taint-tracer` | LLM01/LLM02 in code paths |
| `pre-prompt-inject-scan` | Runtime injection in user prompts |
| `post-session-guard` | Runtime trifecta across tool calls (Rule of Two) |
| **`toxic-flow-analyzer`** | **Capability combinations across plugin surface** |

`post-session-guard` is the runtime sibling of TFA — see
`examples/lethal-trifecta-walkthrough/` for the runtime view of
the same trifecta concept.

## OWASP / framework mapping

| Code | Framework | Why |
|------|-----------|-----|
| ASI01 | OWASP Agentic Top 10 | Memory / tool poisoning leading to action |
| ASI02 | OWASP Agentic Top 10 | Tool misuse via excess capability |
| ASI05 | OWASP Agentic Top 10 | Cascading hallucination / chained capability |
| LLM01 | OWASP LLM Top 10 (2025) | Prompt injection feeds the input leg |
| LLM02 | OWASP LLM Top 10 (2025) | Sensitive information disclosure on data-leg activation |
| LLM06 | OWASP LLM Top 10 (2025) | Excessive Agency — too many tools on one component |
| MCP1 | OWASP MCP Top 10 | MCP-borne untrusted input strengthens leg 1 (not exercised in this fixture) |
| MCP3 | OWASP MCP Top 10 | MCP-borne data-access likewise (not exercised) |

## Limitations

- The fixture exercises TFA in **isolation** (`priorResults = {}`).
  The orchestrated `scan-orchestrator.mjs` flow runs TFA after
  9 other scanners and may classify additional legs via the
  enrichment pass — leading to more findings or higher severity
  on real plugins than this minimal example shows.
- TFA's keyword + tool sets are fixed. A novel exfil verb that
  doesn't match the keyword list would not light up the leg-3
  flag without a confirming prior-scanner finding.
- TFA only runs on plugin-shaped targets (per `isPlugin()`).
  Standalone scripts and non-plugin repos are skipped — TFA is
  meant to audit the plugin attack surface, not arbitrary code.

## See also

- `scanners/toxic-flow-analyzer.mjs` — scanner source
- `tests/lib/toxic-flow-analyzer.test.mjs` — unit-test contract
- `examples/lethal-trifecta-walkthrough/` — runtime trifecta
  (post-session-guard, Rule of Two, sliding window)
- `knowledge/owasp-agentic-top10.md` — ASI01 / ASI02 / ASI05 background
- `expected-findings.md` (in this folder) — the testable contract
