Sections in this lesson
Memory & Context Poisoning
An untrusted instruction stored as memory can influence later runs after the original document is gone.
Reviewed: · Content owner: Interview Preparator
What you will learn
- Persistence changes the threat
- Reauthorize cached context
- Recover without reinfection
Before you start
The flow
- 1.Identify scope
- 2.Inspect evidence
- 3.Apply host control
- 4.Run negative cases
- 5.Compare benign behavior
- 6.Review remaining risks
Persistence changes the threat
An untrusted instruction stored as memory can influence later runs after the original document is gone. Record origin, approval, version and ownership separately from the text. Conversation history and durable execution state are different assets.
The fixture memoryRead checks tenant, user, conversation, trusted state and version. The trusted flag is host-authored; accepting the same flag from model output would erase the boundary.
Reauthorize cached context
Scope cache keys by tenant, user and corpus version, but do not stop there. Deletion, permission revocation and incident invalidation can happen while a key still matches; recheck the current source before reuse.
Test cross-user conversations, stale versions, untrusted records and revoked evidence. A checkpoint must not restore a capability that the current policy has removed.
Recover without reinfection
Quarantine suspect memory, revoke affected grants and reconstruct context from authorized sources. Retain sanitized incident evidence without copying secret-bearing prompts into ordinary logs.
This example is an in-memory policy fixture, not a production memory database or automated poison detector. Durable cleanup and migration require integration and their own recovery tests.
Try it
import { authorize, payloadDigest } from './policy.mjs';// Teaching host: no shell, network, or arbitrary-file tools are exposed.export class TeachingHost { constructor({ scope, resource, grant, maxCalls = 3, maxTokens = 100, deadline = 1000, epoch = 1 }) { if (!Number.isSafeInteger(maxCalls) || maxCalls < 1 || !Number.isSafeInteger(maxTokens) || maxTokens < 1 || !Number.isFinite(deadline)) throw new Error('Invalid budget'); this.scope = Object.freeze({ ...scope }); this.resource = structuredClone(resource); this.grant = grant ? structuredClone(grant) : null; this.maxCalls = maxCalls; this.maxTokens = maxTokens; this.deadline = deadline; this.epoch = epoch; this.calls = 0; this.tokens = 0; this.mutations = 0; this.receipts = new Map(); this.trace = []; } execute(call, callId, reservedTokens, now) { if (!Number.isSafeInteger(reservedTokens) || reservedTokens < 1 || !Number.isFinite(now) || !/^[a-z0-9-]{1,80}$/.test(callId)) throw new Error('Invalid operation'); if (now >= this.deadline) return this.record('TIMEOUT'); const permission = authorize(this.scope,call,this.resource,this.grant,now,this.epoch); if (permission !== 'ALLOW') return this.record(permission); const digest = payloadDigest(call); const cached = this.receipts.get(callId); if (cached) return this.record(cached.digest === digest ? 'REPLAY' : 'PAYLOAD_MISMATCH'); if (this.calls >= this.maxCalls || reservedTokens > this.maxTokens - this.tokens) return this.record('BUDGET_EXCEEDED'); this.calls++; this.tokens += reservedTokens; if (call.action === 'completePlan') this.mutations++; this.receipts.set(callId,{ digest }); return this.record('EXECUTED'); } record(status) { this.trace.push({ status, calls:this.calls, tokens:this.tokens, mutations:this.mutations }); return status; } stopAndRevoke() { this.epoch++; this.grant = null; }}// Cache keys include authorization scope and corpus version; cache values still// require current authorization at read time (revocation/deletion can change).export function cacheKey(scope, corpusVersion, question) { return JSON.stringify([scope.tenant,scope.user,corpusVersion,question]); }export function memoryRead(record, scope, version) { return record.tenant === scope.tenant && record.user === scope.user && record.conversation === scope.conversation && record.trusted === true && record.version === version ? record.text : null;}Run npm run examples:security and npm run eval:security. This source is a deterministic teaching fixture; it does not prove live model safety, NLI, production auth or OS/network isolation.
Interview questions
What boundary matters in Memory & Context Poisoning?
The fixture memoryRead checks tenant, user, conversation, trusted state and version. The trusted flag is host-authored; accepting the same flag from model output would erase the boundary.
What does the executable fixture demonstrate?
Test cross-user conversations, stale versions, untrusted records and revoked evidence. A checkpoint must not restore a capability that the current policy has removed.
What remains a production integration responsibility?
This example is an in-memory policy fixture, not a production memory database or automated poison detector. Durable cleanup and migration require integration and their own recovery tests.
Practice
Extend one negative scenario and its authorized positive counterexample for Memory & Context Poisoning.
Check your solution
- State the trusted input and effect boundary.
- Assert the expected status and observed effect.
- Report coverage and limitations without claiming production readiness.
Official references
Continue learning
Category sequence
AI Security
Lesson 10 / 14
Was this lesson useful?
Change notes: 2026-10-31: security/reliability curriculum and source-bound fixture.