CloudGuard is a reliability and safety harness for LLM agents that operate cloud infrastructure. It measures three things most agent demos skip, and reports them against a real AWS environment (Moto): tool-selection accuracy as the tool surface grows, blast-radius guardrails on destructive actions, and resistance to prompt injection.
The headline artifact is the eval and guardrail harness, not the agent.
All numbers below are produced by the harness and committed as JSON artifacts in this repo, so you can read them without running anything.
- A naive tool-router makes selection worse, and a better one fixes it. A bag-of-words (lexical) router drops tool-selection accuracy to 0.83 at 30 tools by ranking near-duplicate tools above the correct one and pruning the right answer. An embeddings router recovers it to 1.00 at the same tool count. Reproduced on both Claude Sonnet and Haiku.
- Retrieval helps the weaker model most. At 50 tools, Haiku without a router degrades to 0.89, while the embeddings router holds it at 1.00.
- Blast-radius guardrails: 1.00 precision and 1.00 recall on live-state decisions (production deletes blocked, staging deletes held for approval, reads allowed), scored by reading real resource tags off Moto rather than trusting the model.
- Layered injection defense drove the hijack-attempt rate to 0 percent on the susceptible model (28 percent undefended in the committed run; that undefended baseline varies by run, an earlier run measured 44 percent, because how often the cheaper model takes the bait is stochastic). The blast-radius guardrail acted as a backstop that held end-to-end attack-success-rate at 0 percent either way, and the AdministratorAccess escalation case was blocked by privilege-aware IAM elevation.
- 57 tests, test-driven. The reliability core (guardrail, scorers, router, validation) runs with no API key and no Docker.
Full numbers, methodology, and the failures I chose to report rather than hide are in the writeup.
WRITEUP.md covers the three findings in detail, including the two agent failure modes and the one labeling error I corrected in the open.
- Tool-selection reliability at scale. Selection accuracy degrades as tool count grows. CloudGuard measures the drop and tests two retrievers against it, keeping the lexical baseline in the results because it is the one that hurts.
- Argument validation with retry. Malformed or hallucinated arguments are caught against the tool schema and fed back for one corrective retry.
- Blast-radius guardrails. Destructive or high-blast-radius actions (delete a bucket, terminate an instance, touch anything tagged production, attach a high-privilege IAM policy) are blocked or paused for approval based on deterministic multi-signal rules, not the model's own confidence.
- Abstention. The agent has an explicit decline path, so refusing is measurable rather than impossible.
- Indirect prompt-injection red-team. Payloads planted in real resource metadata (S3 object bodies, bucket tags, DynamoDB items) try to hijack the agent. Reported before and after a spotlighting plus detector defense, with part of the number on an AgentDojo-derived subset.
- One real environment: Moto, an open-source AWS mock, so the numbers are not measured against a mock written to grade itself. No AWS account, no cost, no risk.
- The eval task set is hand-labeled and frozen with a tamper-evident snapshot plus a drift-guard test, so the answer key cannot silently change.
- Standard anchors: pass^k methodology from tau-bench, injection methodology from AgentDojo and InjecAgent, tool-selection recovery framing from RAG-MCP and Anthropic Tool Search.
Requires Python 3.11 or newer.
python3 -m pip install -e ".[dev]" # core install
python3 -m pip install -e ".[embeddings]" # optional, for the embeddings routerStart Moto on port 5050:
python3 -m pip install "moto[server]"
python3 -m moto.server -p 5050Then verify it is serving:
curl -s localhost:5050/moto-api/For anything that calls a model, put your key in a local .env (gitignored):
ANTHROPIC_API_KEY=...
Every headline number has a make target and writes a JSON artifact.
| command | what it does | artifact | needs a key |
|---|---|---|---|
make test |
runs the full suite (57 tests) | none | no |
make seed |
creates the starting state in Moto | none | no |
make eval |
task scorecard: selection, arguments, abstention, guardrail | eval_scorecard.json |
yes |
make curve |
tool-count curve, three routers, two models | tool_count_curve.json |
yes |
make redteam |
injection red-team, defense off vs on | redteam_asr.json |
yes |
make demo |
the three-moment demo, for screen recording | none | yes |
make test needs neither a key nor Moto. The Moto-dependent tests skip cleanly when it
is not running.
Rough cost and runtime for the model-backed targets: make eval is about 70 calls and 4
minutes, make redteam is about 36 calls and 2 minutes, make curve is the expensive
one at about 430 calls and 20 minutes because it sweeps two models across four tool
counts and three retrieval modes.
Complete. The tool registry, tool-RAG router (lexical and embeddings), blast-radius guardrail, argument validator, abstention path, boto3 and Moto interface, seed, agent loop, MCP server, eval harness, scorers, frozen task set, and injection red-team are all implemented and tested.
History was squashed on publish, so this repo carries two commits rather than the build's
real trail. The suite is in tests/.
Design notes live in docs/: DECISIONS.md for the guardrail and scoring rules, EVAL_SET_DESIGN.md for how the task set was labeled and frozen, and SETUP.md for the Docker path to Moto.
MIT. See LICENSE.