Skip to content

Follow-up to #60: offline unit tests + behavioural-steering demo for memory poisoning - #62

Open
opitaru-sys wants to merge 2 commits into
GenAI-Security-Project:mainfrom
opitaru-sys:followup-memory-poisoning-tests
Open

Follow-up to #60: offline unit tests + behavioural-steering demo for memory poisoning#62
opitaru-sys wants to merge 2 commits into
GenAI-Security-Project:mainfrom
opitaru-sys:followup-memory-poisoning-tests

Conversation

@opitaru-sys

Copy link
Copy Markdown

Summary

Follow-up to #60, as agreed with @felipepenha on #22 ("I will leave #56 open until you come up with the follow up to #60").

This carries over the pieces of my #56 that add coverage to the merged sandbox without duplicating it or changing any of the merged code. It touches only new files plus small, additive edits (two Makefile targets, a pytest dev dep, and README lines).

What it adds

  1. Offline unit tests for the memory module - sandboxes/llm_memory_local/tests/test_memory.py
    Model-free tests that exercise the merged app/memory.py directly against a temporary SQLite file, so the Conversation Memory Poisoning mechanism is provable in CI with no Ollama, container, or network. They cover:

    • the write path: a remember that message is promoted to a fact, a benign message is not;
    • the cross-session leak: a fact planted by one session is returned to an unrelated session. The merged store is intentionally unscoped, so this asserts the leak; a session-scoped design would make the same test assert isolation, which is why it is framed as the scope boundary;
    • the injection sink: stored facts are rendered into a system preamble that labels attacker-supplied text as trusted;
    • reset_all clears the store.
      Run with make test-unit (mirrors the sandboxes/mcp_local convention: PYTHONPATH=. uv run pytest tests/, with pytest added to the dev group and [tool.pytest.ini_options] pythonpath = ["."]).
  2. Offline behavioural-steering demo - exploitation/memory_poisoning/steering_demo.py
    A deterministic companion to attack.py that drives the real memory code and isolates the behavioural half of the attack: a stand-in safety model refuses the malicious download link in the attacker's own turn, then serves the same link to a fresh victim session once it has been laundered through trusted memory. The only stand-in is a small guard_model policy, documented as such, so the demo is reproducible offline. Run with make steering-demo.

Notes for reviewers

Relates to #22. Builds on #60.

🤖 Generated with Claude Code

Follow-up to GenAI-Security-Project#60 (per maintainer request on GenAI-Security-Project#22), carrying over the pieces
of GenAI-Security-Project#56 that add to the merged sandbox without duplicating it:

- sandboxes/llm_memory_local/tests/test_memory.py: model-free unit tests for
  the merged memory module (write path, cross-session leak as the scope
  boundary, trusted-context injection sink, reset). Run offline via
  make test-unit, mirroring the mcp_local sandbox convention.
- exploitation/memory_poisoning/steering_demo.py: a deterministic, offline
  companion to attack.py that drives the real memory code and shows the
  assistant refuse the link in the attacker turn, then serve it to a fresh
  victim session once laundered through trusted memory. Run via
  make steering-demo.

Additive only: no change to the merged app, exploit, or threat model.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
@fasinet

fasinet commented Sep 7, 2026

Copy link
Copy Markdown
Contributor

@opitaru-sys this looks like a nice addition to the sandbox. Would love to see it extended.

@opitaru-sys

Copy link
Copy Markdown
Author

Thanks @fasinet, glad it fits. Building on your sandbox made this easy, the memory code was already clean to test against.

Two directions I'd be happy to take it, if they're useful:

  • swap the stand-in guard_model in the steering demo for a real local model, so the refuse-in-the-attacker-turn then serve-to-the-victim behaviour is shown against an actual safety-tuned model rather than a policy stub;
  • add a session-scoped store variant as a defensive reference, so the same isolation test that currently asserts the leak flips to asserting containment: attack and mitigation side by side.

Either would be a separate follow-up on top of this one, so #62 stays clean to review. Which would you rather see first?

@felipepenha

Copy link
Copy Markdown
Collaborator

@opitaru-sys I think I understand what has been implemented here, but I found the explanation a bit hard to follow.

I believe that including a flow diagram in the README file would help a lot. I suggest using mermaid flowcharts:

https://mermaid.ai/open-source/syntax/flowchart.html

Could you include it, in addition to the diagram already included by @fasinet?

Adds a mermaid flowchart for the refuse-then-serve steering mechanism next
to fasinet's Attack Strategy diagram, per review feedback on GenAI-Security-Project#62.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
@opitaru-sys

Copy link
Copy Markdown
Author

Thanks @felipepenha, that's fair, the steering half needed a picture. I've added a flowchart to exploitation/memory_poisoning/README.md, next to @fasinet's Attack Strategy diagram. It traces the same malicious link being refused in the attacker's own turn, then served to a fresh victim session once it has been laundered through the unscoped memory store. Pushed to this branch. Happy to add the same treatment to the sandbox README if that would help too.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants