direct
holds0.0%
worst case across M = 1, 3, 10, 50
effective reliability 0.20
No intermediary. The attacker injects at their own untrusted tier.
Step 1 of 4, Simulation
Proves the defense holds under a volumetric flood on an untrusted channel, and fails honestly when the attacker forges trust.
Attacker confined to an untrusted source such as scraped web content. The belief under attack was established by 4 trusted observations at reliability 0.90. These two controls scope the lanes below. The panels after them are fixed measurements and do not follow the selector.
Lane A
Attack success at M = 5 injections, channel trust 0.20, over 400 trials per cell.
Last write wins
100.0%
Majority vote
100.0%
Lines coincide at the right edge, so 1 end label is omitted rather than stacked. Use the legend, the crosshair or the table.
Read the two baselines separately. Majority vote reads 0.0% at M = 1 and only flips from M = 5, once the flood outnumbers the 4 trusted observations. Last write wins flips from M = 1.
Lane B
Attack success at M = 5 injections, channel trust 0.20, over 400 trials per cell.
Nous, provenance capped
0.0%
Nous, trust signal removed
100.0%
The ablation is the control. Strip the provenance signal and the same Bayesian update is flipped at 100.0%, so what is defending here is the channel trust, not the update rule.
Where the defense breaks
Lines coincide at the right edge, so 3 end labels are omitted rather than stacked. Use the legend, the crosshair or the table.
Below the breakpoint the flood size does not matter. Above it, volume buys the attacker the belief.
Laundering
0.0%
worst case across M = 1, 3, 10, 50
effective reliability 0.20
No intermediary. The attacker injects at their own untrusted tier.
100.0%
worst case across M = 1, 3, 10, 50
effective reliability 0.90
Laundered content inherits the intermediary's tier and forgets its untrusted origin. This is what a system with no taint tracking does.
0.0%
worst case across M = 1, 3, 10, 50
effective reliability 0.20
Laundered content is down-tiered to its untrusted origin. This is what a system that propagates provenance does.
Taint propagation is implemented in the engine, in nous/provenance.py, and measured end to end by benchmark/taint_engine_bench.py. The rows here remain the belief-layer simulation of the three policies. The engine-level result is reported separately.
Measured against the real engine, 100 trials per cell
Implemented in nous/provenance.py. Memory layer only. Provenance propagates through derivation chains inside the Nous pipeline. This is not general information-flow control, and content laundered outside the pipeline and presented with no chain is not detected.
Benign cost: 100.0% accuracy without chains, 100.0% with, and 200 of 200 posteriors identical. Benign episodes replayed with no chain and with a single-hop chain. Identical posteriors means turning taint tracking on reproduced today's numbers exactly, not merely scored the same.
Integration
The same attack run through the real wrapper over langchain_core.chat_history.BaseChatMessageHistory. Tools resolve to channels via configs/langchain_channels.yaml, channels to tiers via configs/trust_policy.yaml.
| Configuration | M = 1 | M = 3 | M = 10 | M = 50 | What it shows |
|---|---|---|---|---|---|
| unwrapped_raw | 100.0% | 100.0% | 100.0% | 100.0% | flippedNo firewall. A last-write-wins reader over the transcript, which is what an agent does when it reads its own history. |
| wrapped_direct | 0.0% | 0.0% | 0.0% | 0.0% | heldFirewall on. Poison arrives on a low-trust declared channel. |
| wrapped_laundered_naive | 100.0% | 100.0% | 100.0% | 100.0% | flippedPoison laundered through a trusted tool with a declared chain, taint ablated. The vulnerability. |
| wrapped_laundered_taint | 0.0% | 0.0% | 0.0% | 0.0% | heldIdentical call, taint honored. The defense. |
The raw transcript still contains the poison in 600 of 600 wrapped trials. Screening is an overlay. The raw write always proceeds, so the transcript still contains the poison in every wrapped trial. What changes is what the agent believes, not what it can read back.
Screening coverage
Coverage in the attack table is clean by construction, because structured mode shapes every write into a phrasing the rule-based extractor handles. The probes are the number an operator gets on an unconstrained transcript. Writes that yield no claims are counted as unscreened and listed, never silently passed. LLM extraction mode is the path to closing that gap.
Unscreened writes, recorded rather than passed over
Episodic memory
The attacker is query only and never writes to the store. Every poisoned trace is written by the trusted agent process at the agent's own tier. The only thing that distinguishes it is the session that induced it.
This is NOT a MINJA reproduction. MINJA uses optimized indication prompts and bridging steps against specific agent pipelines, and its published success rates are not comparable to anything measured here. The claim is exactly: a MINJA-shaped threat model, meaning a query-only attacker, agent-written traces, later retrieved as demonstrations, reduced to the retrieval layer and defended by session provenance.
| Enforcement | M = 1 | M = 3 | M = 10 | M = 50 | What it shows |
|---|---|---|---|---|---|
| no_enforcement | 100% / 100% | 100% / 100% | 100% / 100% | 100% / 100% | retrievedRelevance alone. The vulnerability. |
| write_channel_tiering | 100% / 100% | 100% / 100% | 100% / 100% | 100% / 100% | retrievedTiers the process that wrote the trace, which is the agent, which is trusted. Against a query-only attacker it admits everything. This row is the point: it is the naive design, measured failing. |
| session_hard_floor | 0% / 100% | 0% / 100% | 0% / 100% | 0% / 100% | held backTiers the interaction that induced the trace. The untrusted session is the chain origin, binds the minimum, and the entry is held back at retrieval. It is not deleted, it stays in the store with its chain. |
| session_soft_downweight | 100% / 100% | 100% / 100% | 100% / 100% | 100% / 100% | retrievedScales relevance by the origin tier. Reported wherever it landed. |
Each cell is attack success / benign utility. Both are shown because a defense that empties retrieval is not a defense.
Why soft downweight lands where it does
Scaling demotes the poison from mean rank 2.46 to 3.00, and it is still inside the top k in 100% of trials. Scaling a score reorders, it does not exclude. Only two benign traces share the target topic, so the remaining top k slots go to near-zero filler, and a highly relevant poison at a low tier still beats filler. A floor excludes. A weight does not.
What the defense costs
Lines coincide at the right edge, so 1 end label is omitted rather than stacked. Use the legend, the crosshair or the table.
reliability conflict: free down to tier 0.50, then falls to 53.8% at tier 0.20. The true value arrives from a few confident observations, the false value from many hedged ones.
stable noise: free down to tier 0.50, then falls to 8.8% at tier 0.20. The true value arrives from many confident observations, the false value from a few hedged ones that sometimes land last.