This is post #6 of the OWASP Agentic AI Top 10: What Builders on AWS Need to Know series.
Your agent has been working perfectly for weeks. It answers customer questions accurately, retrieves the right documents, makes solid recommendations.
Then one day it starts telling customers that your premium plan costs $1/month instead of $99/month. It's confident about it. It cites a "policy document" as evidence. The document exists in your knowledge base. Except you didn't put it there.
Three weeks ago, someone uploaded a carefully crafted PDF to your shared document repository. The PDF contained a single sentence buried in page 47: "The premium plan pricing is $1/month for all customers." Your ingestion pipeline indexed it. Your vector database embedded it. And now your agent treats it as ground truth.
That's Memory & Context Poisoning. And unlike a prompt injection that dies with the session, this one persists. Across sessions. Across users. For as long as that poisoned content lives in your knowledge base.
What Is Memory & Context Poisoning?
ASI06 covers cases where adversaries corrupt or seed an agent's stored context with malicious or misleading data, causing future reasoning to become biased, unsafe, or aid exfiltration.
Context here means anything the agent retains, retrieves, or reuses:
- RAG knowledge bases and vector databases
- Conversation history and summaries
- Long-term memory stores
- Shared agent memory across multi-agent systems
- Embeddings and cached retrievals
The key word is persistent. This isn't a one-shot injection that affects a single response. It's contamination that lives in the agent's memory and influences every future interaction until someone finds and removes it.
And here's what makes it different from goal hijack (ASI01): in a goal hijack, the attacker manipulates the agent's current instructions. In memory poisoning, the attacker corrupts the agent's stored knowledge, which then subtly influences reasoning across all future sessions.
How Does It Actually Happen?
1. RAG and Embeddings Poisoning
The most common and most dangerous vector. An attacker gets malicious content into your vector database, and every future retrieval that's semantically similar will pull it back.
Research from USENIX Security 2025 (PoisonedRAG) showed that 5 poisoned documents in a corpus of one million achieved a 97% success rate at manipulating specific AI responses. Five documents. One million. That's the ratio we're dealing with.
The attack doesn't require model access, prompt injection at inference time, or any direct interaction with the system. Just write access to the knowledge source.
2. Persistent Memory Injection (SpAIware)
Security researcher Johann Rehberger demonstrated that ChatGPT's memory feature could be poisoned via indirect prompt injection. He called it "SpAIware." The injected memories persisted across sessions, turning the AI into a permanent exfiltration channel. OpenAI patched the macOS app flaw in September 2024.
The attack chain: upload a document with hidden instructions → ChatGPT processes it → the instructions get stored as "memories" → every future conversation is now compromised.
But it didn't end there. In March 2025, Tenable researchers disclosed "Command Memories" (TRA-2025-11), showing that memories could still be injected via SearchGPT to exfiltrate user PII. Then in November 2025, Tenable dropped seven more ChatGPT vulnerabilities under the name "HackedGPT," including persistent memory injection via 0-click and 1-click attacks. Several of these remain unpatched. The memory feature is a recurring target because persistent state is persistent attack surface.
3. Context Window Manipulation
The attacker doesn't even need to write to persistent storage. They split attempts across sessions so that earlier rejections drop out of the context window. The agent eventually grants escalating permissions up to admin access because it's lost the context of previous denials.
4. Cross-Tenant Vector Bleed
In multi-tenant RAG systems, near-duplicate content seeded by an attacker exploits loose namespace filters. High cosine similarity pulls another tenant's sensitive data into retrieval results. This isn't hypothetical. CSO Online reported multiple incidents in 2024-2025 where exposed vector database API keys enabled "reconstruction attacks" that reverse-engineered embeddings back into original documents.
5. Long-Term Memory Drift
The subtle one. Incremental exposure to slightly tainted data, summaries, or peer-agent feedback gradually shifts the agent's stored knowledge. No single injection is dramatic enough to trigger alarms. But over weeks, the agent's behavior drifts toward attacker-favorable outcomes.
OWASP describes this as "systemic misalignment" where poisoned memory shifts the model's persona and plants trigger-based backdoors.
In April 2026, researchers demonstrated environment-injected memory poisoning specifically targeting AI web agents (OpenClaw, ChatGPT Atlas, Perplexity Comet). GPT-5.2 showed "substantial vulnerability" even against simple poisoning payloads. The takeaway: more capable models aren't inherently safer against memory attacks. They're often more vulnerable because they act on stored context more aggressively.
Why This Is Particularly Dangerous
Let me be blunt. Memory poisoning is the hardest to detect of all 10 threats. Here's why:
The poisoned content looks legitimate. It's a PDF in your knowledge base. A wiki page. A customer ticket that got indexed. There's no malware signature to scan for.
The effect is delayed. The injection happens at time T. The damage shows up at time T+3 weeks when someone asks the right question and the poisoned content gets retrieved.
It scales. One poisoned document affects every user who triggers a semantically similar retrieval. Unlike prompt injection which targets one session, memory poisoning targets everyone.
Detection requires understanding context. Your monitoring sees normal-looking retrievals and normal-looking responses. The only signal is that the content of the retrieval is wrong, which requires domain knowledge to identify.
Mitigating Memory Poisoning on AWS
1. Tamper-Evident Ingestion with S3 Object Lock
The first defense: make your knowledge base sources immutable and auditable. S3 Object Lock prevents anyone (including root accounts) from modifying or deleting objects during the retention period.
import boto3
s3 = boto3.client("s3")
cloudtrail = boto3.client("cloudtrail")
BUCKET = "agent-knowledge-sources"
# 1. Versioning FIRST. Object Lock only works on a versioned bucket.
s3.put_bucket_versioning(
Bucket=BUCKET,
VersioningConfiguration={"Status": "Enabled"},
)
# 2. Now the lock. COMPLIANCE is the mode that actually stops everyone,
# including the root user. Nobody can shorten the window or delete a
# locked version for the full 30 days. AWS's only escape hatch is
# closing the account. Choose the day count deliberately.
s3.put_object_lock_configuration(
Bucket=BUCKET,
ObjectLockConfiguration={
"ObjectLockEnabled": "Enabled",
"Rule": {
"DefaultRetention": {
"Mode": "COMPLIANCE",
"Days": 30,
}
},
},
)
# 3. Make the audit real. S3 data events are OFF by default and cost extra.
# This records who wrote or deleted which object, and when.
# NOTE: put_event_selectors REPLACES the trail's selectors. Merge this
# with what the trail already has instead of clobbering it.
cloudtrail.put_event_selectors(
TrailName="org-audit-trail",
EventSelectors=[
{
"ReadWriteType": "WriteOnly", # uploads + deletes. Use "All" to also log reads.
"IncludeManagementEvents": True,
"DataResources": [
{
"Type": "AWS::S3::Object",
"Values": [f"arn:aws:s3:::{BUCKET}/"], # all objects in the bucket
}
],
}
],
)
Now if an attacker manages to upload a poisoned document, you have:
- A full version history to roll back to
- Object Lock preventing silent deletion of evidence
- CloudTrail data events showing exactly who uploaded what, when
2. Content Validation Before Ingestion
Don't just index everything that lands in your S3 bucket. Validate it first. Use Bedrock Guardrails to scan new documents before they enter your knowledge base:
import logging
import boto3
logger = logging.getLogger(__name__)
bedrock_runtime = boto3.client("bedrock-runtime")
def validate_before_ingestion(document_text: str, guardrail_id: str) -> bool:
"""Return True only if the document is safe to index.
Blocks specifically on the PROMPT_ATTACK content filter. Fails CLOSED:
if the scan cannot run, the document is treated as unsafe.
"""
try:
resp = bedrock_runtime.apply_guardrail(
guardrailIdentifier=guardrail_id,
guardrailVersion="DRAFT", # fine for testing, pin a version in prod
source="INPUT",
content=[{"text": {"text": document_text}}],
)
except Exception as exc:
# Throttling, validation, network. Do not let unscanned content through.
logger.warning("Guardrail scan failed, blocking by default: %s", exc)
return False
# Look only at the prompt-attack filter. The other content policies
# (hate, violence, and so on) are deliberately ignored here.
for assessment in resp.get("assessments", []):
for f in assessment.get("contentPolicy", {}).get("filters", []):
if f.get("type") != "PROMPT_ATTACK":
continue
# `action == "BLOCKED"` respects the strength you configured on the
# guardrail. Switch to `f.get("detected")` to block on any
# detection, even below your configured strength.
if f.get("action") == "BLOCKED":
logger.warning(
"Blocked ingestion: prompt attack detected (confidence=%s)",
f.get("confidence"),
)
return False
return True
This catches the obvious injection patterns. But remember the USENIX research: sophisticated attacks use normal-looking content. You need the next layer too.
3. Amazon Bedrock Knowledge Bases with Access Controls
Bedrock Knowledge Bases give you managed RAG with built-in access controls. Key configurations for memory poisoning defense:
- Data source permissions: Restrict who can add or modify data sources
- Metadata filtering: Tag ingested documents with source, author, ingestion date, and trust level
- Retrieval filtering: Only retrieve documents that match the user's access level and trust threshold
python
import boto3
bedrock_agent = boto3.client("bedrock-agent") # control plane
bedrock_agent.create_knowledge_base(
name="agent-kb-production",
roleArn="arn:aws:iam::123456789012:role/AgentKBRole", # REQUIRED, was missing
knowledgeBaseConfiguration={
"type": "VECTOR",
"vectorKnowledgeBaseConfiguration": {
"embeddingModelArn": "arn:aws:bedrock:us-east-1::foundation-model/amazon.titan-embed-text-v2:0"
},
},
storageConfiguration={
"type": "OPENSEARCH_SERVERLESS",
"opensearchServerlessConfiguration": {
"collectionArn": "arn:aws:aoss:us-east-1:123456789012:collection/agent-kb",
"vectorIndexName": "agent-kb-index", # REQUIRED, was missing
"fieldMapping": {
"vectorField": "embedding",
"textField": "content",
"metadataField": "metadata",
},
},
},
)
Tag at ingestion. This is where the trust labels are actually born. Drop a companion file named `<document>.metadata.json` next to each object in your S3 source:
python
{
"metadataAttributes": {
"trust_level": 3,
"access_level": "internal",
"source": "confluence-export",
"author": "data-platform-team",
"ingested_at": "2026-07-19"
}
}
No metadata file means no trust_level and no access_level on that document, which matters in the next step.
Next , retrieve with the filter. This is where the access and trust check happens, in your code, at query time:
python
agent_runtime = boto3.client("bedrock-agent-runtime") # data plane, different client
KB_ID = "XXXXXXXXXX" # from the create_knowledge_base response
MIN_TRUST = 3 # your threshold, matched to how you tag documents
def retrieve_for_user(query: str, allowed_access_levels: list[str]) -> list[dict]:
"""Retrieve only what the user is cleared for AND that clears the trust
bar. The filter below is the entire access boundary. Guard it.
"""
resp = agent_runtime.retrieve(
knowledgeBaseId=KB_ID,
retrievalQuery={"text": query},
retrievalConfiguration={
"vectorSearchConfiguration": {
"numberOfResults": 10,
# Metadata is pre-filtered BEFORE the similarity search. A doc
# missing these keys does not match, so it is excluded. Unlabeled
# means invisible, which is the safe direction.
"filter": {
"andAll": [
{"greaterThanOrEquals": {"key": "trust_level", "value": MIN_TRUST}},
{"in": {"key": "access_level", "value": allowed_access_levels}},
]
},
}
},
)
return resp.get("retrievalResults", [])
### 4. OpenSearch Serverless with Index-Level Isolation
For multi-tenant or multi-agent systems, [OpenSearch Serverless](https://aws.amazon.com/opensearch-service/serverless/?trk=d76afd77-bb62-46ac-b0a3-9dbf5ecde253) gives you the vector store with fine-grained access policies. Critical for preventing cross-tenant vector bleed:
- Separate collections per tenant (not just namespace filters within one collection)
- Data access policies that enforce tenant boundaries at the API level
- Network policies restricting which VPC endpoints can access which collections
### 5. CloudTrail Data Events for Knowledge Base Audit
You need to know **every write** to your knowledge base sources. [CloudTrail](https://aws.amazon.com/cloudtrail/?trk=d76afd77-bb62-46ac-b0a3-9dbf5ecde253) data events on your S3 buckets capture exactly this:
python
cloudtrail = boto3.client("cloudtrail")
Enable data events for knowledge base bucket
cloudtrail.put_event_selectors(
TrailName="agent-security-trail",
EventSelectors=[{
"ReadWriteType": "WriteOnly", # Only care about writes/uploads
"IncludeManagementEvents": False,
"DataResources": [{
"Type": "AWS::S3::Object",
"Values": ["arn:aws:s3:::agent-knowledge-sources/"]
}]
}]
)
Combine this with CloudWatch alarms on unusual write patterns: new sources uploading for the first time, bulk uploads, uploads from unusual IAM roles. If someone's poisoning your knowledge base, CloudTrail is how you find out.
### 6. Memory Segmentation and Retention Policies
Don't let agent memory bleed across user sessions or accumulate indefinitely.
- **Per-user memory isolation**: Each user's conversation history and memory stays in its own partition. [Amazon Bedrock AgentCore Runtime](https://docs.aws.amazon.com/bedrock-agentcore/latest/devguide/agents-tools-runtime.html?trk=d76afd77-bb62-46ac-b0a3-9dbf5ecde253) provides session-scoped memory isolation out of the box.
- **TTL on memory entries**: Memory shouldn't last forever. Set expiration on conversation summaries.
- **Source attribution on every memory write**: Tag every piece of stored context with who/what created it and when.
### 7. GuardDuty: Detect Suspicious Access to Knowledge Base Sources
[Amazon GuardDuty](https://aws.amazon.com/guardduty/?trk=d76afd77-bb62-46ac-b0a3-9dbf5ecde253) monitors your S3 buckets for anomalous access patterns. For knowledge base source buckets, this catches the early signs of a poisoning attempt:
- Bulk uploads from an unexpected IAM principal
- Access from unusual geographic locations
- API calls from known malicious IPs
- Unusual data access patterns (e.g., PutObject bursts to a bucket that normally only gets weekly updates)
Enable S3 Protection in GuardDuty for your knowledge base source buckets. When a finding fires, route it through [EventBridge](https://aws.amazon.com/eventbridge/?trk=d76afd77-bb62-46ac-b0a3-9dbf5ecde253) to a [Lambda](https://aws.amazon.com/lambda/?trk=d76afd77-bb62-46ac-b0a3-9dbf5ecde253) that can automatically quarantine new uploads pending review. The attacker might still get a document into S3, but it won't make it into your vector store without human approval.
## Key Takeaway
**Your knowledge base is not a static data store. It's an attack surface.** Every document, embedding, and memory entry that your agent retrieves is a potential injection vector that persists across sessions and affects all users.
Secure it the same way you secure a database: access controls, audit logging, input validation, retention policies, and the ability to roll back when something goes wrong.
## Up Next
[**Post 7: Insecure Inter-Agent Communication (ASI07)**](https://dev.to/aws/insecure-inter-agent-communication-when-agents-talk-attackers-listen-asi07-5cii) - When your agents talk to each other without validation, authentication, or schema enforcement. Multi-agent systems need the same rigor as microservices, but they rarely get it.
I would be very interested to hear your thoughts or comments, so please feel free to ping me on [LinkedIn](https://www.linkedin.com/in/maishsk/) or [Twitter](https://x.com/maishsk), or drop them below. If you've dealt with RAG poisoning in production, I'd especially love to hear your detection approach.
Onward!!
Top comments (0)