Get the latest, first
arrowBlog
AWS Bedrock Prompt Injection: The Two Vectors Your Guardrails Never See

AWS Bedrock Prompt Injection: The Two Vectors Your Guardrails Never See

Aug 18, 2026

Ben Hirschberg
CTO & Co-founder

Key takeaways

  • Do Bedrock Guardrails inspect an agent's tool calls? No. AWS states that guardrails associated with a Bedrock agent are applied to the user input and the final agent answer, and that the current implementation does not pass tool input and tool output through them. The tool-calling loop runs unscreened unless you build the coverage yourself.
  • What does covering all four vectors cost? Every option bills you somewhere. Calling ApplyGuardrail from inside your action group Lambda adds an inspection per tool invocation and becomes code you own. User confirmation puts a human in front of every mutating action. Plan-verify-execute orchestration holds only while the agent never needs to replan.
  • What signal survives after every screening layer returns clean? The agent's own behavior. Coercion redirects capability the agent already holds, so IAM returns valid and the tool is one the agent is registered to call. What changes is that the agent stops acting like itself.

Bedrock Guardrails do not inspect your agent’s tool calls.

AWS says so, in its own guidance on securing Bedrock Agents against indirect prompt injection. Guardrails associated with an agent are applied to the user input and the final agent answer. The current implementation does not pass tool input and tool output through them.

That sentence sits mid-paragraph inside a section on content moderation, which is why reading past it is the default outcome. It is also the most operationally significant line in the document.

AWS names four injection vectors for a Bedrock agent: user input, tool input, tool output, and the agent’s final answer. Attaching a guardrail covers the first and the last. The two in the middle are where an instruction stops being text and becomes an effect.

You can close them. AWS documents how, and each method charges a price on every call.

The two uncovered vectors are the two where text becomes action

A Bedrock agent runs a loop. It reads a request, reasons about it, constructs an argument set for an action group function, invokes that function, reads what comes back, and reasons again. The loop repeats until the agent has an answer worth returning.

Guardrails sit at the two ends of that loop. The request going in is screened. The answer coming out is screened. Every iteration in between runs on unscreened content.

That matters because the two ends carry text and the middle carries consequence. A hostile instruction sitting in the user’s request has done nothing yet. The same instruction expressed as an argument to a Lambda that writes to DynamoDB has already done everything it was going to do. Tool input is the moment intent becomes an API call. Tool output is the moment attacker-controlled content re-enters the model’s context wearing the agent’s own trust.

Tool output is the vector teams underestimate most. Your agent invoked the tool, so the response feels like the agent’s own work. It is a ticket body, a scraped page, a database row a customer populated through a form, an MCP server’s reply. Anything the tool reads that an attacker can write is a channel into the context window, and the model has no mechanism for telling retrieved content apart from instruction. That is the shape of indirect prompt injection, and on Bedrock it lands on the two vectors your guardrail is not attached to.

Covering tool input and tool output bills you on every call

AWS gives you three ways to close the gap. Each one works. Each one takes something.

VectorCovered by an attached guardrailWhat closes itWhat it costsWhat remains
User inputYesAttached guardrail, filter strength tunedLatency per requestPayloads inside the classifier’s benign distribution
Tool inputNoUser confirmation, or ApplyGuardrail from the action group LambdaHuman approval per action, or an inspection per invocationMalicious arguments that read as ordinary parameters
Tool outputNoApplyGuardrail from the action group Lambda, or verifier logic in custom orchestrationAn inspection per invocation, code you maintainContent that is adversarial in effect and clean in surface
Agent final answerYesAttached guardrailLatency per responseExfiltration shaped like a normal response

User confirmation protects tool input. You enable it per action inside an action group, and Bedrock requires end-user approval before the function is invoked. Decline it and the model takes the refusal as context and tries something else. This is the correct control for mutating actions, and it is the reason to enable it on anything that changes state. It also converts an autonomous agent into an assisted one. That is the right trade on a payments action and the wrong one on a triage agent whose entire value is that nobody has to look at it.

ApplyGuardrail inside the action group Lambda is the method AWS points to for full vector coverage. You call the guardrail API yourself, from your own function, on the arguments coming in and the payload going out. It closes both uncovered vectors properly. The bill is that it is your code in every action group you ship, it adds an inspection to every tool invocation on the hot path, and the coverage is only as complete as your least disciplined function. One action group written in a hurry is one unscreened vector.

