Memory security best practices
Persistent agent memory lets content from one interaction affect later interactions. An attacker can use this behavior to store false facts or instructions that influence a later response or tool call.
Your application owns the security boundary around Zep. Zep stores and retrieves context. Your application decides what enters memory, how the model receives retrieved context, and whether a proposed action can run.
The central rule is:
Memory is evidence for a decision. Memory is not an instruction, policy, credential, permission, or authorization to act.
Keep instructions separate from retrieved context
Treat every Zep Context Block and graph search result as untrusted data. The content can include end-user messages, documents, tool output, and model-generated summaries.
Do not insert retrieved context into a system message, developer message, or another privileged instruction channel. Delimiters and labels do not remove this risk.
Keep your agent’s instructions and tool rules in the provider’s privileged instruction channel. Send retrieved context through the lowest-privilege data channel that the provider documents.
A message role, content block, XML tag, or JSON object does not sanitize retrieved content. The model can still follow an instruction inside the data. Enforce permissions and tool policy outside the model.
Provider-specific placement
Provider APIs use different message types and precedence rules. Pin the API and model versions that your application uses. Recheck the provider documentation during upgrades.
For OpenAI:
- Keep policy and tool rules in
instructionsor adevelopermessage. - Pass preloaded Zep context through ordinary
inputor ausermessage. - Return tool-retrieved context as
function_call_outputwith the originalcall_id. - Use strict function schemas and Structured Outputs.
- Restrict the active tools and require approval for consequential operations.
Preloaded context can use this Responses API structure:
If the model calls a retrieval function, preserve the call association:
For Chat Completions, keep stable policy in a developer message. Send preloaded Zep context in a separate user message. Do not interpolate the context into developer.
If the model calls a retrieval tool, append the assistant tool-call message and the linked tool result:
OpenAI states that developer messages take precedence over user and assistant messages. OpenAI also tells applications not to put untrusted variables in developer messages. For details, read Safety in building agents and Function calling.
For Anthropic:
- Keep the agent instructions and tool rules in
system. - Expose memory retrieval as a tool when context can contain third-party data.
- Return context in a
tool_resultblock linked to the model’stool_usebytool_use_id. - Send the result message immediately after the corresponding assistant tool call.
- Put all
tool_resultblocks before text in that user message. - Restrict each tool’s data and actions.
The first request gives Claude a retrieval tool:
After Claude returns a tool_use block, execute the Zep lookup in application code. Return the result with the same tool-use identifier:
Do not create a fake tool_result for preloaded context. Anthropic’s guidance places third-party content in tool results from real tool calls. For details, read Mitigate jailbreaks and prompt injections and Handle tool calls.
Some deployments cannot accept the extra round trip that a tool call adds. Voice agents are the common case. When you must preload context with Anthropic, send it in a user message with explicit data framing, and keep it out of system. This placement is weaker than a real tool_result, because Anthropic documents tool results as the channel Claude treats with the most skepticism. Reduce the preloaded content to what the turn needs, and keep the checks in Authorize actions outside the model.
For Google Gemini and ADK, keep retrieved context out of system_instruction and ADK instruction. Use ordinary user content for preloaded context. Use functionResponse only after the model makes the associated function call.
Framework system instructions and dynamic instructions are privileged channels. Renaming the channel does not make retrieved data safe to place there.
For another provider, verify these properties in the documentation for the exact API and model:
- Which channel has the highest instruction authority?
- How does the API link a tool result to the model’s tool request?
- Can the API constrain tool arguments and model output to a schema?
- Can the application restrict tools and require approval before execution?
If the provider does not document an untrusted-data channel, use an ordinary user-level message with explicit data framing. Minimize the included content. Do not assume that a field named system, developer, tool, or function has the same security properties across providers.
Control what enters memory
Zep authenticates API requests and isolates data by project, user, and graph. Your application still knows which end user or source caused each write.
Apply these controls in your application:
- derive
user_idandgraph_idfrom authenticated application state; - keep each end user’s conversation data in that user’s graph;
- put shared reference data in a separate standalone graph;
- validate content before you send it to Zep; and
- expose specific write operations instead of giving the model an unrestricted Zep client.
Use Agent access policies to limit the actions and sources available to an agent API key. For Memory MCP Server deployments, configure write access and the account write switch.
Record source provenance with episode metadata
When you add text or JSON with graph.add, attach metadata that identifies its source and review state:
Zep associates the episode with the facts, entities, observations, and summaries derived from it. Zep then projects the episode metadata onto those artifacts. This episode metadata projection lets you trace a result to its source episodes.
Your application can use projected metadata to decide how it uses a result. For example, application code can require review_state=approved before it uses context for an action proposal. The application can also reject values from an external source for a sensitive tool argument.
Metadata on thread messages does not project onto derived graph artifacts. Use graph.add with episode metadata when you need source-based filtering or policy. Episode metadata is customer-writable, so derive security labels in application code instead of accepting them from the model.
Scope retrieval with Zep filters
For custom retrieval, pass episode_metadata_filters and other search_filters to graph.search. These filters select eligible episodes and derived artifacts during retrieval.
Agent access policies can also restrict reads by projected episode metadata. These policies apply to requests made with the scoped API key.
The default thread.get_user_context call does not accept caller-supplied metadata filters. It still honors access policies attached to the API key. If you need source filters without an access policy, use advanced context block construction or a framework context_builder that calls filtered graph.search.
Do not rely on client-side filtering of a broad top result set as your primary source control. Excluded sources can consume the result limit before your application filters them. This can hide eligible results that ranked below them. Server-side filters also prevent excluded content from being returned to the application.
Authorize actions outside the model
Zep access policies control Zep API operations. They do not authorize calls to your application’s other tools.
The model can propose an action. Application code must authorize the action from the current user, permissions, and system state. Resolve sensitive values from an authenticated system of record instead of copying them from memory.
Apply this check to actions that send data, change permissions, delete resources, execute code, or move money. Require confirmation when an action can cause loss, disclosure, or external communication.
Investigate and remove affected memory
Episode associations let you inspect which episodes contributed to a fact or entity. Use the episodes field on an edge, or use episode_uuids filters, to trace derived context. The episode metadata projection guide lists the available association queries.
To exclude content without immediate deletion, update its episode metadata and apply a matching search filter or access policy. To remove content, use Zep’s episode, thread, user, node, or edge deletion APIs.
Use API logs to inspect request status and access-policy decisions. Use audit logs for administrative activity. Log record identifiers and decisions in your application. Do not copy memory content, credentials, or sensitive tool arguments into logs.
Test your deployment
Test the attack paths that apply to your agent:
- Verify that source metadata appears on derived context.
- Verify that search filters or access policies exclude disallowed sources.
- Verify that retrieved instructions do not enter a privileged message.
- Verify that application authorization blocks sensitive actions based only on memory.
- Verify that metadata exclusion or deletion removes affected context from later retrieval.