What happens
UNTRUSTED_INPUT_COMMAND matches exactly two things:
r"\bgh\s+(issue|pr)\s+(view|list|diff|comment)|"
r"\bgit\s+(fetch|pull|clone)\b"
That is content arriving from a code forge, which is the threat model in the Agent Data Injection paper the rule was built against. Reading an agent's own instruction or memory file earns nothing:
cat ~/.claude/CLAUDE.md -> []
cat AGENTS.md -> []
cat .cursorrules -> []
cat ~/.claude/projects/x/memory/MEMORY.md -> []
Empty. Not a weaker trait, no trait. So a read of a poisoned instruction file cannot start the untrusted-input-then-risky-action chain, because nothing marks it as untrusted content.
Why it matters
These files are an instruction channel by design. The agent reads them and treats them as directives, which is the entire point of them. If one is poisoned, it is a prompt injection with a long fuse: it survives the session it was planted in and fires on every later session.
OWASP AST10 names memory files as untrusted content directly, and this project is otherwise covered on AST05.
Same shape as #142. A channel that carries instructions, not classified as one, so the detector is blind to it while looking correct.
The naive fix is wrong
Do not flag every read of CLAUDE.md.
These files are overwhelmingly written by the operator and read constantly. Treating every read as untrusted input would produce a critical on the first thirty seconds of every session, which is exactly the false-positive class 0.7.0 spent a release removing. It would be #44 again with a different filename.
The shape that is actually interesting
Not the read. The write followed by a read.
- The agent, or content it fetched, modifies an instruction file
- A later session reads it and acts
That is a two-step sequence across sessions, which is a different correlation window from anything the engine does today: every current rule correlates inside one session. A poisoned memory file is specifically an attack that crosses sessions, so the rule that catches it has to as well.
Worth considering alongside host-subagent-swarm-burst, which is the only existing host-scoped rule and the only precedent for correlating beyond a single session.
Open questions
- Which paths count.
CLAUDE.md, AGENTS.md, .cursorrules, .github/copilot-instructions.md, per-agent memory stores. This list is a maintenance burden and gets stale as agents add conventions.
- Whether a write by the operator is distinguishable from a write by the agent. If it is not, the rule cannot separate "I edited my own instructions" from "the agent rewrote them", and that distinction is the whole finding.
- Whether this belongs as a trait, a rule, or both.
Blocked by the freeze
traits.py and rules.py are hashed into the ruleset fingerprint. The dogfood clock restarted 2026-08-30 and closes no earlier than 2026-09-26, so this cannot land before then. #55 is the other change queued behind that, and both should go in one pass so the fingerprint moves once.
Source
Noticed while checking whether an unrelated article on agent memory design touched anything here. It did not, but the topic pointed at this. Verified against classify_command rather than assumed.
What happens
UNTRUSTED_INPUT_COMMANDmatches exactly two things:That is content arriving from a code forge, which is the threat model in the Agent Data Injection paper the rule was built against. Reading an agent's own instruction or memory file earns nothing:
Empty. Not a weaker trait, no trait. So a read of a poisoned instruction file cannot start the
untrusted-input-then-risky-actionchain, because nothing marks it as untrusted content.Why it matters
These files are an instruction channel by design. The agent reads them and treats them as directives, which is the entire point of them. If one is poisoned, it is a prompt injection with a long fuse: it survives the session it was planted in and fires on every later session.
OWASP AST10 names memory files as untrusted content directly, and this project is otherwise covered on AST05.
Same shape as #142. A channel that carries instructions, not classified as one, so the detector is blind to it while looking correct.
The naive fix is wrong
Do not flag every read of
CLAUDE.md.These files are overwhelmingly written by the operator and read constantly. Treating every read as untrusted input would produce a critical on the first thirty seconds of every session, which is exactly the false-positive class 0.7.0 spent a release removing. It would be #44 again with a different filename.
The shape that is actually interesting
Not the read. The write followed by a read.
That is a two-step sequence across sessions, which is a different correlation window from anything the engine does today: every current rule correlates inside one session. A poisoned memory file is specifically an attack that crosses sessions, so the rule that catches it has to as well.
Worth considering alongside
host-subagent-swarm-burst, which is the only existing host-scoped rule and the only precedent for correlating beyond a single session.Open questions
CLAUDE.md,AGENTS.md,.cursorrules,.github/copilot-instructions.md, per-agent memory stores. This list is a maintenance burden and gets stale as agents add conventions.Blocked by the freeze
traits.pyandrules.pyare hashed into the ruleset fingerprint. The dogfood clock restarted 2026-08-30 and closes no earlier than 2026-09-26, so this cannot land before then. #55 is the other change queued behind that, and both should go in one pass so the fingerprint moves once.Source
Noticed while checking whether an unrelated article on agent memory design touched anything here. It did not, but the topic pointed at this. Verified against
classify_commandrather than assumed.