
Banc de test local pour l'empoisonnement de la mémoire d'un agent - observez une mémoire implantée orienter un agent, puis prouvez la réparation. Cibles synthétiques, agent déterministe scripté.
Votre agent s'en souvenait. Aurait-il dû ?
Un banc d'essai local pour l'empoisonnement de la mémoire des agents : la mémoire persistante comme surface d'attaque. Injectez dans la mémoire d'un agent des écritures non fiables contrôlées, observez une mémoire implantée modifier le comportement de l'agent, puis essayez de l'arrêter - et prouvez que la réparation a fonctionné.
Périmètre, clairement. Des cibles synthétiques, un agent déterministe scripté. Ce dépôt démontre la mécanique de l'empoisonnement de la mémoire, de la défense et de la réparation issue de recherches publiées, sur de vrais backends de mémoire. Il ne constitue pas une preuve concernant la sûreté d'un quelconque LLM réel, service de mémoire hébergé ou système de production. Ne pointez jamais quoi que ce soit ici vers un système qui ne vous appartient pas.
Les couches de mémoire des agents sont déployées rapidement, et les classes d'attaques qui les visent sont documentées dans la recherche (voir Sources). Ce dépôt est un petit espace local pour observer ces attaques se produire et tester un correctif. Les quatre premiers scénarios sont construits à partir de la taxonomie publiée ; A5 étend le périmètre à la fenêtre de classement elle-même, et A6 à une seconde décision pour laquelle le propriétaire n'a jamais écrit de politique :
| Scénario | Classe d'attaque | Mécanique testée |
|---|---|---|
| A1 - Injection directe | Contenu en forme d'instruction dans une écriture d'outil non fiable | La récence naïve laisse la « préférence » la plus récente l'emporter sur la politique du propriétaire |
| A2 - Blanchiment par résumé | La charge utile survit à la compaction | La summarisation extractive réétiquette l'écriture en summary ; l'étiquette paraît fiable, la véritable origine reste la source non fiable |
| A3 - Répétition | La même affirmation, trois fois | La fusion des doublons porte la confiance de l'affirmation au-dessus de la politique du propriétaire lui-même |
| A4 - Import blanchi | Le poison arrive pré-étiqueté summary depuis un autre système | Le filtrage limité aux écritures d'outils ne le voit jamais, et la véritable origine pré-import est irrécupérable ; le refus par défaut des affirmations non-propriétaire est ce qui l'arrête |
| A5 - Inondation de récupération | Dix écritures non fiables avec un fort recouvrement de tokens de requête | La politique du propriétaire est évincée de la fenêtre de rappel top-k de l'agent, si bien que même une faible affirmation par écriture l'emporte par éviction ; le filtrage met l'inondation en quarantaine, et la politique d'origine est le filet de sécurité |
| A6 - Greffe de préférence | Une écriture non fiable greffe une préférence sur un sujet que le propriétaire n'a jamais défini (le coursier) | Rien en forme d'instruction à filtrer, et aucune affirmation du propriétaire à contredire - le refus par défaut sur l'origine le bloque, mais la réparation limitée aux conflits seule ne peut pas le supprimer. Une fois que le propriétaire énonce une politique, la même passe de réparation supprime la greffe |
Chaque scénario s'exécute de trois manières contre des backends réels et neufs - sans mocks :
repair(), et la décision doit revenir à la ligne de base. A6 est l'exception délibérée : sans affirmation du propriétaire sur le sujet greffé, la réparation limitée aux conflits n'a rien à quoi s'ancrer et ne supprime rien - le scénario épingle cette limite et la remédiation (le propriétaire énonce une politique, la réparation supprime la greffe).Tableau de bord actuel, vérifié par la suite de tests : 6/6 attaques font basculer l'agent non défendu, 6/6 sont bloquées avec les défenses activées, 5/6 sont restaurées par la réparation seule - A6 documente la limite de la réparation et est restauré une fois que le propriétaire énonce une politique de coursier.
Les attaques et les défenses sont des propriétés du contrat de mémoire, non d'une implémentation particulière, c'est pourquoi l'ensemble du banc d'essai s'exécute contre trois backends :
ReferenceMemory - stockage en mémoire sans dépendance. Utile pour comprendre les mécanismes.ChromaMemory (mpolab/adapters.py) - un adaptateur au-dessus de ChromaDB, un véritable stockage vectoriel local avec son modèle d'embedding ONNX par défaut. Pas de serveur, pas de clé API, pas d'appels payants : pip install chromadb et les six mêmes attaques sont rejouées contre les embeddings et le rappel par plus proches voisins. Une précision : la première exécution de ChromaDB télécharge son modèle d'embedding ONNX par défaut (environ 80 Mo) depuis Internet ; après ce téléchargement unique, il fonctionne entièrement hors ligne.SQLiteVecMemory (mpolab/adapters.py) - un adaptateur au-dessus de sqlite-vec, rappel vectoriel par plus proches voisins au sein de SQLite (KNN exact, sans index) : pas de serveur, aucun téléchargement de modèle. Il génère les embeddings avec un sac de mots haché déterministe (bibliothèque standard uniquement), ce qui exerce donc une véritable plomberie de rappel vectoriel hors ligne ; ce n'est pas une mesure de la qualité des embeddings sémantiques. pip install sqlite-vec.tests/test_adapters.py exécute un contrat de conformité ainsi que chaque scénario enregistré contre chaque backend installé. L'ajout de votre propre adaptateur (mem0, Zep, Letta, pgvector - tout ce qui dispose de retain/recall) hérite de l'intégralité de la suite d'attaques.
Chaque entrée de mémoire porte deux champs de provenance : trust (l'étiquette de stockage rapportée par le backend, que la compaction et les imports peuvent réécrire) et origin (la source enregistrée au moment de l'écriture et jamais réécrite). Les défenses raisonnent sur origin. C'est dans cette séparation que le blanchiment par résumé vit et meurt. Une note d'honnêteté : l'origin d'une entrée importée n'est aussi honnête que le système qui l'a importée - la provenance antérieure à l'import est irrécupérable. C'est précisément pourquoi la politique défendue ne permet qu'aux affirmations d'origine owner de définir la politique (A4), plutôt que de faire confiance à une origine déclarée.
python3 scripts/build_demo.py # regenerates demo/index.html from real runs
open demo/index.html # no server, no dependencies
Rejouez les écritures mémoire, cliquez sur la carte que vous pensez être le poison, puis révélez : la décision inversée, l'exécution de défense et la passe de réparation. Les états liables en profondeur (#reveal=A2, #defense=A4) rendent tout résultat partageable sous forme d'URL.
Python 3.10+. La suite de référence n'a aucune dépendance :
python3 -m unittest discover -s tests -v
Pour inclure le backend ChromaDB :
Vérifié sur un checkout propre :
python3 -m venv .venv && source .venv/bin/activate
pip install '.[chroma]' # local checkout extra; pulls chromadb
pip install '.[sqlitevec]' # or the sqlite-vec backend; no model download
python3 -m unittest discover -s tests -v