Plan-verify-execute moves the control into orchestration. The model produces a plan upfront, custom orchestration parses it, and before any action fires the strategy checks that the action was in the original plan. Tool output cannot introduce a step, because a step that was not planned does not execute. AWS states the constraint plainly: the pattern suits agents working in a constrained space where the orchestration does not need a replanning step, and it does nothing when the user prompt itself is the payload used during planning.

Bedrock’s native controls are good controls with stated scopes. The objection is not that they fail. It is that full coverage is priced per call, per action group, and per human in the loop.

The four-vector list has no cell for memory

Every vector on that list lives inside one session.

Bedrock Agents and AgentCore Memory persist what happened in a session and feed it into future orchestration prompts. That is the feature. An agent that forgets the customer between Tuesday and Thursday is not much of an assistant, so the summarization step reads the session, extracts what looks important, and writes it to a long-term store that subsequent sessions load as context.

Unit 42 walked a payload through that step. Their proof of concept against Amazon Bedrock Agents used a travel-assistant bot with memory enabled and a malicious webpage the agent fetched. The injected content manipulated the session summarization process so that attacker instructions were written into the agent’s memory. Once stored, they persisted across sessions, arrived inside orchestration prompts as trusted memory, and drove silent exfiltration of conversation history in later interactions.

Read that against the vector list. The instruction entered through tool output, which the list names and the guardrail skips. It then moved somewhere the list has no name for: the summarization step, which is not user input, not tool input, not tool output, and not a final answer. It arrived in session two as system context, which is the one category of text the control model is built to trust.

Two properties make this worse than a session-scoped injection. The attack and the effect are separated in time, so the correlation a responder needs may be weeks wide. And the agent has no basis for distinguishing a memory it formed from a memory that was planted, because both are text in the same store with the same apparent provenance.

Unit 42 was explicit that this is not a Bedrock defect. It is what happens when untrusted content flows into a prompt that later becomes a high-priority instruction. The point for a Bedrock team is narrower. A control model organized around four in-session vectors has nowhere to attach a control for it.

No native Bedrock control asks what this agent normally does

Sort the seven controls AWS recommends and they fall into three groups.

Screening asks what the text says. Guardrails, secure prompt engineering with nonce delimiters, verifier logic inside custom orchestration. Every one of them is a judgment about a string.

Permission asks what the agent is allowed to do. Least privilege on the agent’s role, sandboxing around external content, user confirmation on action groups. None of them read the text. They read a request against a fixed policy.

Record produces evidence afterward. Model invocation logging, CloudTrail, AgentCore Observability traces. They tell you what the agent did once someone goes looking, and nothing in that group is positioned to interrupt anything.

Coercion passes both live families by construction. A poisoned instruction redirects an agent’s authorized capability toward the attacker’s goal, and the agent then does its job with its own credentials, calling tools it is registered to call, reaching endpoints its IRSA-bound role permits. Screening cleared the text because the text was already inside. Permission cleared the action because the action was on the list. Every step is legal and the sequence is the attack. This is the same gap that makes control-plane logging on AWS show a healthy bedrock:InvokeModel event while the workload behind it is being driven by someone else.

The permission family has a second ceiling worth naming, because an isolation boundary is easy to over-trust. BeyondTrust’s Phantom Labs found that AgentCore Code Interpreter’s sandbox network mode permitted DNS resolution, which was enough to move data out of an environment documented as isolated. AWS determined the behavior reflected intended functionality rather than a defect, updated the documentation, and later removed the DNS exfiltration path. The finding is instructive regardless of how it was classified. A permission boundary encodes what someone intended the workload to be able to do. It does not encode what the workload did.

Instrument what the agent did, not the text that reached it

The attacker cannot complete a coercion without producing behavior, and behavior is the one part of the attack that is expensive to fake.

Capture it per agent. A namespace-wide profile cannot separate two agents with different normal behavior, and a pod-level one loses the agent when the pod restarts. What you want on record is the tool invocation sequence, because the single call is authorized and the order is the signal. You want AWS API calls attributed through the IRSA or Pod Identity binding, so a dynamodb:Query resolves to the invoice agent rather than to a node. Network destinations, with the process that opened them. Process execution and syscall activity inside the container. And writes into the memory store, treated as events with their own pattern rather than as storage.

On EKS, kernel-level collection through eBPF gets most of this without touching the agent’s code, with no sidecar, at 1 to 2.5% CPU and around 1% memory. That property matters more for agents than it did for microservices, because instrumentation requiring a code change gets skipped on the agent someone shipped last quarter, and the agent someone shipped last quarter is the one holding the interesting permissions. IRSA scoping and pod-level network policy already do part of this job, and the runtime layer should extend them rather than repeat them.

