Prompts are not permissions: where to enforce what an agent's tool calls may do
A rule written in the system prompt can be talked around. This is where each kind of agent permission rule should live instead: tool code, a gateway policy, IAM or a human approval step.
On this page
The problem: a rule the agent can be talked out of
Say an agent can issue refunds. Somewhere there is a rule: agents may refund up to a limit, and anything above it needs a person. The question for a platform team is simple to ask and easy to get wrong. Where does that rule actually live?
A common first answer is the system prompt: "Never refund more than 500." That is a request to the model, not a control. OWASP's LLM06:2025 Excessive Agency entry lists hallucination and direct or indirect prompt injection among the common triggers for an agent taking damaging actions with the tools it has. Its "complete mediation" mitigation says to "Implement authorization in downstream systems rather than relying on an LLM to decide if an action is allowed or not".
The second common answer is "we scoped the IAM role". That helps, but IAM does not see that this particular call is a refund of, say, 4,000 for a customer the caller should not touch. So the decision is about layers. Each kind of rule has a place where it can be enforced deterministically, and putting it anywhere else either does nothing or makes it hard to change and audit.
Four places a rule can live
"Catches" here means the layer can refuse the call deterministically. The comparison is reasoning built on the cited docs.
| Layer | What it can see | What it catches | How it fails |
|---|---|---|---|
| Prompt instructions | Whatever the model reads | Nothing deterministically. It lowers how often the agent tries a bad call | OWASP lists hallucination and direct or indirect prompt injection among the common triggers of Excessive Agency |
| Checks in tool code and the database | Arguments and live data: order state, balances, ownership | Rules that need stored data: "not more than the order total", "only open orders can be cancelled", idempotency | Every tool team re-implements rules, there is no single view of who may do what, and a rule change needs a deploy. Tool code often does not receive the caller's identity |
| Gateway policy (AgentCore Policy with Cedar) | Caller identity from the token, tool name, tool arguments, and with temporal policies the session history | Which caller may call which tool, with which argument values, and in which order within a session | Sees only the request input, not downstream data. Covers only traffic through the gateway, and only tool calls: MCP prompts and resources are always allowed |
| IAM and resource policies | AWS API action, resource ARN, condition keys | What the tool's credentials can reach at all, and who may invoke the tool | Does not see business arguments such as a refund amount, and does not know the end user unless the call runs with that user's credentials |

