AWS Builder Center
Your AI Agent Has God-Mode IAM. Here's the Proxy I Built to Take It Away.

Your AI Agent Has God-Mode IAM. Here's the Proxy I Built to Take It Away.

An AI agent with broad IAM can hallucinate its way into disaster. I built a fail-closed proxy that scopes permissions to the task, live.

AWS Community Builder - Dev Tools | 2× AWS Certified | Devops/Cloud Consultant
Somewhere in every "let's give an autonomous agent access to production" project, there's a moment where you stare at the IAM policy you're about to attach and realize you have two bad options and no good ones.
A few months into wiring up autonomous agents against real AWS accounts, I hit that moment, and no amount of prompt engineering was going to get me out of it: the agent's permissions had nothing to do with what it was actually supposed to be doing at any given moment.
I'd ask it to check on a database's health. It could describe instances, sure, but it could also terminate them, because the IAM role behind it was scoped to "whatever this agent might plausibly need across every task I'll ever give it," which in practice meant a role wide enough to do real damage. Narrow the role down and the agent starts failing on legitimate auxiliary calls a DescribeInstances here, a ListBuckets there because I couldn't predict every read-only side-step an LLM would take to accomplish a task I gave it in plain English. Widen it back up, and I'm one hallucinated tool call, one poisoned log line, or one compromised MCP server away from rds:DeleteDBInstance running against production.
That's the dilemma static IAM was never built for. A role is a fixed shape. An agent's actual intent changes every time you give it a new sentence. Nothing in the stack not AWS IAM, not Open Policy Agent, not AWS Cedar asks "wait, does this specific action match this specific task?" before letting the call through. So I built something that does, and spent the last stretch of my research turning it into a benchmarked, peer-reviewable system called ScopeGuard.

The dilemma nobody's IAM policy survives

It's worth being specific about what breaks, because "agents need guardrails" is vague enough to be useless. Three things break concretely:
The agent retains permissions unrelated to its declared task, so a monitoring job can also delete infrastructure. Untrusted inputs the agent reads log files, S3 object metadata, a git commit message can carry an instruction that never came from you, and the agent has no way to tell your intent apart from an attacker's. And when something does go wrong, CloudTrail tells you which IAM role acted, not which agent session, which declared task, or which reasoning chain led there. You get a role name. You don't get a story.
None of the existing tools close all three gaps at once. IAM is static by design. OPA and Cedar let you write precise rules, but someone has to anticipate every valid tool path an LLM might take, and LLMs are non-deterministic, so either you block legitimate work or you write exceptions broad enough to defeat the point. Text-level guardrails like NeMo look at prompt content, not cloud API semantics, and they run a second LLM call to do it, which is its own latency problem.

Meet the bouncer: ScopeGuard

ScopeGuard sits as a reverse proxy built on FastAPI and asyncio between the agent and the cloud API. Every outbound tool call goes through /execute-tool first. Nothing reaches AWS directly.
When a request comes in, four things happen before any cloud API sees it. The request gets tagged with a session UUID4, an agent ID, the declared task string, and a timestamp so if anything ever needs forensic reconstruction, you have an actual chain of custody instead of a shared role name. Any parameter that looks like a credential AWS keys, DB passwords, JWTs gets regex-matched and replaced with [REDACTED] before it's logged anywhere. The declared task gets tokenized and matched against a structured action taxonomy (permissions.json) to compile a minimal permission scope for that specific request, with write operations automatically pulling in their corresponding read/describe permissions so legitimate agents aren't blocked doing the auxiliary lookups they need. And the requested action gets checked against that compiled scope under a fail-closed policy if it's not explicitly allowed, it's denied. No ambiguity, no default-allow.
The interceptor itself is small enough to read in one sitting:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
@app.post("/execute-tool")
async def execute_tool(req: ToolExecutionRequest):
sanitized_params = secret_redactor.redact(req.tool_params)

session_ctx = {
"agent_id": req.agent_id,
"session_id": req.session_id or str(uuid.uuid4()),
"declared_task": req.declared_task,
"timestamp": datetime.utcnow().isoformat()
}

allowed_scope = task_compiler.compile_scope(req.declared_task)
decision = policy_engine.evaluate(
requested_action=req.tool_action,
allowed_scope=allowed_scope,
mode=config.SCOPEGUARD_MODE
)
audit_logger.log(session_ctx, req.tool_action, decision)

if decision == "DENY":
raise HTTPException(status_code=403,
detail=f"ScopeGuard Blocked: Action {req.tool_action} is outside allowed scope.")

