What the Forever Security research shows
On 25 October 2026, BleepingComputer reported the work of Forever Security. Researchers found a booby-trapped AGENTS.md file in a customer's ~/Downloads folder, instructing a coding agent to install an npm package named "function-color". That folder was also the agent's working directory, and agents automatically load the instruction files of their working directory. The reconstructed scenario goes like this: a web page silently drops the file into the downloads folder, the user starts their agent in that folder, then the agent follows the instructions with the tools and rights it already has.
On OpenAI's Codex, the full chain, with a harmless payload (opening the calculator), succeeded in 90% of tests. With Claude Code, a booby-trapped CLAUDE.md redirected API traffic to a server controlled by the researchers, which exposed the OAuth token and allowed code execution in roughly 50% of tests. The researchers say there is nothing to patch on the OpenAI or Anthropic side: the agent works as designed.
What the study establishes, and what it does not say
- Established — an instruction file placed in the working directory is enough to steer the agent, with no software flaw exploited.
- Condition — the attack requires the user to start the agent in the folder concerned. According to BleepingComputer, OpenAI and Anthropic, for their part, recommend running the agent in the project directory.
- Harmless payloads — the 90% and roughly 50% rates come from tests with a harmless payload; they do not measure real attacks and do not generalize to every tool.
- Unknown delivery — the researchers could not determine how the customer's file reached their downloads: the web page remains a hypothesis.
- Cursor CLI — mentioned as launched from that folder, but not described as tested.
- Our reading — a cloned repository can contain its own instruction file. That is our inference, not a finding of the study.
Running the agent in the right folder and approving commands: why a separate server?
Human approval and the tool's sandbox are real safeguards, and they should stay on: the source article recommends it too. Still, they depend on constant attention, a well-configured tool and a launch in the right folder. The day one of the three slips, the question becomes: what can the hijacked agent reach?
On a workstation, the answer is long: SSH keys, cloud tokens, other clients' repositories, a logged-in browser. On a dedicated VPS, it is short and you choose it. The server holds only the project, a minimal-scope token and an unprivileged user. It can be destroyed and rebuilt without touching your workstation. A VPS does not make the agent safe: it limits its blast radius, and that is the only promise of this guide.
Prerequisites: what you need to build the VPS
Coding agents call a provider's API: the model's computation does not run on your server, so the VPS can stay modest. Plan for 2 vCPUs, 4 GB of RAM and 40 GB of disk for an ordinary Node or PHP project, more if you compile or run containers. You need root access, Debian or Ubuntu LTS, a dedicated IPv4 address and an SSH key to connect.
On the network side, a single inbound port is open: 22, for SSH. Outbound, the agent's user can only reach port 8888 on the loopback interface, where a proxy listens and relays to a short list of hosts. So note down before you start the hosts your tool needs: the npm registry, the provider's API, the project's Git repository. The commands below are written for Debian and Ubuntu, with apt, ufw and nftables, and assume an administration account with sudo, separate from the agent's user.
Build the VPS in nine steps
Create an unprivileged user
sudo adduser --disabled-password --gecos "" agentid agentsudo -l -U agent
The second command must not list thesudogroup, and the third must answer that the user is not allowed to run sudo. The account has no password: you enter it withsudo -iu agentfrom your administration account. Plan one VPS per client, so that theagentuser of a server only sees that client's projects.Reserve one directory per project
sudo install -d -o agent -g agent -m 700 /srv/agents/projet-a
Always start the agent from this root, never from/,~or a downloads folder: a broad parent directory loads instruction files you have not reviewed. To fetch the code, enter the agent's session then clone:sudo -iu agentgit clone <url-du-dépôt> /srv/agents/projet-a
Use a read-only deploy key, generated on this VM and specific to the project's repository.Cut access to your keys
sudo -iu agent printenv SSH_AUTH_SOCKecho 'AllowAgentForwarding no' | sudo tee /etc/ssh/sshd_config.d/10-agents.confsudo sshd -t && sudo systemctl reload ssh
The first command must print nothing: no SSH agent is forwarded into the coding agent's session. Connect to the VM withssh -o ForwardAgent=no. Do not place any production deploy key on this server, nor any configuration file from another client.Limit environment variables to the project
sudo -iu agentinstall -m 600 /dev/null ~/.agent-env
Then edit~/.agent-envto hold only the provider's token, with minimal scope, dedicated to this VM and revocable. Start the agent with an emptied environment, so it inherits no variable from your session:env -i HOME="$HOME" PATH="$PATH" TERM="$TERM" HTTPS_PROXY=http://127.0.0.1:8888 bash -c 'set -a; . ~/.agent-env; cd /srv/agents/projet-a && exec claude'
Replaceclaudewith your tool's command. If the tool signs in with an account (OAuth) rather than an API key, the token lives in the user's home directory: that is the token the study saw exposed with Claude Code. Prefer a dedicated API key, which you revoke when you destroy the server.Close the inbound side with ufw
sudo ufw default deny incomingsudo ufw limit 22/tcpsudo ufw enable
Keep a second SSH session open while you enable it.ufwfilters by address and port, not by host name or user: that is why outbound traffic is handled in the next two steps, with a proxy andnftables.Install an allowlist outbound proxy
sudo apt install tinyproxy
In/etc/tinyproxy/tinyproxy.conf, setListen 127.0.0.1,Port 8888,FilterDefaultDeny Yes,Filter "/etc/tinyproxy/allowlist", then write the list:printf '%s\n' '^registry\.npmjs\.org$' '^api\.anthropic\.com$' | sudo tee /etc/tinyproxy/allowlistsudo systemctl restart tinyproxy
Each line is a regular expression on the host name. Replace the example API with your provider's, taken from its documentation, and add^github\.com$only if the project requires it. The tinyproxy documentation describes domain-based filtering, but does not say it applies to the host of HTTPS (CONNECT) tunnels: that is undocumented, so it must be tested. Do not take this filter for granted before the test in step 8.Block every other outbound path with nftables
Append to the end of
/etc/nftables.conf, after theflush rulesetline:table inet agent_egress {chain output {type filter hook output priority 0; policy accept;meta skuid "agent" oifname "lo" tcp dport 8888 acceptmeta skuid "agent" limit rate 5/minute log prefix "agent-egress-drop: "meta skuid "agent" counter drop}}
Theagentuser can now only reach the local proxy; everything else is logged then dropped. Check the syntax before enabling:sudo nft -c -f /etc/nftables.confsudo systemctl enable --now nftablesTest that the filter holds
sudo -u agent curl -sS --max-time 5 https://example.comsudo -u agent curl -sS -x http://127.0.0.1:8888 https://example.comsudo -u agent curl -sS -x http://127.0.0.1:8888 -o /dev/null -w '%{http_code}\n' https://registry.npmjs.org/-/ping
The first command must time out, because the firewall drops the direct connection. The second must fail with a proxy refusal (with curl, a message likeCONNECT tunnel failed; the exact code is in the tinyproxy log). The third must answer 200. These are the expected results: observe them on your own VM. If the second command succeeds, the filter does not cover HTTPS tunnels: switch to a proxy whose documentation covers CONNECT, and fix this before you start the agent.Plan for destroying the server
Keep these commands in a versioned script. A suspect VPS is deleted and rebuilt from the original image, and you revoke the provider's token. For your own access to the server, go through a bastion rather than direct exposure: the bastion host guide linked at the bottom of the page covers this point.
Each safeguard limits one specific thing
Scroll the table
| Safeguard | What it limits | What it lets through |
|---|---|---|
| Human approval of commands | Every command that runs | A command that looks harmless, approved out of habit |
| The tool's sandbox | The agent's actions, depending on its settings | Whatever depends on a forgotten setting or is specific to the tool |
| Unprivileged user | Root rights, system files, other accounts | The user's own files, including its token |
| Narrow project directory | Instruction files loaded by mistake | The project's own content |
| Filtered outbound traffic | An install from an unplanned host, a send to an unknown host (once the filter is verified) | Anything going through an allowed host, registry or API |
| No production key on the VM | Access to production servers and other clients | What the provider token and the project repository expose |
| Dedicated, rebuildable VPS | Persistence after compromise, the workstation | The client's data present on the VM |
Review instruction files before an agent follows them
An instruction file acts like code, through the agent: review it as such. To list them, run find /srv/agents -type f \( -name AGENTS.md -o -name CLAUDE.md \) -not -path '*/node_modules/*' . To detect a change, keep a fingerprint:find /srv/agents -type f \( -name AGENTS.md -o -name CLAUDE.md \) -not -path '*/node_modules/*' -exec sha256sum {} + | sort -k2 > ~/instructions.sha256
then compare before each session with find /srv/agents -type f \( -name AGENTS.md -o -name CLAUDE.md \) -not -path '*/node_modules/*' -exec sha256sum {} + | sort -k2 | diff ~/instructions.sha256 - . In a repository, git log -p -- AGENTS.md CLAUDE.md shows what changed since your last review.
Tools do not all look for their files in the same places (current directory, parent folders, home folder): read your tool's documentation and keep those locations empty, or known. Never start an agent in ~/Downloads or in a broad parent folder: it is the study's first mitigation.
The function-color package case: what the filter changes, and what it does not
In the documented case, the booby-trapped file asked to install the npm package "function-color". The report does not say from which registry. Our reading, which is a mitigation and not a research result: egress restricted to a list of hosts should block or flag, once the filter is verified in step 8, an install from an unplanned registry or server, and the proxy log shows it.
It does not block a package published on the allowed registry, since registry.npmjs.org is on the list. Against that case, rely on reviewing the instruction file, on a list of approved packages and, above all, on having no secrets to steal. The npm supply chain guide, linked below, covers that part.
Count the refusals, and cut install scripts
Read the refusals every day: sudo journalctl -k --since today | grep agent-egress-dropsudo grep -i filtered /var/log/tinyproxy/tinyproxy.log
A repeated refusal to an unknown host is a signal to understand before adding the host to the list: note the name, find out why the tool calls it, then decide. On the npm side, npm config set ignore-scripts true (run as agent) stops dependencies' install scripts from running automatically.
Troubleshooting: the errors you will meet
The messages below are the ones the components of this setup produce. Each has a remedy that never involves weakening the filter.
- A sudo refusal for
agent— expected behavior: the user has no privilege, and the exact wording of the refusal depends on the sudo version. Do the operation with your administration account, and never addagentto thesudogroup. - A
CONNECTtunnel failure (with curl, a message likeCONNECT tunnel failed) — the proxy probably refused the host; the exact code depends on tinyproxy and is in its log. Read its name in/var/log/tinyproxy/tinyproxy.log, then add it to the list only if the tool needs it. - Depending on the tool, a timeout or a network failure (for example
ETIMEDOUTorfetch failed) — the tool attempts a direct connection thatnftablesdrops. Check thatHTTPS_PROXYis exported in the agent's session. Some clients ignore this variable: they then fail closed, and their documentation says whether they accept a proxy. Permission deniedwhen reading another user's folder — that is the isolation working. If the agent needs a file, copy it into the project directory.- SSH connection cut after
ufw enable— the port 22 rule was missing, or SSH listens elsewhere. From the VPS console, runsudo ufw statusthensudo ufw allow <port>/tcp.
What this server does not solve
The VPS limits the blast radius, it does not remove the risk. The provider's token is on the machine: a hijacked agent can use it until you revoke it. The client's code is there too, and a leak remains possible through an allowed host, since a registry or a Git repository also accepts incoming data. The researchers recommend keeping the tool's approval prompts and sandbox: this setup adds to them, it does not replace them. Finally, a development server that serves public traffic needs its own hardening, covered in the checklist linked below.
For an agency: one VPS per client
The isolation easiest to explain to a client is physical: one VPS per client, with its own user, token and firewall. No production key and no other client lives there, so a hijacked agent does not go beyond that perimeter. Keep a single setup script for all servers: an outbound rule that changes is replayed everywhere. At the end of an engagement, you delete the VPS, revoke the token and move on with no residue on your workstation. A ServOrbit.com VPS provides root access, a dedicated IPv4 address and a choice of OS: that is the base of this design.
Official documentation
BleepingComputer's report on the Forever Security research: https://www.bleepingcomputer.com/news/security/ai-coding-agents-hijacked-using-agentsmd-file-in-downloads-folder/
nftables documentation: https://wiki.nftables.org/wiki-nftables/index.php/Quick_reference-nftables_in_10_minutes
tinyproxy documentation: https://tinyproxy.github.io/
adduser manual page (Debian): https://manpages.debian.org/stable/adduser/adduser.8.en.html
npm configuration: https://docs.npmjs.com/cli/using-npm/config