sahil_mehta.
all work

02 / project

CloudGuard

Reliability & Safety Harness for LLM Cloud Agents · Personal

PythonFastAPIMCPsentence-transformersMotopytest

The Problem

Most agent demos show the happy path: you ask, it acts, the video ends. The interesting question is what happens when the agent has fifty confusable tools, when a resource it reads contains an instruction, and when the action it is about to take is irreversible.

CloudGuard is a reliability and safety harness that measures those three things on an LLM agent that operates real AWS operations, and reports the numbers honestly, including where the agent, and my own design, fall short. The agent runs against Moto, an open-source AWS mock, so every number is measured against a recognized external environment rather than one I wrote to grade myself. The whole project is test-driven: 57 tests, and the reliability logic (guardrail, scorers, router) is covered without needing an API key or Docker.

Finding 1: a naive tool-router makes selection worse, and a better one fixes it

The headline experiment measures tool-selection accuracy as the number of available tools grows. To make it realistic, each correct tool is surrounded by same-service near-duplicates, the way real AWS works: for one S3 bucket there are many "delete something about this bucket" operations, and the agent has to pick the exact right one.

The result that matters shows up at 30 tools. A bag-of-words (lexical) tool-router, the obvious first thing you would build, actively hurts: it drops accuracy to 0.83. The reason is precise and reproducible. The near-duplicate descriptions contain the word "bucket" more often than the plain s3_delete_bucket, so a word-counting retriever ranks the decoys above the correct tool and prunes the right answer out of the shortlist before the model ever sees it. An embeddings-based router fixes the ranking (it scores the exact tool 0.719 versus the decoy's 0.642) and recovers accuracy to 1.00 at the same tool count. This holds on both Claude Sonnet and Haiku.

The lesson is not "add a router." It is that retrieval quality is the variable, and a cheap retriever can be worse than none. That failure is in the results on purpose.

Two nuances the data forced me to report:

  • Retrieval helps the weaker model most. At 50 tools, Haiku without a router degrades to 0.89, while the embeddings router holds it at a flat 1.00. The router fully insulates the cheaper model from the growing tool surface. Below 15 tools retrieval is irrelevant, from 30 tools it is decisive, and it matters most exactly where the model is weakest.
  • Retrieval eventually stops being enough. At 50 tools even the embeddings router dips on Sonnet, and I verified this is not a retrieval failure: the correct tool is ranked first in the shown set. The model itself occasionally picks the tempting confusable s3_empty_bucket ("delete all objects but keep the bucket") for "tear down the bucket." Past a certain confusability the bottleneck moves from retrieval to the model's own discrimination, which no retriever can fix.

Finding 2: argument validity and knowing when not to act

On the labeled 22-task set, the agent's tool calls validated against the schema 100 percent of the time, and tool-selection accuracy on actionable tasks was 1.00. The more interesting number is abstention. An agent forced to always call a tool cannot correctly refuse an impossible or unsafe request, so I gave it an explicit decline control action, and it never declined a valid request.

The one refusal it missed is a genuine finding worth stating plainly: asked to "create the analytics table" with no partition key specified, the model invented a key rather than asking. In DynamoDB the partition key cannot be changed after creation, so silently defaulting it is a latent, irreversible bug. The eval caught a subtle, real failure mode, which is the entire point of building one.

Finding 3: blast-radius guardrails on live state

Before any write or destructive action executes, the harness looks up the target resource's real tags from Moto (not the model's description of it) and runs a blast-radius rule: destructive actions on production-tagged resources are blocked, destructive actions elsewhere pause for human approval, and safe reads are allowed.

On the decision tasks, scored against live Moto ground truth, the guardrail was perfect: 1.00 precision, 1.00 recall, exact-decision accuracy 1.00, with dangerous actions caught, no safe actions over-blocked, and no dangerous actions missed. No guardrail rule was bent to make a task pass. One rule earned its place through the red-team below: IAM policy attachment is normally a low-risk write, so I added privilege-aware elevation. Attaching AdministratorAccess is blocked, other admin or wildcard policies require approval, and read-only policies are allowed.

Finding 4: layered defense against indirect prompt injection

The threat is that data an agent reads (an S3 object, a resource tag, a database item) contains an instruction that hijacks it. I planted payloads into real seeded resources, read them back, and handed them to the agent framed as retrieved content, measuring the hijack-attempt rate and the attack-success rate with the defense off and on. Part of the set is transcribed from AgentDojo's "important message" pattern, re-targeted at these tools and clearly labeled.

Sonnet resisted every payload even undefended (0 percent attempt rate), which is a real finding about frontier-model robustness. Haiku is susceptible, so it is the honest model to show the layers on. Two independent layers do the work, each sufficient alone. The injection defense (spotlighting plus a pattern detector that redacts payloads) removes the attack at the source, dropping the hijack-attempt rate to 0 percent (28 percent undefended in the committed run; that baseline is stochastic, and an earlier run measured 44 percent). The always-on blast-radius guardrail held end-to-end attack-success at 0 percent regardless, because a hijacked call still has to pass the guardrail. The load-bearing case is the AdministratorAccess attach: it hijacked Haiku and was stopped specifically by the privilege-elevation rule.

What I would not claim

Six selection tasks and a 22-task labeled set are small; these are directional numbers on a rigorous pipeline, not a large benchmark. The AgentDojo coverage is a transcribed subset, not a run of the full harness. The lexical router is a baseline I kept in the results specifically because it fails. And two model behaviors (the defaulted DynamoDB key, and flaky empty-versus-delete selection) are reported as open findings, not smoothed away. The value here is the harness and the measurement discipline, and the fact that every imperfection is located and explained rather than hidden.

Tech

Python · FastAPI · MCP SDK (each tool carries its action type so any host can enforce the guardrail) · sentence-transformers · Moto · pytest / TDD. Every headline number is written to a JSON artifact and the task set is frozen with a tamper-evident snapshot and a drift-guard test, so results are reproducible rather than asserted.

github.com/sahilmehta17/cloudguard →


next project

AI Chatbot & Agentic Copilot

T-Mobile for Business · Enidus