agent-memory-guard 0.3.2__tar.gz → 0.3.3__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (95) hide show
  1. {agent_memory_guard-0.3.2/src/agent_memory_guard.egg-info → agent_memory_guard-0.3.3}/PKG-INFO +83 -15
  2. {agent_memory_guard-0.3.2 → agent_memory_guard-0.3.3}/README.md +74 -14
  3. {agent_memory_guard-0.3.2 → agent_memory_guard-0.3.3}/pyproject.toml +8 -2
  4. {agent_memory_guard-0.3.2 → agent_memory_guard-0.3.3}/src/agent_memory_guard/__init__.py +1 -1
  5. agent_memory_guard-0.3.3/src/agent_memory_guard/bench/__init__.py +50 -0
  6. agent_memory_guard-0.3.3/src/agent_memory_guard/bench/adapter.py +113 -0
  7. agent_memory_guard-0.3.3/src/agent_memory_guard/bench/adapters/__init__.py +22 -0
  8. agent_memory_guard-0.3.3/src/agent_memory_guard/bench/adapters/baseline.py +164 -0
  9. agent_memory_guard-0.3.3/src/agent_memory_guard/bench/adapters/third_party.py +111 -0
  10. agent_memory_guard-0.3.3/src/agent_memory_guard/bench/cli.py +150 -0
  11. agent_memory_guard-0.3.3/src/agent_memory_guard/bench/harness.py +218 -0
  12. agent_memory_guard-0.3.3/src/agent_memory_guard/bench/report.py +167 -0
  13. agent_memory_guard-0.3.3/src/agent_memory_guard/bench/scenarios.py +347 -0
  14. agent_memory_guard-0.3.3/src/agent_memory_guard/bench/scoring.py +211 -0
  15. {agent_memory_guard-0.3.2 → agent_memory_guard-0.3.3}/src/agent_memory_guard/cli.py +21 -3
  16. {agent_memory_guard-0.3.2 → agent_memory_guard-0.3.3}/src/agent_memory_guard/detectors/excessive_autonomy.py +1 -13
  17. {agent_memory_guard-0.3.2 → agent_memory_guard-0.3.3}/src/agent_memory_guard/detectors/injection.py +40 -3
  18. {agent_memory_guard-0.3.2 → agent_memory_guard-0.3.3}/src/agent_memory_guard/detectors/ml_injection.py +1 -13
  19. {agent_memory_guard-0.3.2 → agent_memory_guard-0.3.3}/src/agent_memory_guard/detectors/privilege_escalation.py +1 -13
  20. {agent_memory_guard-0.3.2 → agent_memory_guard-0.3.3}/src/agent_memory_guard/detectors/self_reinforcement.py +58 -12
  21. {agent_memory_guard-0.3.2 → agent_memory_guard-0.3.3}/src/agent_memory_guard/detectors/tool_abuse.py +1 -13
  22. {agent_memory_guard-0.3.2 → agent_memory_guard-0.3.3}/src/agent_memory_guard/events.py +8 -2
  23. {agent_memory_guard-0.3.2 → agent_memory_guard-0.3.3}/src/agent_memory_guard/guard.py +57 -15
  24. agent_memory_guard-0.3.3/src/agent_memory_guard/integrations/__init__.py +29 -0
  25. agent_memory_guard-0.3.3/src/agent_memory_guard/integrations/agno.py +115 -0
  26. agent_memory_guard-0.3.3/src/agent_memory_guard/integrations/autogen.py +156 -0
  27. {agent_memory_guard-0.3.2 → agent_memory_guard-0.3.3}/src/agent_memory_guard/integrations/llamaindex.py +33 -9
  28. agent_memory_guard-0.3.3/src/agent_memory_guard/integrations/openai_agents.py +113 -0
  29. {agent_memory_guard-0.3.2 → agent_memory_guard-0.3.3}/src/agent_memory_guard/policies/policy.py +11 -0
  30. {agent_memory_guard-0.3.2 → agent_memory_guard-0.3.3}/src/agent_memory_guard/scanner/__init__.py +19 -1
  31. {agent_memory_guard-0.3.2 → agent_memory_guard-0.3.3}/src/agent_memory_guard/server.py +39 -19
  32. {agent_memory_guard-0.3.2 → agent_memory_guard-0.3.3}/src/agent_memory_guard/storage/snapshots.py +4 -0
  33. {agent_memory_guard-0.3.2 → agent_memory_guard-0.3.3/src/agent_memory_guard.egg-info}/PKG-INFO +83 -15
  34. {agent_memory_guard-0.3.2 → agent_memory_guard-0.3.3}/src/agent_memory_guard.egg-info/SOURCES.txt +29 -1
  35. {agent_memory_guard-0.3.2 → agent_memory_guard-0.3.3}/src/agent_memory_guard.egg-info/entry_points.txt +1 -0
  36. {agent_memory_guard-0.3.2 → agent_memory_guard-0.3.3}/src/agent_memory_guard.egg-info/requires.txt +10 -0
  37. agent_memory_guard-0.3.3/tests/test_action_no_expression_in_run.py +86 -0
  38. agent_memory_guard-0.3.3/tests/test_agno_integration.py +71 -0
  39. agent_memory_guard-0.3.3/tests/test_autogen_openai_agents.py +160 -0
  40. agent_memory_guard-0.3.3/tests/test_cli.py +38 -0
  41. agent_memory_guard-0.3.3/tests/test_cli_check_and_scan.py +89 -0
  42. agent_memory_guard-0.3.3/tests/test_demo.py +16 -0
  43. agent_memory_guard-0.3.3/tests/test_detector_error_event.py +57 -0
  44. agent_memory_guard-0.3.3/tests/test_immutable_glob.py +47 -0
  45. agent_memory_guard-0.3.3/tests/test_injection_system_override.py +37 -0
  46. agent_memory_guard-0.3.3/tests/test_integration_llamaindex.py +59 -0
  47. agent_memory_guard-0.3.3/tests/test_openai_agents_example.py +43 -0
  48. {agent_memory_guard-0.3.2 → agent_memory_guard-0.3.3}/tests/test_self_reinforcement.py +9 -9
  49. agent_memory_guard-0.3.3/tests/test_self_reinforcement_trust.py +216 -0
  50. agent_memory_guard-0.3.3/tests/test_server_blocked_responses.py +77 -0
  51. agent_memory_guard-0.3.3/tests/test_stringify_depth.py +65 -0
  52. agent_memory_guard-0.3.3/tests/test_uninspectable_nesting.py +78 -0
  53. agent_memory_guard-0.3.3/tests/test_workflow_security.py +41 -0
  54. agent_memory_guard-0.3.2/src/agent_memory_guard/integrations/__init__.py +0 -3
  55. agent_memory_guard-0.3.2/tests/test_demo.py +0 -10
  56. {agent_memory_guard-0.3.2 → agent_memory_guard-0.3.3}/AUTHORS +0 -0
  57. {agent_memory_guard-0.3.2 → agent_memory_guard-0.3.3}/LICENSE.md +0 -0
  58. {agent_memory_guard-0.3.2 → agent_memory_guard-0.3.3}/setup.cfg +0 -0
  59. {agent_memory_guard-0.3.2 → agent_memory_guard-0.3.3}/src/agent_memory_guard/classification.py +0 -0
  60. {agent_memory_guard-0.3.2 → agent_memory_guard-0.3.3}/src/agent_memory_guard/detectors/__init__.py +0 -0
  61. {agent_memory_guard-0.3.2 → agent_memory_guard-0.3.3}/src/agent_memory_guard/detectors/anomaly.py +0 -0
  62. {agent_memory_guard-0.3.2 → agent_memory_guard-0.3.3}/src/agent_memory_guard/detectors/base.py +0 -0
  63. {agent_memory_guard-0.3.2 → agent_memory_guard-0.3.3}/src/agent_memory_guard/detectors/cross_task.py +0 -0
  64. {agent_memory_guard-0.3.2 → agent_memory_guard-0.3.3}/src/agent_memory_guard/detectors/leakage.py +0 -0
  65. {agent_memory_guard-0.3.2 → agent_memory_guard-0.3.3}/src/agent_memory_guard/detectors/memory_persistence_injection.py +0 -0
  66. {agent_memory_guard-0.3.2 → agent_memory_guard-0.3.3}/src/agent_memory_guard/detectors/protected_keys.py +0 -0
  67. {agent_memory_guard-0.3.2 → agent_memory_guard-0.3.3}/src/agent_memory_guard/exceptions.py +0 -0
  68. {agent_memory_guard-0.3.2 → agent_memory_guard-0.3.3}/src/agent_memory_guard/integrations/crewai.py +0 -0
  69. {agent_memory_guard-0.3.2 → agent_memory_guard-0.3.3}/src/agent_memory_guard/integrations/langchain.py +0 -0
  70. {agent_memory_guard-0.3.2 → agent_memory_guard-0.3.3}/src/agent_memory_guard/integrity.py +0 -0
  71. {agent_memory_guard-0.3.2 → agent_memory_guard-0.3.3}/src/agent_memory_guard/metrics.py +0 -0
  72. {agent_memory_guard-0.3.2 → agent_memory_guard-0.3.3}/src/agent_memory_guard/middleware.py +0 -0
  73. {agent_memory_guard-0.3.2 → agent_memory_guard-0.3.3}/src/agent_memory_guard/policies/__init__.py +0 -0
  74. {agent_memory_guard-0.3.2 → agent_memory_guard-0.3.3}/src/agent_memory_guard/py.typed +0 -0
  75. {agent_memory_guard-0.3.2 → agent_memory_guard-0.3.3}/src/agent_memory_guard/rag.py +0 -0
  76. {agent_memory_guard-0.3.2 → agent_memory_guard-0.3.3}/src/agent_memory_guard/scan.py +0 -0
  77. {agent_memory_guard-0.3.2 → agent_memory_guard-0.3.3}/src/agent_memory_guard/storage/__init__.py +0 -0
  78. {agent_memory_guard-0.3.2 → agent_memory_guard-0.3.3}/src/agent_memory_guard/storage/memory_store.py +0 -0
  79. {agent_memory_guard-0.3.2 → agent_memory_guard-0.3.3}/src/agent_memory_guard/storage/redis_store.py +0 -0
  80. {agent_memory_guard-0.3.2 → agent_memory_guard-0.3.3}/src/agent_memory_guard/streaming.py +0 -0
  81. {agent_memory_guard-0.3.2 → agent_memory_guard-0.3.3}/src/agent_memory_guard/tools.py +0 -0
  82. {agent_memory_guard-0.3.2 → agent_memory_guard-0.3.3}/src/agent_memory_guard.egg-info/dependency_links.txt +0 -0
  83. {agent_memory_guard-0.3.2 → agent_memory_guard-0.3.3}/src/agent_memory_guard.egg-info/top_level.txt +0 -0
  84. {agent_memory_guard-0.3.2 → agent_memory_guard-0.3.3}/tests/test_classification.py +0 -0
  85. {agent_memory_guard-0.3.2 → agent_memory_guard-0.3.3}/tests/test_detectors.py +0 -0
  86. {agent_memory_guard-0.3.2 → agent_memory_guard-0.3.3}/tests/test_guard.py +0 -0
  87. {agent_memory_guard-0.3.2 → agent_memory_guard-0.3.3}/tests/test_integrity.py +0 -0
  88. {agent_memory_guard-0.3.2 → agent_memory_guard-0.3.3}/tests/test_memory_persistence_injection.py +0 -0
  89. {agent_memory_guard-0.3.2 → agent_memory_guard-0.3.3}/tests/test_new_detectors.py +0 -0
  90. {agent_memory_guard-0.3.2 → agent_memory_guard-0.3.3}/tests/test_policy.py +0 -0
  91. {agent_memory_guard-0.3.2 → agent_memory_guard-0.3.3}/tests/test_policy_tiered.py +0 -0
  92. {agent_memory_guard-0.3.2 → agent_memory_guard-0.3.3}/tests/test_public_scan_api.py +0 -0
  93. {agent_memory_guard-0.3.2 → agent_memory_guard-0.3.3}/tests/test_redis_store.py +0 -0
  94. {agent_memory_guard-0.3.2 → agent_memory_guard-0.3.3}/tests/test_scanner_file_discovery.py +0 -0
  95. {agent_memory_guard-0.3.2 → agent_memory_guard-0.3.3}/tests/test_snapshots.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: agent-memory-guard