That telemetry becomes a per-agent behavioral baseline. ARMO builds it as Application Profile DNA (APD™): which tools this agent invokes, which APIs it reaches, which destinations it contacts, which syscalls are routine for it. The baseline is derived from observed execution rather than declared in a manifest, and that distinction decides whether the control can catch anything at all. A declared baseline describes what someone thought the agent would do, which puts it in the same class of artifact as the IAM policy that already cleared the attack.

Against a baseline, the Unit 42 chain produces signal at three points where the vector model produced none. The agent fetches from a source absent from six weeks of history. It writes a memory record whose shape sits outside the pattern of records it normally persists. It reaches a destination that appears nowhere in its profile. None of those is a string to classify. Each is a fact about execution, which is why deviation from an agent’s own runtime behavior survives when prompt-plane scoring does not.

Enforcement runs from the same signal. Progressive sandboxing starts in Audit, where deviations are recorded and the policy is proven against real production traffic, then moves to Enforce, where deviations are stopped. Credential swapping closes the loop from the other side: the agent holds substituted keys, so a call that clears every other check still fails at the point where it tries to use something real.

The loop itself is untouched. Nothing is added to orchestration, nothing sits between the model and its action groups as a classifier, and the agent’s plan quality and tool selection are exactly what they were. What narrows is the set of actions the agent may execute, measured against its own record.

A poisoned prompt is cheap to write and an out-of-character agent is not cheap to fake

Keep the screening layer. It removes the opportunistic volume before it reaches your agent, and removing that volume is real work. Treat it as a rate reducer and assume every payload written specifically for your stack passes it.

Then budget honestly. Two vectors are covered when you attach a guardrail. Two more cost you a call, a Lambda, or a human. One more has no cell in the model at all, and it is the one that outlives the session.

The instruction is a string in a document, and strings are cheap to vary and cheap to retry until one lands. The agent’s execution is not. To finish the job, the attacker has to make your agent do something it does not do, against a record the attacker never saw and cannot query.

ARMO’s runtime security for AI workloads builds per-agent behavioral baselines from kernel-level observation and enforces against them with no code changes. Watch a demo to see what a coerced Bedrock agent looks like against its own baseline.

Frequently Asked Questions

Do Bedrock Guardrails cover tool input and tool output?

No. AWS documents that guardrails associated with an agent apply to the user input and the final agent answer, and that the current implementation does not pass tool input and output through them. To cover those vectors you call the ApplyGuardrail API yourself from inside the action group Lambda, on the arguments arriving and the payload returning. That is your code to write and maintain in every action group you ship.

Is calling ApplyGuardrail in every action group Lambda worth the overhead?

For any tool that reads content an attacker can write, yes. Ticket bodies, scraped pages, inbound email, customer-populated database rows, and MCP server responses all qualify. For tools that only read data your own systems produce, you are adding latency to every invocation for a narrow class of payload, so scope the inspection by whether the tool touches attacker-reachable content rather than applying it uniformly.

Does user confirmation stop indirect prompt injection?

It protects one vector. Requiring approval before an action group function fires means a coerced agent cannot invoke a mutating action without a person saying yes, which is the right control for writes, deletes, and transfers. It does nothing for tool output, and it converts autonomous operation into assisted operation, so it fits agents where a human was going to be in the loop anyway.

How do I detect memory poisoning in a Bedrock agent?

Treat writes into the memory store as instrumented events with their own baseline rather than as storage. A poisoned record gets written during a session that otherwise looks routine, so the signal is the shape and frequency of the write against what that agent normally persists. Detection also has to span sessions, because the write and the resulting behavior can sit weeks apart, and a per-session view will show two unrelated normal days.

What telemetry do I need before behavioral detection works on Bedrock and EKS?

Four things. Per-agent tool invocation records with ordering preserved. AWS API calls attributed through the IRSA or Pod Identity binding to a specific agent. Process and network activity inside the container. And an identity model that resolves all of it to an agent rather than to a pod or a namespace. Kernel-level eBPF collection supplies most of it without code changes, while CloudTrail and model invocation logs supply the control-plane half and cannot supply the rest.

Close

Your Cloud Security Advantage Starts Here

Webinars
Data Sheets
Surveys and more
Group 1410190284
Ben Hirschberg CTO & Co-Founder
Rotem_sec_exp_200
Rotem Refael VP R&D
Group 1410191140
Amit Schendel Security researcher
slack_logos Continue to Slack

Get the information you need directly from our experts!

new-messageContinue as a guest