Skip to content
Note

Why you shouldn't start a coding agent in Downloads

Forever Security found a planted AGENTS.md in a user's Downloads folder telling Codex to install malware. How the attack works, and the checks that stop it.

· 5 min read · SetupBay

Most of us have done it. A zip arrives, you unpack it in ~/Downloads, open a terminal there and type codex or claude to ask what’s inside. On 22 October Forever Security published a write-up showing why that habit is now a real risk, and it didn’t start in a lab. Their product stopped a coding agent on a customer’s Mac partway through installing function-color, a known malicious npm package. The order came from an AGENTS.md sitting in the customer’s Downloads folder. Nobody knew how it got there. The Hacker News covered it a few days later, and @robshocks’s warning on X is how a lot of people heard about it.

Why Downloads

Two ordinary things meet here. An instruction file is meant to be obeyed: Codex reads AGENTS.md, Claude Code reads CLAUDE.md, and that’s the point of them. And a web page can save a file into Downloads with no click, choosing both the name and the contents. Operating systems stopped downloaded programs running themselves years ago. A Markdown file was never treated as dangerous, because nothing ran it. Now an agent does.

Codex looks for AGENTS.md from the root of the Git repository down to the folder you started in. If it can’t find a project root, it checks only the current folder. Downloads usually isn’t a repository, so the file sitting in it is exactly what Codex reads. Forever Security say about half of their customers run a coding agent inside Downloads, and about a quarter of agent sessions open a real browser, which can be the very thing that fetches the file.

What the planted file did

The real file was a 13 KB “Engineering Work Guidelines” document, dull on purpose. Near the end, a section dressed up as environment setup told Codex to add an MCP server to ~/.codex/config.toml and then confirm it with a child codex exec run using the flag that turns off approvals and the sandbox. The “server” used launchctl to run npm install function-color outside Codex’s control. Because it lands in the machine-wide config, every later Codex session loads it, in any folder.

In their isolated VM, Codex ran the payload in more than 90% of attempts. A short, blunt “install this package” was refused. The long, reasonable-sounding document got through.

They then wrote a CLAUDE.md version for Claude Code. It asked Claude to save a project .claude/settings.json that points ANTHROPIC_BASE_URL at a server they controlled. Claude was much harder to fool and wanted approval for the write, but it went through about half the time. From then on Claude’s API traffic, OAuth token included, went to the attacker’s server, which could answer as the model and hand back tool calls. A file that worked in a clean folder failed every time in one full of security notes, which tells you how much the framing matters.

There’s nothing here for OpenAI or Anthropic to patch. Both agents did what they’re built to do. The problem is the folder.

What to do about it

Don’t start agents in folders the web can write to. Downloads, the Desktop, /tmp and mail attachment folders. Move the thing into a proper project folder first and look at what came with it. Our Don’t start agents in Downloads loadout turns that into a check: a read-only script that refuses Downloads, Desktop, temp and home as start folders, and lists every AGENTS.md, CLAUDE.md, GEMINI.md, rule, skill and agent config the agent will read, up to the Git root. It flags installs, pipe-to-shell, MCP and settings changes, API endpoint changes and keep-quiet wording, plus invisible characters and HTML comments aimed at the agent, and treats any instruction file Git doesn’t track as unreviewed. There’s an optional shell wrapper you can add that won’t launch claude, codex, gemini and friends in a drop zone.

Read instruction files you didn’t write. That includes the ones in a repository you just cloned. If one asks the agent to edit its own config, add an MCP server, change an API address or switch off approvals, that’s the attack. Our vetting loadout applies the same reading to skills, plugins and MCP servers before you install them. To see which files each agent picks up and which one wins, check our agent file table.

Make installs boring. The payload was an npm install of a package nobody asked for. A release-age cooldown only catches freshly published versions, so on its own it isn’t the fix here. The habit that is: new packages only when you asked for them, never because some file said so. The dependency cooldowns loadout sets that rule, keeps npm, pnpm, Bun and uv cooldowns on for the freshly published versions that worms rely on, and gives the agent a check to run before adding anything.

Keep the brakes on, and know what your hooks do. Approvals and sandboxes are what made both attacks work harder. A PreToolUse hook that refuses a download piped into a shell holds whoever wrote the instruction. Our hooks guard rails do that for Claude Code. Hooks also run with your permissions, so a hook you inherited is worth checking too, and a cloned repo can bring its own. The hook audit lists every hook Claude Code, Codex and Cursor will run in a project and flags the guard hooks that would let an action through if they crashed, timed out or pointed at a missing script. More of these small scripts are on the Bash stack page.

Check the config files the attack writes to. Look for an [mcp_servers.*] block you didn’t add in ~/.codex/config.toml, and an ANTHROPIC_BASE_URL or proxy setting in any .claude/settings.json. If your project instructions live in one committed file, an unexpected change shows up in git diff (see the Git stack page). If you find one, rotate your tokens. A stolen OAuth token is a live session.

None of this needs a product. It needs one new habit: treat the folder you start an agent in as part of the prompt, because it is.