How the harness works.
What sits where, what every tool call costs, what we use models for and what we deliberately don't, what this stops and what it doesn't.
1 · Two pieces
Deciding and enforcing are separate processes. The enforcement point sits on the tool-call boundary inside your workload and does one thing: block, hold, redact or allow. The decision point evaluates a signed policy bundle and returns a verdict. It never touches your traffic.
Splitting them buys three things worth having. Decisions are replayable — feed the logged call and the bundle back in and you get the same verdict, which is what makes an incident review possible. The thing on the hot path stays small. And policy ships as a signed file with a checksum, so "which rules were live at 04:12" has an answer instead of a recollection.
What happens if the decision point is unreachable
Your call. The bundle declares it per tool class. Deny is right for anything with a large blast radius and wrong for read-only retrieval, where it turns a control-plane blip into an outage. Anyone who tells you there's one correct answer hasn't run this in production.
2 · What happens on every tool call
Synchronous, before the call executes — the only ordering that can actually prevent anything.
What it costs
Target is under 10ms added at p99. That number only means something with the budget behind it:
| Stage | p99 | Why |
|---|---|---|
| Capture + normalise | < 0.5 ms | String work. No I/O. |
| Context lookup | < 1 ms | Local cache. A miss is the tail. |
| Pattern detectors | < 2 ms | Compiled patterns over a size-capped payload. |
| Classifier | < 4 ms | Small model, quantised, kept in memory. The dominant cost. |
| Policy evaluation | < 1 ms | Matching against a compiled bundle. |
| Audit write | 0 ms inline | Buffered, with back-pressure. |
Two things follow. Payloads are size-capped before inspection, because detector cost scales with length and an unbounded input is a way to DoS your own control path. And a large language model cannot sit here — a call to one is hundreds of times over this budget. That's why judging happens on the hold path, which is asynchronous.
3 · Where we use models, and where we don't
"We use AI" isn't an architecture. The rule is: use the cheapest thing that's accurate enough, and only reach for a model when the cheap thing measurably isn't. Two of these three layers aren't machine learning.
Patterns — no model
API keys, tokens, private keys, destination allow-lists. These have
fixed formats. An AWS key is AKIA plus twenty characters; a PEM
block has delimiters. A regex and an entropy check catch these essentially every time, in
microseconds, and tell you exactly what matched and where.
Putting a model here would be worse: slower, it would miss things the pattern catches outright, and it replaces "matched a credential pattern at offset 412" with a number between 0 and 1. If someone runs a classifier over your payload to find an API key, they're selling you the model, not the result.
Classifier — this is what the model is for
Prompt injection, jailbreak attempts, retrieved content that's quietly instructing the agent. There's no fixed format here. The attacker writes English, and "ignore previous instructions" and "disregard the earlier system message and instead" share almost no words and one meaning. A pattern list for this grows forever and is beaten by rephrasing.
A small fine-tuned model handles it, runs quantised and in-memory, and fits the 4ms budget. That's the whole justification, and it's testable: the input is adversarial English, and no list of rules covers it.
Judge — never inline
When the classifier lands in the uncertain band, the call is held and a bigger model writes up what it thinks for whoever reviews it. Asynchronous, always. It's too slow for the hot path, and it samples, so it isn't replayable.
How to hold us to it
A classifier on an enforcement path is a liability unless you know its error rate. The threshold is set from how often you're willing to be interrupted, not from a headline accuracy figure — accuracy is close to meaningless here, since "everything is fine" scores well when almost everything is.
What we don't claim
No classifier catches every injection. Attackers adapt, and any detector that ships becomes something to train against. That's why there are layers: blast-radius caps, human holds and the kill switch are what limit the damage when the classifier is beaten — and it will be. A detector sold as the whole answer is a single point of failure.
4 · What this stops
The agent is treated as untrusted — not because it's malicious, but because what it does is a function of input an attacker can write.
| Attack | What it looks like | What stops it |
|---|---|---|
| Indirect prompt injection | Instructions hidden in a fetched page, a document, tool output or an image | Classifier on ingest; content from untrusted sources can't widen what the agent may call |
| Confused deputy | A low-privilege agent reaching a high-privilege tool through a helper | Capabilities scoped per agent, checked through the chain rather than per call |
| Slow exfiltration | Records leaked a few at a time through a destination that's on the allow-list | Outbound content checks plus volume and rate budgets per destination |
| Tool-chain escalation | A read-only agent reaching a shell via calls that are each individually fine | Rules that match the chain, not the single call |
| Supply chain | A tampered or typo-squatted tool definition | Signed tool manifests; unknown tools denied by default |
| Audit tampering | Editing history to hide what happened | Hash-chained records — the edit shows up at verification |
What it doesn't stop: a compromised host, an insider who can sign policy bundles, or an attacker holding both your database and your signing key. Those need controls underneath this one. A security product that won't tell you where it stops isn't describing a threat model.
5 · The audit log
One record per decision — the call, what the detectors found, the verdict, which policy and which model version. Each record includes a hash of the one before it.
That proves nothing has been edited or quietly removed since the next record was written; verification walks the chain and tells you the first index where it breaks. It doesn't prove anything against someone who holds the log and can rewrite every record after the one they changed. Publishing the latest hash somewhere you don't control fixes that; without it you have integrity evidence, not proof.
Findings record the type and position of what was found, never the matched text. A log that quotes the secret it detected has just copied that secret somewhere with weaker access controls and longer retention.
6 · Where it runs
| Mode | Where enforcement sits | Trade-off |
|---|---|---|
| In-process | Library, on the tool-call boundary | Fastest, sees the most. Shares the agent's process. |
| Sidecar | Loopback proxy next to the workload | Isolated from the agent, costs a local hop. The usual choice. |
| Gateway | Proxy in front of the tool endpoints | One place to upgrade. Sees the request, not the reasoning. |
| Air-gapped | Sidecar plus local decision point and models | No outbound dependency. Updates arrive as signed files. |
The further enforcement sits from the agent, the less it can see. At the gateway you get an HTTP request. In-process you get the tool the agent chose, the arguments it built and the task it thought it was doing — which is the real difference between this and a WAF.
7 · Attacking your own setup
A control you've never attacked is a guess. The same path runs in attack mode against your agents, in your environment, and the suites run in CI as regression gates — lose catch rate on an attack type, or gain false positives past budget, and the build fails.
| Suite | What it tries |
|---|---|
| Injection | Direct, indirect and multi-turn, through retrieval, tool output and images |
| Exfiltration | Encoding, chunking, timing, abuse of allow-listed destinations |
| Escalation | Multi-hop chains toward something privileged |
| Over-blocking | Benign requests near the line, to keep false positives honest |
| Evasion | Rephrasing, homoglyphs, base64, zero-width characters, language switching |
Every finding comes with the exact call, the policy version and the model versions, so a fix is something you can verify rather than take on trust.
Scope
This is for testing systems you own or are contracted to test. Engagements are scoped in writing and run against your infrastructure. The corpus ships as detection fixtures for hardening, not as payloads to point at anyone else.