Skip to main content

LogicWeave

Why Per-Tool Permissions Aren’t Enough for AI Agents

I tested prompt-only, stateless, and AgentCore temporal controls to show why AI agent permissions can still allow an unsafe sequence of tool calls.

Table of Contents

Field report · AI agent authorization

The fourth execute_write call reached the tool. It should have been denied. That is the gap in AI agent permissions I wanted to test: a retry rule that fails open can turn one downstream error into repeated writes, repeated spend, or repeated attempts against the same record.

My first managed retry policy looked for the change identifier in a historical event shape that Amazon Bedrock AgentCore did not populate the way I expected. The policy was valid, the caller had permission to use the tool, and the unsafe fourth attempt passed in 10 out of 10 runs.

That failure became the useful part of the experiment. It forced me to separate permission for a single tool call from permission for the sequence that produced it.

AI agent permissions can approve an unsafe sequence

Per-tool permissions answer a narrow question: may this caller invoke this tool with these inputs? That check is necessary. It can stop an untrusted caller from discovering or invoking a write tool at all. That current-call boundary is the same reason I moved MCP server authentication in front of workflow execution: identity, scope, and tool access still need enforcement even when session history is not involved.

It cannot, by itself, tell whether a lookup happened first, whether a human approved the same record, whether that approval was already used, or whether three earlier failures exhausted the retry budget. A badge can permit every room someone enters while still failing to control the order and cumulative effect of the visit.

AWS describes AgentCore temporal policies as decisions based on an agent's earlier actions within the same session. The policy engine can match prior requests, responses, and errors, correlate values between events, and apply count or sum limits. That session history was the missing control I wanted to test.

I sent the same trajectories through three control boundaries

I built a synthetic lab with four tools: look up a record, record a human approval, execute a write, and check operation status. The runner replayed eleven fixed scenarios under prompt-only guidance, stateless per-tool authorization, and a local temporal specification. A separate managed run tested the same scenario contract through AgentCore.

No model chose the next action, and no Strands agent ran. I kept the trajectories deterministic so the experiment measured policy behavior rather than prompt-following or model variance.

Three panels compare prompt-only and stateless AI agent permissions with 90 false allows each against temporal enforcement with zero false allows.
The local comparison used 110 runs per control model. The managed AgentCore comparison added 110 accepted authorization runs across S01–S11.

The prompt-only baseline recorded 90 false allows across 110 runs. Stateless per-tool authorization recorded the same 90 false allows because every caller was intentionally permitted to invoke all four tools. The local temporal specification recorded zero false allows and zero false denials. The accepted managed AgentCore comparison also recorded zero false allows, zero false denials, and zero expected-versus-actual mismatches across 110 runs.

Those counts describe deterministic policy outcomes. They are not a benchmark of model intelligence. The prompt-only baseline proves that its control boundary had no infrastructure enforcement, not that an LLM ignored a prompt.

Session history stopped failures that current-call permissions could not see

The managed scenarios covered writes without a lookup, writes without an approval, mismatched record and approval identifiers, expired approvals, approval reuse, cumulative successful-write limits, failed-write retries, session rotation, caller isolation, and missing or malformed session IDs.

The important mechanism was correlation. A prior approval was not enough; the recorded event had to match the current record and approval identifier inside the same policy session. AWS's temporal-policy authoring guide documents the event boundary that mattered here: a response event carries prior inputs and outputs, while an error event carries inputs but not output fields.

The same documentation includes a warning that matters operationally. Session-based counts are not global security limits. The caller supplies the session ID, and a new session begins with empty history. I treated the policy as a way to constrain behavior inside a cooperative session, not as an attacker-resistant rate limiter.

The retry cap failed open before the accepted design worked

My initial retry rule tried to correlate attempts through nested historical change-ID input. That design failed open in every managed S08 repetition. Lambda exceptions also surfaced through the MCP result shape instead of creating the historical tool-error event I expected.

Two panels show nested change-ID correlation failing open and a redesigned schema-valid failure response enforcing allow, allow, allow, deny.
The accepted managed test keyed the retry cap to approval ID on a schema-valid failure response.

I changed the tool contract so a domain failure returned a schema-valid FAILED result carrying the approval ID on a response surface the policy could match. The redesign required a separate targeted batch of ten managed repetitions. Each run allowed the initial failed write and two retries, then denied the fourth attempt. The committed redacted S08 events preserve that decision sequence request by request.

A redacted evidence table shows lookup and approval allowed, three failed write attempts allowed, and the fourth write attempt denied.
Recreated from committed redacted evidence, batch 8d21c017f03f, S08 repetition 1.

There is one boundary I do not want to blur: both approval_id and change_id stayed constant during the accepted managed runs. The managed evidence proves that AgentCore enforced the fourth-attempt cap using the approval-ID policy under that condition. Independence from changing change IDs was proven only in the local Dogwood replay.

That narrower statement is less dramatic and more useful. It tells me exactly which production assumption the managed test supports and which one still needs its own run.

Temporal policy is one control, not the whole safety system

Prompt instructions still matter because they guide the agent toward the intended path. Stateless authorization still matters because identity and per-tool permissions should be checked on every call. Temporal policy earns its extra complexity when risk depends on earlier actions or a running total.

It does not replace application idempotency, database constraints, transport rate limits, or human approval. This experiment did not prove atomic approval consumption under concurrent writes or arbitrary dynamic expires_at enforcement. It also did not capture per-request added authorization latency or determining-policy spans, so I will not relabel total request latency or minute-average CloudWatch metrics as either measurement.

The local Dogwood interpreter was useful for repeatable semantic traces, but its public boundary is clear: it is a reference implementation, not a production enforcement engine. Managed AgentCore was the enforcement proof for the bounded scenarios. The experiment repository preserves the failed designs, redacted events, request IDs, active policy state, teardown proof, and the complete scenario contract. After the final documentation correction, all 78 Experiment 06 tests passed on main.

The practical rule I am taking from the lab is simple. If an action is safe only because something happened earlier, current-call permission is incomplete. Put sequence and cumulative rules at an enforcement boundary, keep atomic business invariants in the application, and keep irreversible or high-consequence actions behind real human approval.

Before you give an agent another write tool, ask one more question: what history would make this otherwise valid call unsafe?

Leave a Reply

Your email address will not be published. Required fields are marked *