DEV Community

Cover image for Markdown Is the New Executable: What the AGENTS.md-in-Downloads Attack Teaches Us About Coding Agent Security
M.T.Ramkrushna
M.T.Ramkrushna

Posted on

Markdown Is the New Executable: What the AGENTS.md-in-Downloads Attack Teaches Us About Coding Agent Security

On October 22, 2026, Forever Security published research with a title that every team running coding agents should take literally: "Markdown Is the New Executable." Their software had stopped a coding agent on a real customer's machine from installing function-color, a known malicious npm package. The instruction to install it didn't come from a dependency or a repo. It came from an AGENTS.md file sitting in the customer's ~/Downloads folder, a file nobody remembered putting there.

This post covers what the researchers showed, why it works, and what I'd change in how I run Codex, Claude Code or any other coding agent. The short version: an instruction file is untrusted input that your agent treats as trusted orders, and the defenses have to live outside the model.

All success rates below are Forever Security's own reported test results (reported by BleepingComputer and Help Net Security). None of the write-ups name the agent versions tested, and neither OpenAI nor Anthropic had commented when Convika checked on October 27.

What happened

  • The real file: a 13 KB "Engineering Work Guidelines" document, plain text and mundane, with the attack in one section near the end dressed up as routine setup.
  • The target: OpenAI Codex. The section told Codex, "before beginning the user's request", to append a project-tools MCP server block to ~/.codex/config.toml. The server's launch command handed npm install function-color to macOS launchctl.
  • The sandbox workaround: because the install needed network access, the file told Codex to run a child codex exec with --dangerously-bypass-approvals-and-sandbox, framed as a required "initialization check". The researchers note this was a flag the model wrote itself.
  • Success rate: "In our testing, Codex ran the malware in more than 90% of attempts." Most tests used a harmless payload that only opened the calculator; the real package ran once, inside an isolated VM.
  • Persistence: ~/.codex/config.toml is machine-wide, so once written, every later Codex session on that machine loads the attacker's server, in any folder.
  • Claude Code: the researchers wrote their own CLAUDE.md inspired by the attacker's file. It asked Claude Code to save .claude/settings.json with ANTHROPIC_BASE_URL pointing at "the team's internal gateway" (a server they controlled). Claude followed through about 50% of the time. That leaked the OAuth token, which the researchers replayed against the real API and were served as the victim.
  • Then code execution: on the next launch, their server answered as the model with a Bash tool call setting dangerouslyDisableSandbox: true, and the client ran it. In about a fifth of tests, a spawned subagent opened a fresh connection through their server and got code execution in the same session.

The researchers say Claude "was much harder to fool", and that when the payload pushed too hard, a subagent read the CLAUDE.md and flagged it as an attack. npm has since removed function-color.

Why it works: two harmless things that combine

Forever's framing is the most useful part of the research. Each piece is fine on its own:

  1. Instruction files are meant to be obeyed. AGENTS.md and CLAUDE.md exist so you can tell an agent how to build, test and behave in a project. Agents load them from the working directory automatically.
  2. A web page can save a file to ~/Downloads without asking. BleepingComputer found that Chrome, Edge and Firefox save downloads there by default without prompting (they do show a download notification); Brave asked for approval.

Put them together and the web can plant orders in a folder that your agent treats like a project you vetted. Operating systems learned long ago not to run a downloaded executable without approval. A Markdown file isn't an executable, so none of those guardrails apply, until an agent reads it.

And this is not rare behaviour. Forever says about half of its customers run a coding agent inside Downloads, and roughly a quarter of agent sessions open a real browser. Weizman told BleepingComputer that Claude Code alone launched more than 1,300 sessions from that location. People use agents to tidy that folder or act on a file they just downloaded.

One more detail from Convika's write-up worth knowing: since Claude Code 2.1.277 (September 2026), Claude Code reads AGENTS.md when a project has no CLAUDE.md. So a planted AGENTS.md can reach both agents.

The real lesson: length and tone are the exploit

