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.
- {agent_memory_guard-0.3.2/src/agent_memory_guard.egg-info → agent_memory_guard-0.3.3}/PKG-INFO +83 -15
- {agent_memory_guard-0.3.2 → agent_memory_guard-0.3.3}/README.md +74 -14
- {agent_memory_guard-0.3.2 → agent_memory_guard-0.3.3}/pyproject.toml +8 -2
- {agent_memory_guard-0.3.2 → agent_memory_guard-0.3.3}/src/agent_memory_guard/__init__.py +1 -1
- agent_memory_guard-0.3.3/src/agent_memory_guard/bench/__init__.py +50 -0
- agent_memory_guard-0.3.3/src/agent_memory_guard/bench/adapter.py +113 -0
- agent_memory_guard-0.3.3/src/agent_memory_guard/bench/adapters/__init__.py +22 -0
- agent_memory_guard-0.3.3/src/agent_memory_guard/bench/adapters/baseline.py +164 -0
- agent_memory_guard-0.3.3/src/agent_memory_guard/bench/adapters/third_party.py +111 -0
- agent_memory_guard-0.3.3/src/agent_memory_guard/bench/cli.py +150 -0
- agent_memory_guard-0.3.3/src/agent_memory_guard/bench/harness.py +218 -0
- agent_memory_guard-0.3.3/src/agent_memory_guard/bench/report.py +167 -0
- agent_memory_guard-0.3.3/src/agent_memory_guard/bench/scenarios.py +347 -0
- agent_memory_guard-0.3.3/src/agent_memory_guard/bench/scoring.py +211 -0
- {agent_memory_guard-0.3.2 → agent_memory_guard-0.3.3}/src/agent_memory_guard/cli.py +21 -3
- {agent_memory_guard-0.3.2 → agent_memory_guard-0.3.3}/src/agent_memory_guard/detectors/excessive_autonomy.py +1 -13
- {agent_memory_guard-0.3.2 → agent_memory_guard-0.3.3}/src/agent_memory_guard/detectors/injection.py +40 -3
- {agent_memory_guard-0.3.2 → agent_memory_guard-0.3.3}/src/agent_memory_guard/detectors/ml_injection.py +1 -13
- {agent_memory_guard-0.3.2 → agent_memory_guard-0.3.3}/src/agent_memory_guard/detectors/privilege_escalation.py +1 -13
- {agent_memory_guard-0.3.2 → agent_memory_guard-0.3.3}/src/agent_memory_guard/detectors/self_reinforcement.py +58 -12
- {agent_memory_guard-0.3.2 → agent_memory_guard-0.3.3}/src/agent_memory_guard/detectors/tool_abuse.py +1 -13
- {agent_memory_guard-0.3.2 → agent_memory_guard-0.3.3}/src/agent_memory_guard/events.py +8 -2
- {agent_memory_guard-0.3.2 → agent_memory_guard-0.3.3}/src/agent_memory_guard/guard.py +57 -15
- agent_memory_guard-0.3.3/src/agent_memory_guard/integrations/__init__.py +29 -0
- agent_memory_guard-0.3.3/src/agent_memory_guard/integrations/agno.py +115 -0
- agent_memory_guard-0.3.3/src/agent_memory_guard/integrations/autogen.py +156 -0
- {agent_memory_guard-0.3.2 → agent_memory_guard-0.3.3}/src/agent_memory_guard/integrations/llamaindex.py +33 -9
- agent_memory_guard-0.3.3/src/agent_memory_guard/integrations/openai_agents.py +113 -0
- {agent_memory_guard-0.3.2 → agent_memory_guard-0.3.3}/src/agent_memory_guard/policies/policy.py +11 -0
- {agent_memory_guard-0.3.2 → agent_memory_guard-0.3.3}/src/agent_memory_guard/scanner/__init__.py +19 -1
- {agent_memory_guard-0.3.2 → agent_memory_guard-0.3.3}/src/agent_memory_guard/server.py +39 -19
- {agent_memory_guard-0.3.2 → agent_memory_guard-0.3.3}/src/agent_memory_guard/storage/snapshots.py +4 -0
- {agent_memory_guard-0.3.2 → agent_memory_guard-0.3.3/src/agent_memory_guard.egg-info}/PKG-INFO +83 -15
- {agent_memory_guard-0.3.2 → agent_memory_guard-0.3.3}/src/agent_memory_guard.egg-info/SOURCES.txt +29 -1
- {agent_memory_guard-0.3.2 → agent_memory_guard-0.3.3}/src/agent_memory_guard.egg-info/entry_points.txt +1 -0
- {agent_memory_guard-0.3.2 → agent_memory_guard-0.3.3}/src/agent_memory_guard.egg-info/requires.txt +10 -0
- agent_memory_guard-0.3.3/tests/test_action_no_expression_in_run.py +86 -0
- agent_memory_guard-0.3.3/tests/test_agno_integration.py +71 -0
- agent_memory_guard-0.3.3/tests/test_autogen_openai_agents.py +160 -0
- agent_memory_guard-0.3.3/tests/test_cli.py +38 -0
- agent_memory_guard-0.3.3/tests/test_cli_check_and_scan.py +89 -0
- agent_memory_guard-0.3.3/tests/test_demo.py +16 -0
- agent_memory_guard-0.3.3/tests/test_detector_error_event.py +57 -0
- agent_memory_guard-0.3.3/tests/test_immutable_glob.py +47 -0
- agent_memory_guard-0.3.3/tests/test_injection_system_override.py +37 -0
- agent_memory_guard-0.3.3/tests/test_integration_llamaindex.py +59 -0
- agent_memory_guard-0.3.3/tests/test_openai_agents_example.py +43 -0
- {agent_memory_guard-0.3.2 → agent_memory_guard-0.3.3}/tests/test_self_reinforcement.py +9 -9
- agent_memory_guard-0.3.3/tests/test_self_reinforcement_trust.py +216 -0
- agent_memory_guard-0.3.3/tests/test_server_blocked_responses.py +77 -0
- agent_memory_guard-0.3.3/tests/test_stringify_depth.py +65 -0
- agent_memory_guard-0.3.3/tests/test_uninspectable_nesting.py +78 -0
- agent_memory_guard-0.3.3/tests/test_workflow_security.py +41 -0
- agent_memory_guard-0.3.2/src/agent_memory_guard/integrations/__init__.py +0 -3
- agent_memory_guard-0.3.2/tests/test_demo.py +0 -10
- {agent_memory_guard-0.3.2 → agent_memory_guard-0.3.3}/AUTHORS +0 -0
- {agent_memory_guard-0.3.2 → agent_memory_guard-0.3.3}/LICENSE.md +0 -0
- {agent_memory_guard-0.3.2 → agent_memory_guard-0.3.3}/setup.cfg +0 -0
- {agent_memory_guard-0.3.2 → agent_memory_guard-0.3.3}/src/agent_memory_guard/classification.py +0 -0
- {agent_memory_guard-0.3.2 → agent_memory_guard-0.3.3}/src/agent_memory_guard/detectors/__init__.py +0 -0
- {agent_memory_guard-0.3.2 → agent_memory_guard-0.3.3}/src/agent_memory_guard/detectors/anomaly.py +0 -0
- {agent_memory_guard-0.3.2 → agent_memory_guard-0.3.3}/src/agent_memory_guard/detectors/base.py +0 -0
- {agent_memory_guard-0.3.2 → agent_memory_guard-0.3.3}/src/agent_memory_guard/detectors/cross_task.py +0 -0
- {agent_memory_guard-0.3.2 → agent_memory_guard-0.3.3}/src/agent_memory_guard/detectors/leakage.py +0 -0
- {agent_memory_guard-0.3.2 → agent_memory_guard-0.3.3}/src/agent_memory_guard/detectors/memory_persistence_injection.py +0 -0
- {agent_memory_guard-0.3.2 → agent_memory_guard-0.3.3}/src/agent_memory_guard/detectors/protected_keys.py +0 -0
- {agent_memory_guard-0.3.2 → agent_memory_guard-0.3.3}/src/agent_memory_guard/exceptions.py +0 -0
- {agent_memory_guard-0.3.2 → agent_memory_guard-0.3.3}/src/agent_memory_guard/integrations/crewai.py +0 -0
- {agent_memory_guard-0.3.2 → agent_memory_guard-0.3.3}/src/agent_memory_guard/integrations/langchain.py +0 -0
- {agent_memory_guard-0.3.2 → agent_memory_guard-0.3.3}/src/agent_memory_guard/integrity.py +0 -0
- {agent_memory_guard-0.3.2 → agent_memory_guard-0.3.3}/src/agent_memory_guard/metrics.py +0 -0
- {agent_memory_guard-0.3.2 → agent_memory_guard-0.3.3}/src/agent_memory_guard/middleware.py +0 -0
- {agent_memory_guard-0.3.2 → agent_memory_guard-0.3.3}/src/agent_memory_guard/policies/__init__.py +0 -0
- {agent_memory_guard-0.3.2 → agent_memory_guard-0.3.3}/src/agent_memory_guard/py.typed +0 -0
- {agent_memory_guard-0.3.2 → agent_memory_guard-0.3.3}/src/agent_memory_guard/rag.py +0 -0
- {agent_memory_guard-0.3.2 → agent_memory_guard-0.3.3}/src/agent_memory_guard/scan.py +0 -0
- {agent_memory_guard-0.3.2 → agent_memory_guard-0.3.3}/src/agent_memory_guard/storage/__init__.py +0 -0
- {agent_memory_guard-0.3.2 → agent_memory_guard-0.3.3}/src/agent_memory_guard/storage/memory_store.py +0 -0
- {agent_memory_guard-0.3.2 → agent_memory_guard-0.3.3}/src/agent_memory_guard/storage/redis_store.py +0 -0
- {agent_memory_guard-0.3.2 → agent_memory_guard-0.3.3}/src/agent_memory_guard/streaming.py +0 -0
- {agent_memory_guard-0.3.2 → agent_memory_guard-0.3.3}/src/agent_memory_guard/tools.py +0 -0
- {agent_memory_guard-0.3.2 → agent_memory_guard-0.3.3}/src/agent_memory_guard.egg-info/dependency_links.txt +0 -0
- {agent_memory_guard-0.3.2 → agent_memory_guard-0.3.3}/src/agent_memory_guard.egg-info/top_level.txt +0 -0
- {agent_memory_guard-0.3.2 → agent_memory_guard-0.3.3}/tests/test_classification.py +0 -0
- {agent_memory_guard-0.3.2 → agent_memory_guard-0.3.3}/tests/test_detectors.py +0 -0
- {agent_memory_guard-0.3.2 → agent_memory_guard-0.3.3}/tests/test_guard.py +0 -0
- {agent_memory_guard-0.3.2 → agent_memory_guard-0.3.3}/tests/test_integrity.py +0 -0
- {agent_memory_guard-0.3.2 → agent_memory_guard-0.3.3}/tests/test_memory_persistence_injection.py +0 -0
- {agent_memory_guard-0.3.2 → agent_memory_guard-0.3.3}/tests/test_new_detectors.py +0 -0
- {agent_memory_guard-0.3.2 → agent_memory_guard-0.3.3}/tests/test_policy.py +0 -0
- {agent_memory_guard-0.3.2 → agent_memory_guard-0.3.3}/tests/test_policy_tiered.py +0 -0
- {agent_memory_guard-0.3.2 → agent_memory_guard-0.3.3}/tests/test_public_scan_api.py +0 -0
- {agent_memory_guard-0.3.2 → agent_memory_guard-0.3.3}/tests/test_redis_store.py +0 -0
- {agent_memory_guard-0.3.2 → agent_memory_guard-0.3.3}/tests/test_scanner_file_discovery.py +0 -0
- {agent_memory_guard-0.3.2 → agent_memory_guard-0.3.3}/tests/test_snapshots.py +0 -0
{agent_memory_guard-0.3.2/src/agent_memory_guard.egg-info → agent_memory_guard-0.3.3}/PKG-INFO
RENAMED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: agent-memory-guard
|
|
3
|
-
Version: 0.3.
|
|
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
|
-
### 📦
|
|
92
|
+
### 📦 14,721 PyPI downloads · 14,026 repository clones
|
|
85
93
|
|
|
86
94
|
[](https://pepy.tech/project/agent-memory-guard) [](https://pepy.tech/project/langchain-agent-memory-guard) [](https://github.com/OWASP/www-project-agent-memory-guard) [](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
|
|
242
|
-
|
|
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(
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
### 📦
|
|
13
|
+
### 📦 14,721 PyPI downloads · 14,026 repository clones
|
|
14
14
|
|
|
15
15
|
[](https://pepy.tech/project/agent-memory-guard) [](https://pepy.tech/project/langchain-agent-memory-guard) [](https://github.com/OWASP/www-project-agent-memory-guard) [](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
|
|
171
|
-
|
|
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(
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
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]
|
|
@@ -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
|
+
]
|