agentmemorytaintgap
Catch untrusted content being written into an AI agent's long-term memory as if it were a trusted fact.
Prompt injection usually gets pictured as a single bad turn: a poisoned web page or tool result sneaks into one prompt, the model says something wrong once, and the blast radius ends there. Agent memory breaks that assumption. A growing class of agent frameworks let the agent persist content — a tool result, a summary, a "fact" — into a long-term memory store that gets read back and fed into every future prompt as if it were the agent's own trusted conclusion. If the content that gets written was actually attacker-controlled (a malicious tool response, a crafted user message, a poisoned web page), and nothing strips or tags it first, the poison survives for the lifetime of that memory store — this is the "agent memory poisoning" risk that's an increasingly-discussed, distinct branch of prompt injection in 2026.
agentmemorytaintgap reads your source with Python's ast module — no
imports, no execution — and flags the memory-write call sites where that
can happen:
$ agentmemorytaintgap agent/
BLOCKER AT001 agent/handler.py:3:4 Memory-write call stores content traced back to an untrusted origin (tool output / external fetch / raw user input) with no sanitization or provenance-tagging call in this function (agent memory poisoning risk).
agent_memory.save_context({"input": user_query}, {"output": tool_result})
1 file(s) scanned · 1 blocker(s) · 0 warning(s)
Exit code 1 on a blocker, so it drops straight into pre-commit or CI.
What it flags
| Rule | Severity | Fires when… |
|---|---|---|
| AT001 | blocker | a memory-write call's stored value traces (single-hop, same function) back to a provably untrusted origin — tool output, an HTTP/fetch response, or a raw user-input parameter — with no sanitize/tag call on it anywhere in that function. |
| AT002 | warning | a memory-write call's stored value origin could not be confidently traced either way (a bare parameter with no naming signal, or a longer assignment chain). Lower confidence — worth a human glance, not a confident blocker. |
The LLM's own generated response (e.g. llm.invoke(...)) is treated as
trusted and is never flagged — the concern here is specifically external
content being stored as if it were the agent's own conclusion.
Recognized memory-write shapes
- LangChain-shaped:
.save_context(...),.chat_memory.add_message(...)/.add_user_message(...)/.add_ai_message(...). - Generic vector-store-as-memory:
.add(...)/.upsert(...)called on a variable whose name containsmemory,mem_store, orlong_term— there is no single standard "agent memory" API the way there is for HTTP, so this is a naming heuristic, documented honestly inDETAILS.md. - Custom memory helpers: a call to
remember(...),store_memory(...), orsave_memory(...), as a free function or bound method.
Recognized untrusted origins
- A tool call:
.run(...)/.invoke(...)on a variable named liketool, or a call to a function decorated with a recognizable@tooldecorator. - An external fetch:
requests/httpx/aiohttp.get/.post/...(...), or a call to a function literally namedfetch. - A raw user-input function parameter (named like
user_input,user_message,raw_input,user_query, ormessage).
Safe-marker short-circuit
If a call whose name contains sanitize, clean, validate, tag_source,
or mark_untrusted is applied to the value anywhere in the
same function before the memory write, the finding does not fire — the
short-circuit is deliberately generous, the same style as its sibling tools.
How it relates to echofence
echofence and agentmemorytaintgap are both prompt-injection-adjacent AST
linters, and they are deliberately distinct, non-overlapping tools:
echofence— input side, single turn. Flags untrusted external content reaching a live LLM prompt directly — the indirect variant of OWASP LLM01. The risk window is one request/response cycle.agentmemorytaintgap— persistence side, every future turn. Flags untrusted content being written into long-term memory that will be read back and replayed as trusted context across every subsequent turn, potentially for the lifetime of the memory store. The artifact, the timing, and the risk shape are different: a poisoned prompt affects one answer; a poisoned memory write affects all future answers until someone notices and purges the store.
See DETAILS.md for the full, honest comparison — including why this is not
just "echofence but for a different sink."
Install
pip install agentmemorytaintgap
Usage
agentmemorytaintgap agent/ # scan a directory
agentmemorytaintgap memory_handler.py # scan a file
agentmemorytaintgap agent/ --strict # AT002 warnings fail the run too
agentmemorytaintgap agent/ --json # machine-readable output
In CI
- run: pip install agentmemorytaintgap
- run: agentmemorytaintgap agent/ --strict
Exit codes: 0 clean · 1 a blocker (AT001), or any finding under
--strict · 2 usage error.
Honest limitations
agentmemorytaintgap is a pragmatic, heuristic, single-hop,
same-function-scope analyzer — not full data-flow / taint analysis.
See DETAILS.md for the complete breakdown, but concretely:
- It only recognizes the memory-write shapes and naming conventions listed
above. A memory variable that doesn't contain
memory/mem_store/long_termin its name, or a tool call that doesn't match the recognized@tool/.run()/.invoke()shapes, is invisible to v0.1. - Tracing is single-hop and scoped to one function. A value laundered through a helper function it doesn't look inside of, or passed across functions before being written to memory, will not be traced.
- A sanitize/tag call anywhere in the function short-circuits the finding — it trusts that the call actually does what its name implies; it does not verify that.
Treat it as a fast reviewer that catches the obvious, high-value cases on every PR, paired with human judgment for the rest.
License
MIT © Jay Tank
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file agentmemorytaintgap-0.1.0.tar.gz.
File metadata
- Download URL: agentmemorytaintgap-0.1.0.tar.gz
- Upload date:
- Size: 17.1 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via:
twine/7.0.0 CPython/3.10.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
19d641479449b5166f0bab04f6ac5507b2660c825164ae529a981f4546d42bf2
|
|
| MD5 |
d13df65066926269285dbf9864e6ffee
|
|
| BLAKE2b-256 |
e69c47e2059868222d14b44e3af5058cc85a325af7ce621cd07a94513e026264
|
File details
Details for the file agentmemorytaintgap-0.1.0-py3-none-any.whl.
File metadata
- Download URL: agentmemorytaintgap-0.1.0-py3-none-any.whl
- Upload date:
- Size: 11.8 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via:
twine/7.0.0 CPython/3.10.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
14ef1db3b623b69b6c81c2770cc946cc74187f022e178949de3dd11e518b07bb
|
|
| MD5 |
7974a273f5d1b2f82037802db6a6c24a
|
|
| BLAKE2b-256 |
d45ace53b1765a7fca890dd0f3b1fc874350d69f6908934475cfeefe1ad01dd2
|