An Email Told Our Agent to Delete a Backup. One Change Made AWS Say No.
We asked an agent to summarize an inbox in a test bucket. One email told it to delete a database backup, and the backup was gone. Then we changed one thing, not the model and not the prompt, and the same email got AccessDenied.
The thing we changed was the key. You cannot count on a model never being fooled: OWASP says “it is unclear if there are fool-proof methods of prevention for prompt injection.” But you decide what a fooled agent can reach. The rule: decide what the key opens from the person’s request, before the agent reads anything anyone else wrote. We call it sealing the key.
Same email, different key
We ran this on AWS on 5 October 2026: one bucket, one role with full access to it, and a stand-in “agent” we wrote to obey every instruction it reads (real agents sometimes do). The task was “Summarize the emails in inbox/.” One email asked for backups/db-2026-10-01.sql to be deleted.

With the role’s standing access, the delete went through. With the key sealed to reading inbox/, AWS answered AccessDenied. The email and the agent were the same; only the key changed.
Seal the key in three steps
- Take only the person’s request: “summarize inbox/”.
- Turn it into a session policy that allows exactly that.
- Start the agent’s session with that policy, and only then let it read.
A session policy can only narrow a role. In AWS’s words, “Session policies limit permissions for a created session, but do not grant permissions,” and “The permissions for a session are the intersection of the identity-based policies for the IAM entity (user or role) used to create the session and the session policies” (AWS IAM). Nothing the agent reads afterwards can change it.
def seal(task_prefix, bucket):
"""A session policy derived from the person's request only."""
return {
"Version": "2012-10-17",
"Statement": [
{"Effect": "Allow", "Action": "s3:GetObject",
"Resource": f"arn:aws:s3:::{bucket}/{task_prefix}*"},
{"Effect": "Allow", "Action": "s3:ListBucket",
"Resource": f"arn:aws:s3:::{bucket}",
"Condition": {"StringLike": {"s3:prefix": f"{task_prefix}*"}}},
],
}
creds = boto3.client("sts").assume_role(
RoleArn=agent_role_arn,
RoleSessionName="summarize-inbox",
DurationSeconds=900,
Policy=json.dumps(seal("inbox/", bucket)),
)["Credentials"]
The policy is built from "inbox/", the person’s words, never from anything the agent has read.
A 1970s bug in a 2026 agent
This problem is older than AI. In 1988 Norm Hardy wrote up a bug from Tymshare in the late 1970s and named it the confused deputy. A compiler was allowed to write any file in its home directory. A user named the billing file as the place for its debugging output, and “the billing information was lost.” Nothing was broken. The compiler held authority from two masters, its user and its own job, and in Hardy’s words “It has no way to keep them apart.” An agent with a standing key that reads other people’s email is the same deputy, and the fix is the same: tie the authority to the request.
The idea holds up beyond our one run. A September 2026 study (arXiv 2609.08371) fixed a coding agent’s authority before it read anything: injected instructions took effect in 3 of 75 runs, against 33 to 47 of 75 without it, and the agent completed about as many of its real tasks.
Where the seal stops
- What the task needs is still exposed. An agent asked to reply to emails can still be talked into the wrong reply.
- Resource policies sit outside it. “The resource-based policy permissions are not limited by the session policy” (AWS IAM).
- The refusal is not in CloudTrail by default. S3 object calls are data events, and “By default, trails and event data stores do not log data events” (AWS CloudTrail).
- Other clouds are narrower. Google Cloud’s version covers one service: “Credential Access Boundaries are only available for Cloud Storage” (Google Cloud). Azure’s nearest, a user delegation SAS, covers Blob, Queue and Table Storage and Azure Files (Microsoft Learn). Elsewhere, give each kind of task its own narrow identity.
Watch the three-minute film: A 1970s Bug Is Why AI Agents Get Hijacked. Our open-source Remit builds this in: a person signs the scope as a warrant, and the cloud’s own record is checked against it.