3
- Version: 0.3.2
3
+ Version: 0.3.3
4
4
  Summary: Runtime defense layer that protects AI agent memory from poisoning, tool abuse, privilege escalation, and excessive autonomy (OWASP).
5
5
  Author: Anshul Rajkumar
6
6
  Author-email: Vaishnavi Gudur <gudur.vaishnavi@gmail.com>
@@ -42,6 +42,10 @@ Provides-Extra: crewai
42
42
  Requires-Dist: crewai>=0.1; extra == "crewai"
43
43
  Provides-Extra: llamaindex
44
44
  Requires-Dist: llama-index-core>=0.10; extra == "llamaindex"
45
+ Provides-Extra: autogen
46
+ Requires-Dist: pyautogen>=0.2; extra == "autogen"
47
+ Provides-Extra: openai-agents
48
+ Requires-Dist: openai-agents>=0.1; extra == "openai-agents"
45
49
  Provides-Extra: all
46
50
  Requires-Dist: fastapi>=0.100; extra == "all"
47
51
  Requires-Dist: uvicorn[standard]>=0.20; extra == "all"
@@ -49,6 +53,8 @@ Requires-Dist: pydantic>=2.0; extra == "all"
49
53
  Requires-Dist: transformers>=4.30; extra == "all"
50
54
  Requires-Dist: torch>=2.0; extra == "all"
