Skip to main content

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 contains memory, mem_store, or long_term — there is no single standard "agent memory" API the way there is for HTTP, so this is a naming heuristic, documented honestly in DETAILS.md.
  • Custom memory helpers: a call to remember(...), store_memory(...), or save_memory(...), as a free function or bound method.

Recognized untrusted origins

  • A tool call: .run(...) / .invoke(...) on a variable named like tool, or a call to a function decorated with a recognizable @tool decorator.
  • An external fetch: requests/httpx/aiohttp .get/.post/...(...), or a call to a function literally named fetch.
  • A raw user-input function parameter (named like user_input, user_message, raw_input, user_query, or message).

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_term in 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

agentmemorytaintgap-0.1.0.tar.gz (17.1 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

agentmemorytaintgap-0.1.0-py3-none-any.whl (11.8 kB view details)

Uploaded Python 3

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

Hashes for agentmemorytaintgap-0.1.0.tar.gz
Algorithm Hash digest
SHA256 19d641479449b5166f0bab04f6ac5507b2660c825164ae529a981f4546d42bf2
MD5 d13df65066926269285dbf9864e6ffee
BLAKE2b-256 e69c47e2059868222d14b44e3af5058cc85a325af7ce621cd07a94513e026264

See more details on using hashes here.

File details

Details for the file agentmemorytaintgap-0.1.0-py3-none-any.whl.

File metadata

File hashes

Hashes for agentmemorytaintgap-0.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 14ef1db3b623b69b6c81c2770cc946cc74187f022e178949de3dd11e518b07bb
MD5 7974a273f5d1b2f82037802db6a6c24a
BLAKE2b-256 d45ace53b1765a7fca890dd0f3b1fc874350d69f6908934475cfeefe1ad01dd2

See more details on using hashes here.

Release history Release notifications | RSS feed

This release

0.1.0 This release

2 files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page