Notice that the gateway asks AgentCore Policy before any tool code runs, the tool code and the table still check what needs live data, and IAM limits what the tool can reach even if a call goes around the gateway.
OWASP lists "minimize extension permissions" and "execute extensions in user's context" as separate mitigations from complete mediation, which matches this split: IAM caps what the credentials can do, and a policy check decides whether this specific call is allowed. It also says logging and rate limiting "will not prevent Excessive Agency, but can limit the level of damage". Useful, but not a permission model.
The same pattern shows up outside AWS. Anthropic's write-up on Claude Code sandboxing describes restrictions enforced "at the OS level" and, for Claude Code on the web, credentials "never inside the sandbox" with a proxy outside it that "verifies this credential and the contents of the git interaction". The check and the credential sit outside the thing that can be manipulated.
How AgentCore Gateway sees a tool call
Policy in Amazon Bedrock AgentCore became generally available on 2026-03-03 in 13 Regions, including Asia Pacific (Mumbai). The announcement says "Policy operates outside your agent code". Policies sit in a policy engine attached to an AgentCore Gateway, "which intercepts agent-tool traffic and evaluates each request against the policies before allowing or denying tool access". You can write rules in natural language and they are converted to Cedar.
The docs describe how a tool call becomes a Cedar authorization request:
- Principal: from the JWT
subclaim, as anAgentCore::OAuthUser. Other JWT claims become tags on the principal. An IAM-authenticated principal (AgentCore::IamEntity) has the IAM ARN as its ID and no tags. - Action: the tool name, for example
RefundTool___process_refund. - Resource: the gateway ARN.
- Context:
context.inputholds the tool arguments.
The engine "enforces default-deny and forbid-wins semantics". A request is allowed only if a policy permits it, and a single forbid from any active policy denies it. Default deny is the property that matters most for agents: a new tool added to the gateway is blocked until a permit covers it. Watch broad permits, though: a permit on action in AgentCore::Action::"<TargetName>" also covers any tool later added to that target.
A few constraints shape how you write rules. The Cedar schema is generated from the gateway's MCP tool manifest. The docs say "Only available context is context.input".
There are no regular expressions (only like with *) and no floats: a JSON number maps to Cedar Decimal with at most 4 decimal places. "Cedar does not support wildcard actions", and a policy that names specific actions must name a specific gateway ARN; to group tools, put them in one gateway target and match action in AgentCore::Action::"<TargetName>".
Two scope limits are easy to miss. "Policy evaluation applies only to MCP tools"; MCP prompts and resources are always allowed. And tool listing is a meta action: a tool is listed if any set of circumstances would permit a call, so "a tool appearing in the list does not guarantee that a subsequent call to that tool will be authorized." The docs show a denied tool call coming back as an MCP tool result with isError: true and a "Tool Execution Denied" message. Treat that as a normal outcome in the agent, not something to retry.
Three kinds of rule, and where each belongs
Argument limits
AWS's own example permits a refund when the principal's username tag is refund-agent and context.input.amount < 500. A fixed limit on a request argument is exactly what the gateway can enforce centrally.
A sketch of the same idea with a hard limit as a forbid. It is illustrative and untested; the gateway ARN and names are placeholders.
// Illustrative only. Not tested against a real policy engine.
permit(
principal is AgentCore::OAuthUser,
action == AgentCore::Action::"RefundTool___process_refund",
resource == AgentCore::Gateway::"<gateway-arn>"
)
when {
principal.hasTag("username") &&
principal.getTag("username") == "refund-agent"
};
// Hard limit. Written with "unless" and "has" so that a missing
// argument denies instead of erroring.
forbid(
principal,
action == AgentCore::Action::"RefundTool___process_refund",
resource == AgentCore::Gateway::"<gateway-arn>"
)
unless {
context.input has amountMinor &&
context.input.amountMinor <= 50000
};Two choices in that sketch are deliberate. The amount is an integer in minor units, because JSON numbers become Decimal and floats are not allowed, and integers are easier to reason about. And the forbid guards the attribute read, for a reason covered in the failure modes below.
If the limit depends on stored data, such as "not more than the order total" or "not more than three refunds per customer per month", the gateway cannot check it. Policy sees only context.input. That rule belongs in tool code or a database condition.
Identity-aware rules
The gateway can match on JWT claims such as scope or role, which arrive as principal tags, or on an IAM role ARN. A rule like this is only as good as the token.
If the agent authenticates with a machine-to-machine token, the principal is the agent, not the person it is acting for. A rule that says "only the account owner may close this account" cannot be written against a token that does not carry the account owner. Fix identity propagation first; policy cannot add identity the token does not have.
Approval gates
OWASP says user approval may be implemented "in a downstream system ... or within the LLM extension itself". On AWS there are three common shapes:
- Amazon Bedrock Agents can ask for user confirmation for specific actions before the agent invokes them.
- AgentCore temporal policies can decide on "the history of an agent's actions within a session", including "requiring an approval before an action". AWS's example permits
disburse_paymentonly ifapprove_claimfor the sameclaimIdreturnedapproved: true"within 1h". These are written in Dogwood, a language the docs describe as "compatible with Cedar". - An approval step in the downstream workflow, outside the agent entirely.
The trap with the temporal option is self-approval. If the agent can call approve_claim with the same identity it uses for disburse_payment, it can approve its own payment.
Separating the two is not simple. The docs say the gateway binds each policy session to the caller's authenticated identity, so an approval made by a different principal is recorded in a different session. In a Gateway to Runtime to Gateway chain, the original caller's identity is carried through to the agent's tool calls. The docs also say a prior action is recorded as a response only if it was itself permitted.
Treat a temporal approval rule as a sequencing check. When the approver must be a different person from the agent, enforce that separation in the approval tool or a downstream workflow.
What breaks
Each point below is reasoning built on the cited fact.
- Calls that go around the gateway. Policy applies only to calls through the gateway. If the Lambda behind a tool can be invoked another way, the policy does nothing for that path. Lock the function's resource policy to the gateway's role, follow AWS's confused-deputy guidance on source conditions when a service principal is involved, and keep the execution role to the permissions the tool needs.
- A silent switch to LOG_ONLY. A gateway runs in
ENFORCEorLOG_ONLY. Anyone withbedrock-agentcore:UpdateGatewaycan switch toLOG_ONLY, where "every tool call succeeds regardless of forbid policies", or remove the engine. The docs say "No separate action or condition key protects the mode field beyond bedrock-agentcore:UpdateGateway". Treat that permission as an admin permission and assertENFORCEin your infrastructure tests. - Forbids that fail open. Cedar's "skip on error" rule says that "if a policy's evaluation returns error, the policy does not factor into the authorization response; it is skipped." A forbid that reads a missing optional argument or tag errors, gets skipped, and the call is allowed if a permit matches. A permit that errors is also skipped, which denies under default deny. So "errors deny" is true for permits only. Guard every attribute read and watch the
MismatchErrorsmetric. - Session limits that reset. For temporal rules the caller sends the session ID in a header, and "The Gateway does not generate a session ID on your behalf." A count limit "constrains activity within a session rather than across all of a caller's sessions." A caller that starts a new session starts from zero. Use temporal rules for sequencing and approvals inside a conversation; keep account-wide limits downstream.
- Agent identity read as user identity. With machine tokens, rules are about the agent. Do not write per-user rules on top of them.
- Scale and ownership. The quotas are not adjustable: 1,000 policies per engine, 20 temporal policies per engine, 3 temporal operators per policy, a 24-hour maximum temporal window and a 400 KB schema per engine. Several teams on one engine need clear ownership of policies.
- Generated policies that are valid but wrong. Natural-language authoring is validated, and automated reasoning flags overly permissive, overly restrictive and ineffective policies. That checks the shape of the rules, not whether they match the business intent. Review generated Cedar as code.
Temporal enforcement has its own setup needs: the gateway and its targets must be in the same account and Region, and the gateway role needs bedrock-agentcore:GetWorkloadAccessToken.
Rolling out a policy without breaking traffic
AgentCore gives you three tools for this, and they are worth using in order.
- Validation. Schema checks always run. The default
validationModeisFAIL_ON_ANY_FINDINGS, which also runs the automated reasoning checks. The docs sayIGNORE_ALL_FINDINGS"is not recommended for production". - Per-policy LOG_ONLY. Each policy is
ACTIVEby default orLOG_ONLY. ALOG_ONLYpolicy is evaluated on every request and reported in traces and metrics, but "never combined into the enforced decision". TheLogOnlyDecisionFlipsmetric counts the cases where it would have changed the outcome. Ship a new rule this way, check the flips, then make it active. - Decisions you can audit. CloudWatch metrics in
AWS/Bedrock-AgentCoreincludeAllowDecisions,DenyDecisions,MismatchErrorsandNoDeterminingPolicies. With gateway traces on, spans carryauthorization_decision,authorization_reasonanddetermining_policies, so a denied call can be traced to the rule that denied it.
The recommendation
For an agent with write access on AWS whose tools sit behind AgentCore Gateway, the layering below is reasoning built on the sources above.
- Put "who may call which tool with which arguments" in the gateway policy. Start from default deny, write explicit permits per tool or per target, add forbids for hard limits, guard every attribute read, keep
FAIL_ON_ANY_FINDINGS, and roll out new rules asLOG_ONLYuntil the flips look right. This is OWASP's complete mediation in one audited place. - Keep data-dependent rules in tool code and the database. Order state, ownership, running totals and idempotency need live data the policy cannot see.
- Keep IAM and resource policies tight. The tool reaches only what it needs, and only the gateway can call it. This is the backstop when layers 1 and 2 are wrong.
- Use the prompt for intent only. Telling the agent the rules means fewer bad attempts and cleaner logs. It is never the control.
- Put a human approval in front of high-impact actions, through Bedrock Agents confirmation or a downstream workflow step. A temporal policy can require that the approval step came first in the session, but check how your setup keeps the approver's identity separate from the agent's.
- Protect the control itself. Restrict
UpdateGatewayand test that the mode isENFORCE.
When a different choice is better
- One agent, a few tools, one team, no gateway yet. Checks in tool code plus tight IAM may be enough. A central policy layer earns its cost when several agents or teams share tools, or when security or compliance needs one place where decisions are made and logged.
- Tools that are not behind AgentCore Gateway. Policy does not cover them. If the agent runs on AgentCore harness, the
before_tool_callhook added in September 2026 lets a Lambda "return a synchronous allow or deny decision that can stop an invocation or skip a tool call." The hook is configured with the agent, so check who can change it. For custom checks on traffic that does go through the gateway, a REQUEST interceptor is another option (one per gateway, Lambda only, documented for "fine-grained access control" and custom authorization; it should be idempotent because the gateway "may retry"). - Rules that need downstream data or limits across sessions. Enforce them in the downstream system.
- Rules about the end user rather than the agent. Fix identity propagation into the token first.
- Content checks on the request. Policies can call Amazon Bedrock Guardrails (content filters, prompt attack and sensitive information) at evaluation time and condition on a confidence score between 0 and 1. That is a signal to add, not a replacement for permission rules.
- Money movement in a regulated setting. Use a human approval step that the agent's own identity cannot satisfy, and keep account-wide limits downstream, in addition to the gateway rules.
A reference build, and what it does not show
InfraTales/agentcore-mcp-gateway is a public reference build of the gateway and policy layer, not a production system. It puts three MCP tools on two Lambda functions behind an AgentCore Gateway, with Cognito CUSTOM_JWT inbound auth for two machine-to-machine clients. It has two Cedar permits: read tools for any OAuthUser, and cancel_order only when the principal's scope tag matches *orders/admin*. Its CDK tests assert that the gateway defaults to ENFORCE and that policies use FAIL_ON_ANY_FINDINGS. CI runs npm audit, the check script, engine tests against DynamoDB Local and a 14-mutant script, and the latest ci run on main (commit bf7cb35, 2026-10-16) succeeded.
What it does not show:
- It has no published deployment or smoke-test result, so it does not show the gateway denying a real call.
- Its policy tests check the Cedar text and the CloudFormation template. Nothing evaluates Cedar decisions.
- It has no forbid policy, no argument-level limit, no end-user identity (machine tokens only), no approval gate and no temporal policy. The refund-limit, self-approval and session examples in this article are not in the repo.
So it shows the shape of scope-based permits with enforcement mode and validation pinned in tests. The harder rules above are written guidance, not demonstrated code.
Sources
Pages were read on 2026-10-17.
Amazon Bedrock AgentCore Developer Guide
- Policy in Amazon Bedrock AgentCore: Control Agent Interactions
- Authorization flow
- Policy conditions
- Schema constraints
- Core concepts
- Policy scope
- Use an AgentCore Gateway with Policy in AgentCore
- Policy enforcement modes
- Test a policy in LOG_ONLY mode
- Validation and analysis overview
- AgentCore generated policy observability data
- Errors
- Temporal policies
- Policy sessions and identity propagation
- Guardrails in policies
- Using interceptors with Gateway
- Quotas for Amazon Bedrock AgentCore
- Release notes for Amazon Bedrock AgentCore
Other AWS documentation
- Policy in Amazon Bedrock AgentCore is now generally available, AWS What's New, 2026-03-03: general availability and Regions.
- Get user confirmation before invoking action group function, Amazon Bedrock User Guide: Bedrock Agents confirmation.
- Create a service role for Amazon Bedrock Agents, Amazon Bedrock User Guide: the Lambda resource policy.
- Defining Lambda Event Function permissions with an execution role, AWS Lambda Developer Guide: execution role scoping.
- The confused deputy problem, IAM User Guide: source conditions.
Standards and specifications
- Authorization, Cedar Policy Language Reference Guide: Cedar's evaluation rules.
- LLM06:2025 Excessive Agency, OWASP Gen AI Security Project: the mitigations, including complete mediation.
Industry sources
- Making Claude Code more secure and autonomous with sandboxing, Anthropic, 2025-10-20: the sandboxing pattern.
Reference code
- InfraTales/agentcore-mcp-gateway, GitHub: own reference build.
Next step
Have the same problem on your stack?
Send the architecture, AWS bill concern, deploy pain, or GenAI reliability issue. I'll find the first real bottleneck and propose a small, reversible fix.
FAQ
How do I stop an AI agent from calling dangerous tools?
Enforce the rules outside the agent. Put who may call which tool with which arguments in a gateway policy such as AgentCore Policy, which starts from default deny. Keep rules that need live data in tool code and the database, keep IAM and resource policies tight so only the gateway can call the tool, and put a human approval in front of high-impact actions. The system prompt only lowers how often the agent tries a bad call.
Is a system prompt enough to stop an agent from calling a tool it should not?
No. A prompt lowers how often the agent tries a bad call, but hallucination and prompt injection can still produce one. OWASP's complete mediation mitigation says to implement authorization in downstream systems rather than relying on the model to decide.
What can AgentCore Policy see when it evaluates a tool call?
The principal from the JWT sub claim with other claims as tags (or an IAM ARN), the tool name as the action, the gateway ARN as the resource, and the tool arguments in context.input. It cannot see downstream data such as an order's current state.
Does a Cedar policy that hits an error deny the request?
Not always. Cedar skips a policy whose evaluation errors. A skipped permit leads to a deny under default deny, but a skipped forbid stops blocking, and the call goes through if a permit matches. Guard attribute reads and watch MismatchErrors.
Can a gateway policy enforce a per-customer limit across conversations?
Only partly. Temporal counts and totals are per session, the caller supplies the session ID, and a temporal window can be at most 24 hours. AWS describes using a stable per-user session ID for cross-conversation limits, but whoever sets that header controls the boundary. Keep limits that must always hold, such as monthly per-customer totals, in the downstream system.
Should we still scope IAM if we have a gateway policy?
Yes. Policy only covers traffic through the gateway. IAM and resource policies decide what the tool's credentials can reach and who can invoke the tool at all, which is what limits damage if a call goes around the gateway or a policy is wrong.
When is a gateway policy layer not worth it?
When one team runs one agent with a few tools and no gateway. Tool-code checks plus tight IAM can be enough there. The central layer pays off when agents or teams share tools, or when security needs one audited place for decisions.
Related reading

Rahul Ladumor
Principal Cloud & AI Platform Architect. AWS Professional certified, 5x AWS Community Builder. I work with teams that have real users, real AWS bills, and real production pressure.
About Rahul →