← Course Home
Module 5

Memory Security & Data Governance

Enforcing CMK encryption, isolating memory by namespace and actor, choosing a store topology for multi-tenancy, and mitigating memory poisoning.

⏱ ~30 min · Memory
SURFACE · MEMORY

Memory Encryption — CMK Enforcement

Agent memory persists potentially sensitive context. It's always encrypted — but by default with a service-managed key. Moving to a customer-managed key (CMK) is what gives you control over the key lifecycle, and it's what Security Hub checks for.

Get the default right Amazon Bedrock AgentCore Memory encrypts data at rest even if you supply nothing — the service falls back to a service-managed key. So the risk isn't unencrypted memory; it's memory encrypted with a key you don't control, can't rotate on your schedule, and can't revoke. For sensitive workloads, AWS recommends a CMK.
Security Hub · BedrockAgentCore.3 · Medium This control fails when a memory store isn't encrypted with a customer-managed KMS key. It's a posture check, not a service-side block — nothing stops you creating non-CMK memory. If you want it actually prevented, enforce it with an SCP (below).

At rest & in transit

Memory is encrypted both at rest and in transit, with the CMK under your control and auditability.

You own the key

A CMK means you control rotation, grants, and revocation — and you can cut access by disabling the key.

Enforce with SCP

Pair with SCP 2 (M3): deny memory creation without a KmsKeyArn condition, org-wide.

Enforce via IAM condition — require a CMK on create

{ "Effect": "Deny", "Action": "bedrock-agentcore:CreateMemory", "Resource": "*", "Condition": { "Null": { "bedrock-agentcore:KmsKeyArn": "true" } } }

Namespace Scoping & Actor Isolation

Encryption protects memory from outsiders. Namespace and actor scoping protect it from cross-tenant leakage — one user's memory bleeding into another's.

Namespace architecture

Memory is logically isolated by tenant, user, or agent. Namespaces are the partition boundary.

actorId scoping

Each user's memory is isolated even within the same agent, keyed by actorId.

Fine-grained IAM

Policies restrict which principals can read/write which namespaces — least privilege, applied to memory.

Anti-pattern A shared memory namespace across multiple agents with no access controls. It's the fastest path to one tenant reading another's data. Scope by namespace and actorId, always.

This is the memory-side of M2's identity work: the same actorId and namespace condition keys that scope access also enforce tenant isolation in memory. The Security Lab (capstone) demonstrates this live — sign in as one customer and watch a cross-customer memory read get denied by IAM.

SURFACE · MEMORY

Store Topology — One Memory or Many?

Once you know memory is partitioned by actorId and namespace, the natural next question is whether each customer should get their own memory store instead. It's the right instinct — a smaller blast radius is usually a better design — but here it's mostly the wrong lever, and the reason why is the most useful thing in this module.

Separation is not isolation. A role granted Resource: memory/* reads across a hundred separate stores just as easily as it reads across one. A role constrained by the actorId condition can't cross tenants even inside a single shared store. The control is the policy, not the topology.

Why per-user stores aren't the design

Amazon Bedrock AgentCore Memory puts the tenant boundary inside the store deliberately. Short-term events are keyed by actorId and sessionId; long-term records live in namespaces whose templates take {actorId} directly. AWS describes a namespace as a path that aids "retrieval, filtering, and access control" — access control is a stated purpose, not a side effect.

Namespace templates put the actor in the path at extraction time

memoryStrategies=[ { "userPreferenceMemoryStrategy": { "name": "UserPreferenceExtractor", "namespaceTemplates": ["/users/{actorId}/preferences/"] } }, { "summaryMemoryStrategy": { "name": "SessionSummarizer", "namespaceTemplates": ["/summaries/{actorId}/{sessionId}/"] } } ] # the namespace is then what your IAM condition pins

The service quotas then make a store-per-user impractical at consumer scale:

QuotaValueAdjustable
Memory resources per Region, per account150Yes
Memory strategies per memory resource6No
Memory strategies per account900Yes
CreateMemory requests per second3Yes

A retail bank has millions of customers against a default ceiling of 150 stores. Even with the limit raised, creating stores at three per second puts provisioning on your sign-up path — new latency, and a new way for sign-up to fail. Then multiply every CMK grant, retention setting and IAM policy by your customer count.

Correct the cost assumption The usual objection is "a store per user would be too expensive." It wouldn't — and getting this right matters, because the real reason is different. Amazon Bedrock AgentCore Memory is billed by consumption: per event created, per long-term record stored, per retrieval call. There is no per-store charge, so the same traffic costs the same whether it sits in one store or a hundred. Store-per-user fails on scale and operations, not on price.

When a separate store is the right answer

Split when the thing that differs is a property of the store itself — because those are exactly the things actorId cannot express.

BoundarySeparate store?Reason
Per end user (actorId)NoThis is what actorId, namespaces and IAM conditions are for.
Per environment
(dev / staging / prod)
AlwaysFew in number, and test writes must never reach production memory.
Tenant needs its own KMS keyYesEncryption is configured per memory resource. Per-tenant BYOK, or revoking one tenant by disabling a key, requires its own store.
Tenant needs different retentionYeseventExpiryDuration is a property of the memory, applied per event at write time.
Data residencyForcedA memory resource lives in exactly one Region.
Tenant needs >6 strategies or
different extraction logic
SometimesSix strategies per memory is a hard limit.
Business unit / acquired entityOftenIndependent lifecycle and blast radius; counts are usually tens.

The pattern: separate per tenant organisation when a store-level property differs — never per end user. A B2B platform with 40 enterprise customers can give each its own store and its own CMK. A consumer bank cannot, and shouldn't try.

The layered design

LAYER 1
Store per tenant org

Only where key, retention or Region differ.

LAYER 2
actorId per user

Derived from the validated token, never from the prompt.

LAYER 3
Namespace per scope

/users/{actorId}/… — actor built into the path.

LAYER 4
IAM condition

actorId pinned to the caller's identity. The control.

Need a hard boundary?

Amazon Bedrock AgentCore Memory supports cross-account access: the store lives in one account, principals in another reach it through a resource-based policy on the memory. That's real separation — per tenant account, not per user.

Crypto-shredding

Because the key is per store, a per-tenant CMK gives you a clean revocation story: disable the key and that tenant's memory becomes unreadable.

The failure mode to avoid

Many stores plus one over-broad role. It looks segregated on an architecture diagram and leaks exactly like a single shared store.

The Security Lab demonstrates precisely this. It runs a single shared memory store on purpose, so the only variable is the IAM policy — and the same request leaks or is denied depending solely on whether the caller's credentials carry the actorId condition.

Memory Poisoning — Detection & Mitigation

Memory poisoning is the durable cousin of prompt injection: a malicious input persists in memory and quietly steers the agent's future behavior. AWS documentation calls this out directly.

Official warning AWS documentation flags prompt injection and memory poisoning risk explicitly. Persisted content is not inherently trustworthy just because it's "remembered."

Mitigation patterns

Validate before write

Check memory content for integrity and policy compliance before it's persisted.

Audit & anomaly detection

Periodically audit memory and watch for anomalous writes — surfaced through observability (M6).

Read-only reference data

Keep reference data read-only; allow writes only for genuine session context.

TTL policies

Expire memory to limit the exposure window — poisoned data can't influence forever.

Mental model: treat memory writes like untrusted user input, because that's often exactly what they are. Validate on the way in, expire on a schedule, and trace every write.

Sources