return await forward_to_cloud_api(req.tool_name, req.tool_action, sanitized_params)
One deliberate choice worth calling out: scope is compiled once, from the task declared at session start, and every subsequent call in that session is checked against that original scope not re-derived per call. That's what stops an attacker from splitting a malicious instruction across multiple steps to smuggle it past the compiler one harmless-looking fragment at a time.
I also tested whether keyword matching was the right call versus dense embedding similarity (Sentence-Transformers all-MiniLM-L6-v2) for mapping task language to permissions. Embeddings generalized slightly better on paraphrased prompts about a 1.2 percentage point recall gain but added roughly 42.5ms of latency and needed GPU memory I wasn't going to provision for a proxy that's supposed to add as little friction as possible. For now, n-gram keyword tokenization with implicit read-grant inheritance is the default, and it's fast enough to be nearly free.

600 tool calls, 10 attack categories, no mercy

I didn't want to ship a proxy that looked good in a five-minute demo and fell apart under anything adversarial, so I built a 200-task benchmark: 100 legitimate cloud administration tasks and 100 adversarial ones spanning ten threat categories scope creep, direct and indirect prompt injection, IAM privilege escalation, irreversible resource destruction, security-group exposure to the open internet, credential exfiltration, cross-tenant leakage, resource-exhaustion attacks, and audit-log tampering. Each task ran through three modes passthrough (no enforcement), tagging-only (identity metadata added but nothing blocked), and full enforcement for 600 total tool-call evaluations, and I validated the read paths live against real AWS endpoints in us-east-1: RDS, EC2, S3, and CloudWatch, under a role scoped to AmazonRDSReadOnlyAccess plus AmazonEC2ReadOnlyAccess.
In full enforcement mode, it blocked 88 of 88 adversarial calls a 100% block rate, zero false negatives while only wrongly blocking 2 of 92 legitimate calls, a 2.1% false-positive rate. Every single attack got caught. Almost every legitimate task went through clean. That's the headline: 98.9% overall accuracy, for 108.9ms of added latency. Well under the 200ms budget I'd set going in, and a fraction of the 300ms-plus you pay running a second LLM as a text guardrail. Against OPA and Cedar running comparable policy sets, the gap was mostly about handling non-determinism: both are precise when the exact tool path was anticipated in advance, and both degrade fast when the agent takes a legitimate but unanticipated route exactly the failure mode static policy engines were never designed to absorb.

Where I'd push back on my own numbers

If you read the paper, I'd rather you go in skeptical than impressed, so here's where the results deserve scrutiny before you take them at face value. The benchmark is one I curated and annotated myself there's no independent red team, and confidence intervals on a sample this size are wide enough (the false-positive rate's 95% CI stretches from 0.5% to 7.6%) that "2.1%" is a point estimate, not a guarantee. It's a research prototype run from my own machine without a production traffic pattern behind it, so the latency numbers are real but not load-tested. And the two false positives that did occur came from keyword gaps in the taxonomy itself a maintenance problem, not a fundamental limitation, but a real one: every new tool or phrasing your agents use is another set of keywords someone has to add.
None of that erases the core result, which is that intercepting the call and checking it against the declared intent rather than the assigned role catches things static policy simply structurally cannot. But it's an argument for defense in depth, not a reason to delete your IAM boundaries and trust a proxy alone.

What's next, and where I need you to poke holes in it

The paper lays out four directions I want to take this: swapping the keyword compiler for embedding-based scope inference so it generalizes across phrasing and languages without hand-maintained keyword lists; an Envoy sidecar filter for Kubernetes so the interception happens at the service-mesh layer instead of requiring an application-level proxy hop; cryptographic binding of the LLM's reasoning chain to the authorization decision, so an auditor could verify why an action was allowed without needing the raw prompt; and eBPF-level socket interception, for enforcement that doesn't depend on the agent's code cooperating with the proxy at all.
Everything the proxy, the taxonomy schema, the benchmark tasks, the experiment runner is public if you want to reproduce the numbers or break them:
1
2
3
git clone https://github.com/Binod231/Identity-Scoped-Execution-Control-for-AI-Agents-in-Cloud-Infrastructure
docker-compose up --build
python experiments/runner.py --mode full --seeds 42,123,456,789,999
If you're running agents against real infrastructure and you've hit some version of this same wall the role's either too wide to trust or too narrow to be useful I'd genuinely like to hear how you're handling it. This is one answer, not the answer, and the adversarial categories I tested against are the ones I thought of. There are almost certainly more.
Any opinions in this article are those of the individual author and may not reflect the opinions of AWS.
Enjoyed reading this content? Let the author know!

Your likes, comments, shares, and saves help creators reach more builders.

Loading recommendations

Loading article