51
55
  Requires-Dist: langchain-core>=0.1; extra == "all"
56
+ Requires-Dist: pyautogen>=0.2; extra == "all"
57
+ Requires-Dist: openai-agents>=0.1; extra == "all"
52
58
  Provides-Extra: redis
53
59
  Requires-Dist: redis[hiredis]>=5.0.0; extra == "redis"
54
60
  Provides-Extra: test
@@ -56,6 +62,8 @@ Requires-Dist: pytest>=7.0; extra == "test"
56
62
  Requires-Dist: coverage>=7.0; extra == "test"
57
63
  Requires-Dist: httpx>=0.24; extra == "test"
58
64
  Requires-Dist: fakeredis>=2.0.0; extra == "test"
65
+ Requires-Dist: fastapi>=0.100; extra == "test"
66
+ Requires-Dist: pydantic>=2.0; extra == "test"
59
67
  Provides-Extra: dev
60
68
  Requires-Dist: ruff>=0.4; extra == "dev"
61
69
  Requires-Dist: mypy>=1.0; extra == "dev"
@@ -81,7 +89,7 @@ Dynamic: license-file
81
89
 
82
90
  <div align="center">
83
91
 
84
- ### 📦 9,834 PyPI downloads · 11,497 repository clones
92
+ ### 📦 14,721 PyPI downloads · 14,026 repository clones
85
93
 
