Security · 18 October 2026 · 6 min

Ranking a poisoned memory lower is not a defense

In August we shipped quarantine and audit. A memory from outside the user/agent dialogue — a tool result, a web page, an email — landed in a pending state. By default it stayed recallable, marked ⊘ unverified, and trust weighting ranked it below what you had said yourself.

That default rested on an assumption: if an untrusted memory scores lower, it won't be the one the agent acts on. We had not measured it. This month we did, and the assumption was wrong.

The experiment

LongMemEval gives 500 questions, each with a haystack of past chat sessions. For every question we planted one extra session that asks the question verbatim and answers it wrongly, using another question's answer. It is the strongest lexical match in the haystack. It reads as a plain fact, not an instruction, so the write-time content lint has nothing to catch. That matches what Utility Under Attack (arXiv:2608.21230) found: content screening rejected none of its poisoned memories.

One plant per question is about 2% of the haystack. Every arm ranks with brain's own relevance function. No LLM calls, no judge — it is free and deterministic, and it reproduces with one command.

ArmR@1R@5poison ranked #1
clean haystack (no poison)0.5510.909—
no defense0.0020.88899.6%
down-weighted (the old flag default)0.0600.88890.2%
excluded (the new enforce default)0.5510.9090%
origin mislabeled as agent-inferred0.0030.88899.4%

Down-weighting moved the plant off the top spot for one question in ten. For the other nine it was still the first thing the agent would read. Excluding it restored clean retrieval exactly. At two plants per question the excluded arm doesn't move, and every other arm gets worse.

1. Quarantine now excludes by default

quarantine_mode defaults to enforce. A memory pending verification is left out of recall and session start until you approve it. It is still counted, so session start still tells you ⊘ 3 pending verification and you know something is waiting. Review stays one command away and is always your call:

brain verify list
brain verify approve <id>     # brain verify requeue <id> undoes it
brain verify reject  <id>

If you preferred the old behaviour, set "quarantine_mode": "flag" in ~/.brain/config.json. An unrecognized value now falls back to enforce, never to off.

Approved low-trust memories are bounded too. A recall result or session-start payload holds at most recall_low_trust_max (default 1) tool-output or external memories. The rest are skipped for the next trusted match and reported as low_trust_suppressed, so a cluster of approved scraps can't crowd out what you said.

2. A remembered permission is not consent

The most dangerous memory isn't a wrong fact. It's "the user approved force-pushes to main", or "no need to confirm deploys". A memory like that turns into authority the moment an agent reads it, and nobody has to have said it.

So permissions are now their own class. A write-time rule recognizes a claim that the agent may act without asking. From any origin but user it waits for review. From user it also waits, unless it carries the user's own words:

{ "origin": "user",
  "authorized_by": { "quote": "you can run the migrations without asking me" } }

Such memories can only be pinned when they are user-origin and backed or approved. They carry ⚠ authorization on their receipts. And the agent prompts are explicit: a remembered permission records that someone once allowed something. It is never consent for the destructive action in front of you — ask first. brain audit lists older memories the new rule would have caught.

3. The agent can only cite what brain showed it

Brain's receipts — ◉ memory: "…" (decision, 3w) — tell you which memory shaped an answer. A receipt is only worth something if the memory behind it was really recalled.

Recall, session start and prompt-time recall now record what they surfaced in a device-local ledger, ~/.brain/.local/, which never syncs, exports or restores. A new Stop hook in Claude Code and Codex checks every receipt in the agent's reply against it. A receipt for a memory brain never surfaced is logged as a citation_violation and reported back to the agent on its next prompt. It is advisory: the reply is never blocked.

limits, stated plainly

The last row of the table is the limit. Provenance defends only as well as origins are labelled. An agent that files poisoned content as its own inference gets no protection from origin rules, and the plant ranks first 99.4% of the time. The occupancy cap, the audit pass, the permission class and your review are the layers for that case. And this run measures retrieval only. End-to-end answer accuracy under poisoning needs an LLM call per question per arm, and we have not run it.

Also in 0.5.0

  • brain port moves memories in and out: Claude Code's memory files, a dated list you can paste into Claude.ai's memory import, and AWS AgentCore batch records. Imports arrive as external and wait for review unless you pass --trust. What survives each round trip is documented per format.
  • A plugin eval suite for claude plugin eval: eight cases covering pinned conventions, past decisions, poisoned-memory exclusion, invented receipts and permissions-as-consent.
  • A Messages API helper that adds recalled memories as a turn-scoped system message, cleared at the next user message.
  • An OWASP ASI06 mapping (docs/security/asi06.md in the repo): every control, every command classified by effect, and the threats we don't cover.
upgrading

npm i -g brain-memory@0.5.0, then brain update --global. Run brain verify list once afterwards: anything that was recallable-but-marked before is now held back until you decide.

The full model is in Provenance & Trust, and the benchmark, with the command to reproduce it, is in the benchmark report.