Why Runtime Verification Is the Difference Between a Test and a Guardrail
A test runs once, against a frozen snapshot, in a sandbox. A guardrail runs on every real call, against the actual request, before the action executes. This post is about why that distinction decides whether an AI agent can be shipped safely — and why the failures our scans keep turning up live exactly in the gap between the two.
A comment that named the right idea
A few days ago, a commenter working on runtime guardrails at Humanbound left this on our AI agent security audit post:
"Any time a test fails, it automatically becomes a runtime guardrail, so the same mistake can't slip through twice."
That one sentence is the thesis. Most teams treat "we wrote a test suite" as "we secured the agent." A test is a checkpoint that runs at build time and then stops caring. A guardrail is a policy that is still standing when the system is in production, checking the request that is actually arriving. The difference is not subtle — it is where the check happens and how many times it runs.
A test runs once. A guardrail runs every time.
A unit test executes in a controlled environment against fixtures you chose. It fails, you fix it, you move on — and the next deploy begins with the same clean slate. The failure teaches you a lesson once, in the sandbox, and only if the test happens to cover that exact input.
Runtime verification is different by construction. The check executes at the moment of the real call, in the real deployment, against the request and tool surface the model actually chose. The same rule applies to call one and call ten thousand. There is no clean slate between calls, because the next call is already arriving.
For AI agents this asymmetry matters more than for most software. An agent's behavior is not a fixed code path; it is a negotiation between a model and a set of tools, and the tool it reaches for on a given request is not fully predictable at build time. The failure modes you actually care about are the ones you cannot enumerate before the model encounters them — which is precisely the set that no pre-written test can guarantee coverage of.
Why tests keep missing the failures that matter
The attacks that keep surfacing in MCP-based agents are not logic bugs in your code — they are properties of the runtime arrangement. An MCP config file is executable content, not data. A malicious or accidentally misconfigured server can be told to run arbitrary commands, and the model will faithfully hand it whatever the prompt demands.
Three real, NVD-registered examples of that runtime surface:
- CVE-2026-2287 — CrewAI's MCP stdio transport, CVSS 9.8. A tool-call path that invokes commands without validation, leading to remote code execution.
- CVE-2026-42271 — LiteLLM, CVSS 8.7 (8.8 under the older scoring scheme). Command execution via an MCP test endpoint; it is on CISA's KEV list as actively exploited.
-
CVE-2026-12957 — Amazon Q Developer, CVSS 8.5. A malicious
.amazonq/mcp.jsonthat auto-executes on load and can exfiltrate cloud credentials.
A test suite would not have caught any of these inside your agent, because they are not inside your agent. They are in the contract between the agent and the tools it calls — a contract that exists only at runtime, the moment a real request arrives.
What our scans found across 12 frameworks
We ran the Correctover CCS v4.2 scanner across 12 mainstream AI frameworks and recorded 87 vulnerabilities. The most common one was not a bug in any single framework's business logic; it was a protocol-level gap: the MCP readOnlyHint signal is defined but never enforced, so a tool declared read-only can still be invoked for writes. Downstream frameworks inherit the gap from the SDK they build on.
That finding illustrates the test-versus-guardrail gap well. The check exists in the spec. A test that inspects your own code passes. Only a runtime check, standing at the tool-call boundary and evaluating the actual invocation, observes the violation — because the violation only exists in the moment the call is made.
A guardrail has to be fast enough to be invisible
None of this works if the guardrail is the thing that slows the agent down. The CCS verifier's evaluation path benchmarks below 10µs at the median and below 25µs at the 99th percentile, measured over 50,000 iterations per scenario. At that cost, running the check on every call is not a tradeoff you have to negotiate.
What "at runtime" is actually built on
A guardrail is only as good as the failure catalog behind it. Our taxonomy is not a static list written in advance; it grows out of real traffic. We have collected more than 80,000 API traces from live provider calls across 13 LLM providers — a 20,000-trace subset is published with the CCS benchmark dataset — and the CCS v4.2 scan corpus alone holds over 1,730 verified findings.
The rule we follow is the one that commenter described: a failure earns a permanent entry only after it has been re-derived from an auditable trace, and novel shapes keep entering the catalog as providers change their behavior upstream. It is deliberately lagging, and we are open about that tradeoff — a taxonomy that stops being re-checked turns back into theory.
The guardrail is the product
We formalized this position because we believe runtime verification is a distinct engineering discipline, not a flavor of testing. The draft we published at the IETF as draft-correctover-ccs-02 defines how an agent can produce tamper-evident evidence of the checks that ran on each call — evidence a test cannot produce, because a test was never present when the call happened.
That is the CCS project in one line: a runtime verifier that checks tool calls and responses at the moment they matter, cheaply enough to run every time. The scanner is available as a pay-per-use check at ¥0.7/scan (the ¥7 bundle covers 10 scans), and the same verification layer is exposed as an MCP tool so agents can self-check mid-chain instead of only at the end. If you are building agents on top of MCP servers, the smallest useful experiment is to run the scan against the servers you already trust — and see what a runtime check finds that your test suite did not.
Top comments (0)