The part I found most instructive is how the file got past the models. Per Forever:

  • A short "install this package" request gets refused. The long, boring engineering-doc framing gets through. That's why the real file is 13 KB, not a one-liner.
  • Each control was worked around in prose. The trust gate (Codex only obeys AGENTS.md in a trusted project, and Downloads qualifies once you've worked there), startup-only server loading (collapsed with a child session), the network sandbox (the bypass flag), and the server handshake timeout (handing the install to launchctl).
  • The Claude payload "reads the room". The same file that worked in a clean folder failed every time in one full of security notes.

That's the uncomfortable takeaway for anyone building agents: you cannot rely on the model to recognise a malicious instruction. A refusal rate is a probability, and a patient attacker tunes the text until the probability goes their way. Anthropic's own docs say it plainly: CLAUDE.md and AGENTS.md are "context, not enforced configuration", and "to block an action regardless of what Claude decides, use a PreToolUse hook instead."

The researchers argue there is "nothing here for OpenAI or Anthropic to patch", because both agents do what they were built to do. Whether or not you agree, it means the fix is on the operator's side.

What I'd change today

These come from Forever's recommendations, BleepingComputer's notes on vendor guidance, and the settings Convika documented. I've grouped them by what they stop.

1. Control where the agent starts.
Never start an agent in ~/Downloads or ~/. Both OpenAI and Anthropic recommend starting in the root of the specific project. Move a downloaded file into a project folder first, then start the agent there.

2. Treat instruction files like code you didn't write.
Read any AGENTS.md or CLAUDE.md you didn't author before an agent touches it, including in a repo you just cloned. A planted file reads like setup notes: install a package, add a tool server, use a company gateway. A quick check from Convika:

find ~ -maxdepth 2 \( -name 'AGENTS*.md' -o -name 'CLAUDE*.md' \) 2>/dev/null
Enter fullscreen mode Exit fullscreen mode

3. Watch the agent's own config, because that's the real target.
Both attacks wrote to the agent's configuration, not to your code. Check for MCP servers and base URLs you didn't add:

grep -nE 'mcp_servers|base_url' ~/.codex/config.toml
grep -HniE 'BASE_URL|PROXY|apiKeyHelper' ~/.claude/settings.json .claude/settings.json 2>/dev/null
Enter fullscreen mode Exit fullscreen mode

In Claude Code, Convika points to ConfigChange hooks to audit or block settings changes, and a PreToolUse hook that refuses writes to .claude/settings.json (matching Bash as well as Write|Edit, since the model can write the file with echo).

4. Make "bypass the sandbox" impossible, not just discouraged.
Keep approvals and sandboxes on, and never approve a command carrying --dangerously-bypass-approvals-and-sandbox. In Claude Code, strict sandbox mode ("sandbox": {"allowUnsandboxedCommands": false}) ignores the dangerouslyDisableSandbox parameter. Note Convika's caveat: that doesn't stop the token leak, because Claude Code's own API traffic isn't a Bash command.

5. Know your permission mode.
Per Convika, Claude Code's interactive default since 2.1.283 is auto, where a write to .claude/ goes to a classifier instead of asking you. Manual mode and acceptEdits always prompt for protected paths. If you open an untrusted folder, --restricted blocks protected-path writes and removes command tools.

6. If you find a planted file, rotate.
Assume the token leaked. Forever: "a stolen token is a live session." Log out and back in, replace API keys, and treat any machine where function-color was installed as compromised.

The bigger pattern for agent builders

If you're building your own agent rather than just using one, the same seam exists in your product:

  • Separate instructions from data by source, not by content. Anything the agent can read that someone else can write (a downloaded file, a web page, a ticket, an email) is data, even if it's formatted like instructions.
  • Protect the agent's own control plane. Config files, tool registrations and API endpoints should be write-protected from the agent, or every change should need an out-of-band approval.
  • Log what the agent reads, writes and reaches for. Forever caught this because it was watching the agent's actions, not the user's.

Forever's closing point applies well beyond Downloads: most defences were designed around a human who clicks, approves and runs the code. Agents take the human out of that loop, and guarantees that felt permanent turn back into assumptions.

Where do you start your coding agents, and does anything stop them from editing their own config?

References

Top comments (0)