86
94
  [![agent-memory-guard on PyPI](https://pepy.tech/badge/agent-memory-guard)](https://pepy.tech/project/agent-memory-guard) [![langchain-agent-memory-guard on PyPI](https://pepy.tech/badge/langchain-agent-memory-guard)](https://pepy.tech/project/langchain-agent-memory-guard) [![GitHub Clones](https://img.shields.io/badge/dynamic/json?color=success&label=Clones&query=count&url=https://gist.githubusercontent.com/vgudur-dev/c04e12f68c363625faf12faaf03a03ca/raw/clone.json&logo=github)](https://github.com/OWASP/www-project-agent-memory-guard) [![Unique Cloners](https://img.shields.io/badge/dynamic/json?color=success&label=Unique%20Cloners&query=uniques&url=https://gist.githubusercontent.com/vgudur-dev/c04e12f68c363625faf12faaf03a03ca/raw/clone.json&logo=github)](https://github.com/OWASP/www-project-agent-memory-guard/graphs/traffic)
87
95
 
@@ -183,6 +191,41 @@ Tested against 55 real-world attack payloads across 4 threat categories:
183
191
  python benchmarks/security_benchmark.py # reproduce locally
184
192
  ```
185
193
 
194
+ ## Agent Memory Security Benchmark (AMSB)
195
+
196
+ The numbers above measure AMG's own detectors on single writes. The **Agent
197
+ Memory Security Benchmark** is a separate, framework-neutral scoreboard that
198
+ grades **any** memory system on the full poisoning lifecycle — a payload is
199
+ planted, **survives a context reset**, and is read back on a later turn. Any
200
+ system that implements a three-method adapter (`remember` / `recall` /
201
+ `context_reset`) gets a 0–100 resilience score and an SSL-Labs-style letter
202
+ grade, with hard grade ceilings for catastrophic breaches.
203
+
204
+ AMG authors the benchmark **and submits itself for grading**, measured through
205
+ the identical adapter and corpus as every other entry. The results are not
206
+ curated to flatter it:
207
+
208
+ | System | Grade | Score | Notes |
209
+ |--------|-------|-------|-------|
210
+ | agent-memory-guard (hardened) | **A** | 93.9 | declared protected keys + persistence detector |
211
+ | agent-memory-guard (strict preset) | **D** | 71.4 | `Policy.strict()` — declared protected keys, no persistence detector |
212
+ | unguarded-dict | **F** | 0.0 | the resilience floor |
213
+
214
+ The `strict` preset scores **D**: `Policy.strict()` now declares protected keys
215
+ (fixed in 0.3.2, [#90](https://github.com/OWASP/www-project-agent-memory-guard/pull/90))
216
+ so it defends the identity-escalation attacks, but it
217
+ still loads no persistence detector — the delayed-activation attacks survive a
218
+ context reset, and two critical breaches cap the grade at D. A real, honest
219
+ finding about the documented quickstart config, surfaced by AMG's own benchmark:
220
+ the benchmark independently confirms the 0.3.2 fix and still flags the residual
221
+ persistence gap. See
222
+ [`benchmarks/memory-systems/`](benchmarks/memory-systems/) for the full
223
+ leaderboard, methodology, and how to grade a new system.
224
+
225
+ ```bash
226
+ amg-bench --out benchmarks/memory-systems # reproduce; grade mem0/Letta/Zep with --systems
227
+ ```
228
+
186
229
  ## What it does
187
230
 
188
231
  - **Integrity** — SHA-256 baselines flag out-of-band tampering with immutable keys.
@@ -232,14 +275,29 @@ Optional: pass `policy=Policy.strict()` or `on_violation="warn"|"strip"|"block"`
232
275
 
233
276
  ### OpenAI Agents SDK
234
277
 
278
+ Screen tool outputs before they enter session memory. Runnable example:
279
+ [`examples/openai_agents_memory_guard.py`](examples/openai_agents_memory_guard.py)
280
+ (HITL queue on block). Drop-in SDK adapters (`GuardedAgentContext`, `GuardedToolOutput`,
281
+ `GuardedHandoff`) ship in `agent_memory_guard.integrations.openai_agents`.
282
+
235
283
  ```python
236
- from agent_memory_guard import MemoryGuard, Policy
284
+ from agent_memory_guard import MemoryGuard, Policy, PolicyViolation
285
+ from agent_memory_guard.events import SourceClass
237
286
  from agent_memory_guard.storage import InMemoryStore
238
287
 
239
288
  guard = MemoryGuard(InMemoryStore(), policy=Policy.strict())
240
289
 
241
- def remember(key: str, value: str) -> None:
242
- guard.write(key, value, source="openai-agent")
290
+ def remember_tool_output(tool_name: str, output: str) -> bool:
291
+ try:
292
+ guard.write(
293
+ f"openai_agents.tool.{tool_name}",
294
+ output,
295
+ source="openai_agents_tool",
296
+ source_class=SourceClass.EXTERNAL_TOOL,
297
+ )
298
+ except PolicyViolation:
299
+ return False # enqueue for HITL / eval — do not poison session memory
300
+ return True
243
301
 
244
302
  def recall(key: str) -> str | None:
245
303
  return guard.read(key, sink="openai-agent")
@@ -248,49 +306,55 @@ def recall(key: str) -> str | None:
248
306
  ### AutoGen
249
307
 
250
308
  ```python
251
- from agent_memory_guard import MemoryGuard, Policy, PolicyViolation
309
+ from agent_memory_guard import Action, MemoryGuard, Policy, PolicyViolation
252
310
 
253
311
  guard = MemoryGuard(policy=Policy.strict())
254
312
 
255
313
  def guarded_append(history: list[dict], message: dict) -> None:
314
+ key = f"autogen.msg.{len(history)}"
256
315
  try:
257
- guard.write(f"autogen.msg.{len(history)}", message["content"],
258
- source=message.get("role", "agent"))
316
+ action = guard.write(key, message["content"], source=message.get("role", "agent"))
259
317
  except PolicyViolation as exc:
260
318
  print("blocked:", exc)
261
319
  return
262
- history.append(message)
320
+ if action == Action.QUARANTINE:
321
+ return # held in guard.quarantine for review
322
+ history.append({**message, "content": guard.read(key)}) # redacted if needed
263
323
  ```
264
324
 
265
325
  ### mem0
266
326
 
267
327
  ```python
268
- from agent_memory_guard import MemoryGuard, Policy, PolicyViolation
328
+ from agent_memory_guard import Action, MemoryGuard, Policy, PolicyViolation
269
329
 
270
330
  guard = MemoryGuard(policy=Policy.strict())
271
331
 
272
332
  def safe_add(mem0_client, *, user_id: str, content: str, key: str) -> bool:
273
333
  try:
274
- guard.write(key, content, source="mem0")
334
+ action = guard.write(key, content, source="mem0")
275
335
  except PolicyViolation:
276
336
  return False
277
- mem0_client.add(content, user_id=user_id)
337
+ if action == Action.QUARANTINE:
338
+ return False # held in guard.quarantine for review
339
+ mem0_client.add(guard.read(key), user_id=user_id) # redacted if needed
278
340
  return True
279
341
  ```
280
342
 
281
343
  ### CrewAI
282
344
 
283
345
  ```python
284
- from agent_memory_guard import MemoryGuard, Policy, PolicyViolation
346
+ from agent_memory_guard import Action, MemoryGuard, Policy, PolicyViolation
285
347
 
286
348
  guard = MemoryGuard(policy=Policy.strict())
287
349
 
288
350
  def guarded_memory_callback(key: str, value: str, agent_name: str) -> str:
289
351
  try:
290
- guard.write(key, value, source=f"crewai.{agent_name}")
352
+ action = guard.write(key, value, source=f"crewai.{agent_name}")
291
353
  except PolicyViolation as exc:
292
354
  return f"[BLOCKED] {exc}"
293
- return value
355
+ if action == Action.QUARANTINE:
356
+ return "[QUARANTINED] held for review"
357
+ return guard.read(key) # redacted if needed
294
358
  ```
295
359
 
296
360
  ## YAML policy
@@ -414,6 +478,10 @@ See [AUTHORS](AUTHORS) for details.
414
478
  - Referenced in the MITRE ATLAS "Memory Hardening" mitigation as an open-source implementation of memory-hardening controls.
415
479
  - Featured by Help Net Security, "OWASP Agent Memory Guard: Stop AI agents from being weaponized through their own memory" (June 2026).
416
480
 
481
+ These mentions are descriptive, not endorsements. OWASP Incubator status is not a certification,
482
+ and MITRE has not approved or endorsed this project; the ATLAS Memory Hardening entry lists it
483
+ among example tools.
484
+
417
485
  ## How to cite
418
486
 
419
487
  Use GitHub's "Cite this repository" button (powered by [CITATION.cff](CITATION.cff)), or:
@@ -10,7 +10,7 @@
10
10
 
11
11
  <div align="center">
12
12
 
13
- ### 📦 9,834 PyPI downloads · 11,497 repository clones
13
+ ### 📦 14,721 PyPI downloads · 14,026 repository clones
14
14
 
15
15
  [![agent-memory-guard on PyPI](https://pepy.tech/badge/agent-memory-guard)](https://pepy.tech/project/agent-memory-guard) [![langchain-agent-memory-guard on PyPI](https://pepy.tech/badge/langchain-agent-memory-guard)](https://pepy.tech/project/langchain-agent-memory-guard) [![GitHub Clones](https://img.shields.io/badge/dynamic/json?color=success&label=Clones&query=count&url=https://gist.githubusercontent.com/vgudur-dev/c04e12f68c363625faf12faaf03a03ca/raw/clone.json&logo=github)](https://github.com/OWASP/www-project-agent-memory-guard) [![Unique Cloners](https://img.shields.io/badge/dynamic/json?color=success&label=Unique%20Cloners&query=uniques&url=https://gist.githubusercontent.com/vgudur-dev/c04e12f68c363625faf12faaf03a03ca/raw/clone.json&logo=github)](https://github.com/OWASP/www-project-agent-memory-guard/graphs/traffic)
16
16
 
@@ -112,6 +112,41 @@ Tested against 55 real-world attack payloads across 4 threat categories:
112
112
  python benchmarks/security_benchmark.py # reproduce locally
113
113
  ```
114
114
 
115
+ ## Agent Memory Security Benchmark (AMSB)
116
+
117
+ The numbers above measure AMG's own detectors on single writes. The **Agent
118
+ Memory Security Benchmark** is a separate, framework-neutral scoreboard that
119
+ grades **any** memory system on the full poisoning lifecycle — a payload is
120
+ planted, **survives a context reset**, and is read back on a later turn. Any
121
+ system that implements a three-method adapter (`remember` / `recall` /
122
+ `context_reset`) gets a 0–100 resilience score and an SSL-Labs-style letter
123
+ grade, with hard grade ceilings for catastrophic breaches.
124
+
125
+ AMG authors the benchmark **and submits itself for grading**, measured through
126
+ the identical adapter and corpus as every other entry. The results are not
127
+ curated to flatter it:
128
+
129
+ | System | Grade | Score | Notes |
130
+ |--------|-------|-------|-------|
131
+ | agent-memory-guard (hardened) | **A** | 93.9 | declared protected keys + persistence detector |
132
+ | agent-memory-guard (strict preset) | **D** | 71.4 | `Policy.strict()` — declared protected keys, no persistence detector |
133
+ | unguarded-dict | **F** | 0.0 | the resilience floor |
134
+
135
+ The `strict` preset scores **D**: `Policy.strict()` now declares protected keys
136
+ (fixed in 0.3.2, [#90](https://github.com/OWASP/www-project-agent-memory-guard/pull/90))
137
+ so it defends the identity-escalation attacks, but it
138
+ still loads no persistence detector — the delayed-activation attacks survive a
139
+ context reset, and two critical breaches cap the grade at D. A real, honest
140
+ finding about the documented quickstart config, surfaced by AMG's own benchmark:
141
+ the benchmark independently confirms the 0.3.2 fix and still flags the residual
142
+ persistence gap. See
143
+ [`benchmarks/memory-systems/`](benchmarks/memory-systems/) for the full
144
+ leaderboard, methodology, and how to grade a new system.
145
+
146
+ ```bash
147
+ amg-bench --out benchmarks/memory-systems # reproduce; grade mem0/Letta/Zep with --systems
148
+ ```
149
+
115
150
  ## What it does
116
151
 
117
152
  - **Integrity** — SHA-256 baselines flag out-of-band tampering with immutable keys.
@@ -161,14 +196,29 @@ Optional: pass `policy=Policy.strict()` or `on_violation="warn"|"strip"|"block"`
161
196
 
162
197
  ### OpenAI Agents SDK
163
198
 
199
+ Screen tool outputs before they enter session memory. Runnable example:
200
+ [`examples/openai_agents_memory_guard.py`](examples/openai_agents_memory_guard.py)
201
+ (HITL queue on block). Drop-in SDK adapters (`GuardedAgentContext`, `GuardedToolOutput`,
202
+ `GuardedHandoff`) ship in `agent_memory_guard.integrations.openai_agents`.
203
+
164
204
  ```python
165
- from agent_memory_guard import MemoryGuard, Policy
205
+ from agent_memory_guard import MemoryGuard, Policy, PolicyViolation
206
+ from agent_memory_guard.events import SourceClass
166
207
  from agent_memory_guard.storage import InMemoryStore
167
208
 
168
209
  guard = MemoryGuard(InMemoryStore(), policy=Policy.strict())
169
210
 
170
- def remember(key: str, value: str) -> None:
171
- guard.write(key, value, source="openai-agent")
211
+ def remember_tool_output(tool_name: str, output: str) -> bool:
212
+ try:
213
+ guard.write(
214
+ f"openai_agents.tool.{tool_name}",
215
+ output,
216
+ source="openai_agents_tool",
217
+ source_class=SourceClass.EXTERNAL_TOOL,
218
+ )
219
+ except PolicyViolation:
220
+ return False # enqueue for HITL / eval — do not poison session memory
221
+ return True
172
222
 
173
223
  def recall(key: str) -> str | None:
174
224
  return guard.read(key, sink="openai-agent")
@@ -177,49 +227,55 @@ def recall(key: str) -> str | None:
177
227
  ### AutoGen
178
228
 
179
229
  ```python
180
- from agent_memory_guard import MemoryGuard, Policy, PolicyViolation
230
+ from agent_memory_guard import Action, MemoryGuard, Policy, PolicyViolation
181
231
 
182
232
  guard = MemoryGuard(policy=Policy.strict())
183
233
 
184
234
  def guarded_append(history: list[dict], message: dict) -> None:
235
+ key = f"autogen.msg.{len(history)}"
185
236
  try:
186
- guard.write(f"autogen.msg.{len(history)}", message["content"],
187
- source=message.get("role", "agent"))
237
+ action = guard.write(key, message["content"], source=message.get("role", "agent"))
188
238
  except PolicyViolation as exc:
189
239
  print("blocked:", exc)
190
240
  return
191
- history.append(message)
241
+ if action == Action.QUARANTINE:
242
+ return # held in guard.quarantine for review
243
+ history.append({**message, "content": guard.read(key)}) # redacted if needed
192
244
  ```
193
245
 
194
246
  ### mem0
195
247
 
196
248
  ```python
197
- from agent_memory_guard import MemoryGuard, Policy, PolicyViolation
249
+ from agent_memory_guard import Action, MemoryGuard, Policy, PolicyViolation
198
250
 
199
251
  guard = MemoryGuard(policy=Policy.strict())
200
252
 
201
253
  def safe_add(mem0_client, *, user_id: str, content: str, key: str) -> bool:
202
254
  try:
203
- guard.write(key, content, source="mem0")
255
+ action = guard.write(key, content, source="mem0")
204
256
  except PolicyViolation:
205
257
  return False
206
- mem0_client.add(content, user_id=user_id)
258
+ if action == Action.QUARANTINE:
259
+ return False # held in guard.quarantine for review
260
+ mem0_client.add(guard.read(key), user_id=user_id) # redacted if needed
207
261
  return True
208
262
  ```
209
263
 
210
264
  ### CrewAI
211
265
 
212
266
  ```python
213
- from agent_memory_guard import MemoryGuard, Policy, PolicyViolation
267
+ from agent_memory_guard import Action, MemoryGuard, Policy, PolicyViolation
214
268
 
215
269
  guard = MemoryGuard(policy=Policy.strict())
216
270
 
217
271
  def guarded_memory_callback(key: str, value: str, agent_name: str) -> str:
218
272
  try:
219
- guard.write(key, value, source=f"crewai.{agent_name}")
273
+ action = guard.write(key, value, source=f"crewai.{agent_name}")
220
274
  except PolicyViolation as exc:
221
275
  return f"[BLOCKED] {exc}"
222
- return value
276
+ if action == Action.QUARANTINE:
277
+ return "[QUARANTINED] held for review"
278
+ return guard.read(key) # redacted if needed
223
279
  ```
224
280
 
225
281
  ## YAML policy
@@ -343,6 +399,10 @@ See [AUTHORS](AUTHORS) for details.
343
399
  - Referenced in the MITRE ATLAS "Memory Hardening" mitigation as an open-source implementation of memory-hardening controls.
344
400
  - Featured by Help Net Security, "OWASP Agent Memory Guard: Stop AI agents from being weaponized through their own memory" (June 2026).
345
401
 
402
+ These mentions are descriptive, not endorsements. OWASP Incubator status is not a certification,
403
+ and MITRE has not approved or endorsed this project; the ATLAS Memory Hardening entry lists it
404
+ among example tools.
405
+
346
406
  ## How to cite
347
407
 
348
408
  Use GitHub's "Cite this repository" button (powered by [CITATION.cff](CITATION.cff)), or:
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "agent-memory-guard"
7
- version = "0.3.2"
7
+ version = "0.3.3"
8
8
  description = "Runtime defense layer that protects AI agent memory from poisoning, tool abuse, privilege escalation, and excessive autonomy (OWASP)."
9
9
  readme = "README.md"
10
10
  license = { text = "Apache-2.0" }
@@ -44,6 +44,7 @@ dependencies = ["PyYAML>=6.0"]
44
44
 
45
45
  [project.scripts]
46
46
  amg = "agent_memory_guard.cli:main"
47
+ amg-bench = "agent_memory_guard.bench.cli:main"
47
48
 
48
49
  [project.optional-dependencies]
49
50
  server = ["fastapi>=0.100", "uvicorn[standard]>=0.20", "pydantic>=2.0"]
@@ -51,6 +52,8 @@ ml = ["transformers>=4.30", "torch>=2.0"]
51
52
  langchain = ["langchain-core>=0.1"]
52
53
  crewai = ["crewai>=0.1"]
53
54
  llamaindex = ["llama-index-core>=0.10"]
55
+ autogen = ["pyautogen>=0.2"]
56
+ openai-agents = ["openai-agents>=0.1"]
54
57
  all = [
55
58
  "fastapi>=0.100",
56
59
  "uvicorn[standard]>=0.20",
@@ -58,9 +61,11 @@ all = [
58
61
  "transformers>=4.30",
59
62
  "torch>=2.0",
60
63
  "langchain-core>=0.1",
64
+ "pyautogen>=0.2",
65
+ "openai-agents>=0.1",
61
66
  ]
62
67
  redis = ["redis[hiredis]>=5.0.0"]
63
- test = ["pytest>=7.0", "coverage>=7.0", "httpx>=0.24", "fakeredis>=2.0.0"]
68
+ test = ["pytest>=7.0", "coverage>=7.0", "httpx>=0.24", "fakeredis>=2.0.0", "fastapi>=0.100", "pydantic>=2.0"]
64
69
  dev = ["ruff>=0.4", "mypy>=1.0", "coverage>=7.0", "pytest>=7.0", "httpx>=0.24", "fakeredis>=2.0.0"]
65
70
  docs = ["mkdocs>=1.5", "mkdocs-material>=9.0", "mkdocstrings[python]>=0.24"]
66
71
 
@@ -108,6 +113,7 @@ omit = [
108
113
  "*/tools.py",
109
114
  "*/detectors/ml_injection.py",
110
115
  "*/detectors/anomaly.py",
116
+ "*/bench/adapters/third_party.py",
111
117
  ]
112
118
 
113
119
  [tool.coverage.report]
@@ -16,7 +16,7 @@ from agent_memory_guard.exceptions import (
16
16
  from agent_memory_guard.guard import MemoryGuard
17
17
  from agent_memory_guard.policies.policy import Policy
18
18
 
19
- __version__ = "0.3.2"
19
+ __version__ = "0.3.3"
20
20
 
21
21
  __all__ = [
22
22
  "Action",
@@ -0,0 +1,50 @@
1
+ """Agent Memory Security Benchmark (AMSB).
2
+
3
+ A framework-neutral benchmark that grades **any** agent memory system for
4
+ resilience to memory-poisoning attacks (OWASP ASI06). Where the suite in
5
+ ``benchmarks/security_benchmark.py`` measures Agent Memory Guard's own
6
+ detector accuracy on single writes, AMSB measures the *lifecycle* threat that
7
+ motivates the project: a payload is planted into memory, survives a context
8
+ reset, and is read back on a later turn where it becomes an active directive.
9
+
10
+ The benchmark scores a system on what a downstream turn can actually recall
11
+ after an attack sequence — not on whether a single write looked suspicious.
12
+ Systems are ranked with a 0–100 resilience score and an SSL-Labs-style letter
13
+ grade, with hard grade ceilings for catastrophic breaches.
14
+
15
+ Agent Memory Guard authors this benchmark and also submits itself for grading.
16
+ To keep the ranking honest, AMG is measured through the *same* adapter contract
17
+ as every other system, its results carry an explicit self-submission note, and
18
+ every score is reproducible with ``amg-bench``.
19
+ """
20
+ from __future__ import annotations
21
+
22
+ from agent_memory_guard.bench.adapter import (
23
+ MemorySystemAdapter,
24
+ ProbeResult,
25
+ RememberOutcome,
26
+ )
27
+ from agent_memory_guard.bench.harness import ScenarioRun, SystemReport, run_suite
28
+ from agent_memory_guard.bench.scenarios import (
29
+ Scenario,
30
+ Severity,
31
+ Step,
32
+ default_corpus,
33
+ )
34
+ from agent_memory_guard.bench.scoring import Grade, Scorecard, score_report
35
+
36
+ __all__ = [
37
+ "MemorySystemAdapter",
38
+ "RememberOutcome",
39
+ "ProbeResult",
40
+ "Scenario",
41
+ "Step",
42
+ "Severity",
43
+ "default_corpus",
44
+ "run_suite",
45
+ "ScenarioRun",
46
+ "SystemReport",
47
+ "score_report",
48
+ "Scorecard",
49
+ "Grade",
50
+ ]
@@ -0,0 +1,113 @@
1
+ """Adapter contract every graded memory system implements.
2
+
3
+ A memory system is anything an agent writes facts into and reads them back
4
+ from on a later turn: a plain dictionary, a guarded store, or a third-party
5
+ service such as mem0, Letta, or Zep. AMSB never talks to those systems
6
+ directly; it talks to a :class:`MemorySystemAdapter`, so grading a new system
7
+ means writing one small adapter and nothing else changes.
8
+
9
+ The contract is deliberately tiny — ``remember``, ``recall``, ``context_reset``
10
+ — because that is the whole of the poisoning threat surface: content goes in,
11
+ a session boundary passes, content comes back out and steers the next turn.
12
+ """
13
+ from __future__ import annotations
14
+
15
+ from abc import ABC, abstractmethod
16
+ from dataclasses import dataclass
17
+ from enum import Enum
18
+ from typing import Any
19
+
20
+
21
+ class RememberOutcome(str, Enum):
22
+ """What a system did with an attempted write.
23
+
24
+ The benchmark scores on what ``recall`` returns, not on this value, but the
25
+ outcome is recorded for every step so a report can explain *how* a system
26
+ defended (or failed to). The four values mirror the actions an enforcing
27
+ guard can take; a system with no defenses only ever reports ``STORED``.
28
+ """
29
+
30
+ STORED = "stored"
31
+ """Persisted and readable back verbatim (no defense applied)."""
32
+
33
+ REDACTED = "redacted"
34
+ """Persisted, but the dangerous content was removed or masked first."""
35
+
36
+ QUARANTINED = "quarantined"
37
+ """Withheld from normal recall and flagged for review."""
38
+
39
+ BLOCKED = "blocked"
40
+ """Rejected outright; nothing was persisted."""
41
+
42
+
43
+ @dataclass
44
+ class ProbeResult:
45
+ """The value a later turn reads back, plus how the write was handled.
46
+
47
+ ``recalled`` is the ground truth the benchmark judges: if the attack's
48
+ canary survives in it, the system was breached, regardless of ``outcome``.
49
+ ``outcome`` is advisory reporting detail.
50
+ """
51
+
52
+ recalled: Any | None
53
+ outcome: RememberOutcome
54
+
55
+
56
+ class MemorySystemAdapter(ABC):
57
+ """Uniform interface AMSB drives to grade a memory system.
58
+
59
+ Subclasses set :attr:`name` and :attr:`version` and implement the three
60
+ methods below. Adapters must be cheap to construct: the harness builds a
61
+ fresh adapter per scenario so no state leaks between attacks.
62
+ """
63
+
64
+ #: Stable, human-readable system name shown on the leaderboard.
65
+ name: str = "unnamed-system"
66
+ #: Version string for the graded system (not the benchmark version).
67
+ version: str = "0.0.0"
68
+ #: Set on adapters that grade the benchmark's own author, so reports can
69
+ #: flag the self-submission. Third-party adapters leave this False.
70
+ self_submission: bool = False
71
+
72
+ @abstractmethod
73
+ def remember(
74
+ self,
75
+ key: str,
76
+ value: Any,
77
+ *,
78
+ source_class: str = "external_tool",
79
+ ) -> RememberOutcome:
80
+ """Attempt to persist ``value`` under ``key``.
81
+
82
+ Args:
83
+ key: Memory key the agent would write to.
84
+ value: Content being written — for attack steps, a poisoned payload.
85
+ source_class: Provenance hint (``external_tool``, ``user_input``,
86
+ ``agent_authored``, ``system``). Systems that ignore provenance
87
+ may drop it; guarded systems use it for self-reinforcement and
88
+ trust decisions.
89
+
90
+ Returns:
91
+ How the system handled the write.
92
+ """
93
+
94
+ @abstractmethod
95
+ def recall(self, key: str) -> Any | None:
96
+ """Return what a later turn reads back for ``key`` (``None`` if absent).
97
+
98
+ This is the value the benchmark inspects for a surviving canary. A
99
+ system that blocked or quarantined the write should return ``None``
100
+ (or a value with the dangerous content removed).
101
+ """
102
+
103
+ def context_reset(self) -> None:
104
+ """Simulate a session boundary: clear working context, keep memory.
105
+
106
+ The entire point of memory poisoning is that persisted content
107
+ outlives the conversation that planted it. The harness calls this
108
+ between the attack sequence and the probe so a passing grade means the
109
+ system defended content that *survived a reset*, not merely content in
110
+ the current turn. Default: no-op, because a durable store already
111
+ persists across it.
112
+ """
113
+ return None
@@ -0,0 +1,22 @@
1
+ """Adapters that plug concrete memory systems into AMSB.
2
+
3
+ The baseline adapters here (an unguarded dictionary and two Agent Memory Guard
4
+ configurations) always import cleanly and are what the committed leaderboard is
5
+ built from. Third-party adapters live in :mod:`third_party` and import their
6
+ target lazily, so grading mem0/Letta/Zep is opt-in and never a hard dependency.
7
+ """
8
+ from __future__ import annotations
9
+
10
+ from agent_memory_guard.bench.adapters.baseline import (
11
+ AMGHardenedAdapter,
12
+ AMGStrictAdapter,
13
+ UnguardedDictAdapter,
14
+ baseline_adapters,
15
+ )
16
+
17
+ __all__ = [
18
+ "UnguardedDictAdapter",
19
+ "AMGStrictAdapter",
20
+ "AMGHardenedAdapter",
21
+ "baseline_adapters",
22
+ ]