A Poisoned AGENTS.md in ~/Downloads Hijacked Codex and Claude Code. Here's Where the Scrub Step Fits.
Forever Security found a malicious AGENTS.md sitting in a customer's ~/Downloads folder. That folder was also the agent's working directory. The file tricked an AI coding agent into trying to install a rogue npm package
Forever Security found a malicious AGENTS.md sitting in a customer's ~/Downloads folder. That folder was also the agent's working directory. The file tricked an AI coding agent into trying to install a rogue npm package called function-color. BleepingComputer covered the research, and the numbers are worth reading slowly.
Researchers reproduced the attack against OpenAI Codex with a 90% success rate. They also used a malicious CLAUDE.md to redirect Claude Code's API traffic to an attacker-controlled server. That leaked the OAuth token and enabled spoofed tool calls, which executed code in roughly 50% of tests. A web page can silently drop the instruction file into Downloads, and hundreds of customers reportedly launch agents from that folder.
So the attacker needs a browser tab and a user who runs their agent from the wrong directory.
How it worked
Coding agents read project instruction files from the working directory. AGENTS.md and CLAUDE.md are the conventions, and the agent treats their contents as trusted guidance because that's the whole point of the feature. The file is the developer's voice.
Put an attacker's file in that spot and the trust model collapses. From the summary, two payloads came out of this:
-
Package install. The poisoned
AGENTS.mdcoaxed Codex into attempting to installfunction-color, a rogue npm package. -
API redirect. A poisoned
CLAUDE.mdredirected Claude Code's API traffic to an attacker-controlled server. The OAuth token leaked, and the server could spoof tool calls that then executed code.
The delivery matters as much as the payload. A web page can drop the instruction file into Downloads without the user doing anything deliberate. Then the user opens an agent in that folder, which the researchers say plenty of people do. No exploit chain, no memory corruption. Just a markdown file in a directory the agent already trusts.
The detection gap
What was in place that should have caught this? Not much, and for boring reasons.
- Endpoint tools see a markdown file land in Downloads. Markdown isn't executable, so there's nothing to flag.
- Package registries and scanners act on the package, after the agent has already decided to install it.
- The model's own guardrails are the weak spot. The 90% Codex success rate says that when instructions arrive through a channel the agent considers authoritative, the model follows them. It isn't being jailbroken, it's being given orders by what it believes is its operator.
- Network controls are blind to the API redirect if the traffic looks like normal HTTPS to an API endpoint.
The common thread is that instruction files are read as context, then silently promoted to authority. Nothing in the pipeline asks whether that file should be trusted.
Where Sentinel fits
Sentinel doesn't watch your Downloads folder or the agent's network stack. Be clear about that boundary. What it does is sit in the agentic proxy path and scan content before it reaches the model. If the agent reads AGENTS.md through a Read-style tool, that tool result passes through Sentinel on the /v1/messages route (or /v1/grok, /v1/openai, /v1/gemini, depending on provider) before the model sees it.
Here is what applies to a poisoned instruction file:
- Fast-path regex. Our library of regex patterns covers authority hijacks ("your new system prompt is", "ignore previous instructions") and tool/function abuse patterns. An instruction file telling an agent to install a specific package or reroute its API calls is the kind of tool-abuse phrasing those patterns exist for.
- Deep-path vector similarity. If the wording is subtler than a regex can catch, the content is embedded and compared against our library of attack signature embeddings. Above 0.82 cosine similarity, it blocks.
-
Source-risk scoring. On the agentic routes, a
Read/Grep/Glob/Bashresult is scored against the caller'sX-Sentinel-Trusted-Pathslist. Don't put~/Downloadson it. A path you never marked trusted gets full-sensitivity scanning, with no halved score. Network-exposed locations like/tmpare never discounted regardless of what you configure. -
Neutralize vs. block. On the proxy, a neutralized tool result is wrapped in
[SENTINEL-WARNING: ...]markers telling the model to treat the content as untrusted data. A blocked result is replaced with an inert placeholder, and the SDK gets a normal-format response.
Two limits, stated plainly. Sentinel scans what flows through it, so it only sees the instruction file if the agent's read of that file goes through the proxy. And it does not intercept an outbound API call that the client itself has been reconfigured to send somewhere else. If the CLAUDE.md attack changes where the client sends traffic, that redirect happens outside Sentinel's path. What Sentinel can do is catch the poisoned file when its content is returned to the model as a tool result, before the model acts on the instructions inside. I haven't run the actual Forever Security payloads through it, so "would have flagged this class of content" is the claim, not "would have stopped this attack."
Config and response example
The setup below is illustrative. It is not a capture from the research. Point the SDK at Sentinel and leave the trusted-paths header limited to directories you control.
import anthropic
client = anthropic.Anthropic(
api_key="sk_live_...", # Your Sentinel API key
base_url="https://api.sentinelaifirewall.com/v1",
default_headers={
# Only list directories you authored. ~/Downloads is deliberately absent.
"X-Sentinel-Trusted-Paths": "/home/dev/projects/myapp",
},
)
For an agent that reads a poisoned instruction file, the outcome on the proxy looks like this. The model never sees the install instruction as guidance. It sees a placeholder or a warning-wrapped block.
If you scrub the file directly before handing it to anything, the same check works on /v1/scrub. This response is illustrative:
{
"request_id": "a1b2c3...",
"security": {
"action_taken": "blocked",
"threat_score": 0.91
},
"safe_payload": "[SENTINEL BLOCKED]: Article withheld β fast-path prompt injection detected. Matched: \"...\"."
}
The point is the branch: on blocked, your code never feeds that file into the agent's context. You log the request_id and move on.
What to do today
Check where your agents launch from. The attack in this research depends on a working directory that also receives untrusted files, and Downloads is the worst possible place for that overlap. Run agents from a dedicated project directory, and treat any AGENTS.md or CLAUDE.md you didn't write or review as untrusted input. If one shows up in a folder you didn't put it in, delete it before you launch anything.
Then put a scrub step between your agent's file reads and the model, so a poisoned instruction file gets caught as content instead of obeyed as policy.
Sentinel has a free Starter tier (100 requests/month, no credit card) at sentinelaifirewall.com.
Sources
AI-assisted draft or imaging, human-curated, reviewed and edited.
Originally published by Dev.to AI. Aggregated on AIWithGhost for educational purposes β full credit and traffic to the original publisher.