Lo que muestra la investigación de Forever Security
El 25 de octubre de 2026, BleepingComputer informó del trabajo de Forever Security. Los investigadores encontraron, en la carpeta ~/Downloads de un cliente, un archivo AGENTS.md trampa que pedía a un agente de código instalar un paquete npm llamado «function-color». Esa carpeta era también el directorio de trabajo del agente, y los agentes cargan automáticamente los archivos de instrucciones de su directorio de trabajo. El escenario reconstruido es este: una página web deja el archivo en silencio en las descargas, el usuario lanza su agente en esa carpeta y el agente sigue las instrucciones con las herramientas y los derechos que ya tiene.
Con Codex de OpenAI, la cadena completa, con una carga inofensiva (abrir la calculadora), tuvo éxito en el 90 % de las pruebas. Con Claude Code, un CLAUDE.md trampa redirigía el tráfico de la API hacia un servidor de los investigadores, lo que exponía el token OAuth y permitía ejecutar código en cerca del 50 % de las pruebas. Los investigadores consideran que no hay nada que corregir por parte de OpenAI ni de Anthropic: el agente funciona como está previsto.
Lo que el estudio establece y lo que no dice
- Establecido — un archivo de instrucciones colocado en el directorio de trabajo basta para orientar al agente, sin que se explote ningún fallo de software.
- Condición — el ataque exige que el usuario lance el agente en la carpeta afectada. Según BleepingComputer, OpenAI y Anthropic recomiendan, por cierto, lanzar el agente en el directorio del proyecto.
- Cargas inofensivas — las tasas del 90 % y de cerca del 50 % proceden de pruebas con una carga sin peligro; no miden ataques reales y no se generalizan a todas las herramientas.
- Entrega desconocida — los investigadores no pudieron determinar cómo llegó el archivo del cliente a sus descargas: la página web sigue siendo una hipótesis.
- Cursor CLI — citado como lanzado desde esa carpeta, pero no descrito como probado.
- Nuestra lectura — un repositorio clonado puede contener su propio archivo de instrucciones. Es una deducción nuestra, no un resultado del estudio.
Lanzar el agente en la carpeta correcta y validar los comandos: ¿por qué un servidor aparte?
La validación humana y el sandbox de la herramienta son salvaguardas reales y conviene mantenerlas activas: el artículo fuente también lo recomienda. Aun así, dependen de una atención constante, de una herramienta bien configurada y de un lanzamiento en la carpeta correcta. El día que falla una de las tres, la pregunta pasa a ser: ¿qué puede alcanzar el agente desviado?
En un puesto de trabajo, la respuesta es larga: claves SSH, tokens de la nube, repositorios de otros clientes, un navegador con la sesión abierta. En un VPS dedicado, es corta y la elige usted. El servidor solo contiene el proyecto, un token de alcance mínimo y un usuario sin privilegios. Se destruye y se recrea sin tocar el puesto. Un VPS no vuelve seguro al agente: acota su radio de acción, y esa es la única promesa de esta guía.
Requisitos: lo necesario para montar el VPS
Los agentes de código llaman a la API de un proveedor: el cálculo del modelo no se hace en su servidor, así que el VPS puede ser modesto. Prevea 2 vCPU, 4 GB de RAM y 40 GB de disco para un proyecto Node o PHP corriente, más si compila o lanza contenedores. Hace falta acceso root, Debian o Ubuntu LTS, una IPv4 dedicada y una clave SSH para conectarse.
En cuanto a la red, solo se abre un puerto de entrada: el 22, para SSH. De salida, el usuario del agente solo alcanza el puerto 8888 del bucle local, donde escucha un proxy que retransmite hacia una lista corta de hosts. Anote por tanto antes de empezar los hosts que necesita su herramienta: el registro npm, la API del proveedor, el repositorio Git del proyecto. Los comandos siguientes están escritos para Debian y Ubuntu, con apt, ufw y nftables, y suponen una cuenta de administración con sudo, distinta del usuario del agente.
Montar el VPS en nueve pasos
Crear un usuario sin privilegios
sudo adduser --disabled-password --gecos "" agentid agentsudo -l -U agent
El segundo comando no debe listar el gruposudo, y el tercero debe responder que el usuario no está autorizado a ejecutar sudo. La cuenta no tiene contraseña: se entra consudo -iu agentdesde su cuenta de administración. Prevea un VPS por cliente, para que el usuarioagentde un servidor solo vea los proyectos de ese cliente.Reservar un directorio por proyecto
sudo install -d -o agent -g agent -m 700 /srv/agents/projet-a
Lance siempre el agente desde esa raíz, nunca desde/,~ni una carpeta de descargas: un directorio padre amplio carga archivos de instrucciones que no ha revisado. Para obtener el código, entre en la sesión del agente y clone:sudo -iu agentgit clone <url-du-dépôt> /srv/agents/projet-a
Use una clave de despliegue de solo lectura, generada en esta VM y propia del repositorio del proyecto.Cortar el acceso a sus claves
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
El primer comando no debe mostrar nada: ningún agente SSH se transmite a la sesión del agente de código. Conéctese a la VM conssh -o ForwardAgent=no. No deposite ninguna clave de despliegue de producción en este servidor, ni ningún archivo de configuración de otro cliente.Limitar las variables de entorno al proyecto
sudo -iu agentinstall -m 600 /dev/null ~/.agent-env
Edite después~/.agent-envpara dejar solo el token del proveedor, de alcance mínimo, dedicado a esta VM y revocable. Lance el agente con un entorno vaciado, para que no herede ninguna variable de su sesión: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'
Sustituyaclaudepor el comando de su herramienta. Si la herramienta se conecta con una cuenta (OAuth) en lugar de una clave de API, el token vive en el directorio personal del usuario: es el token que el estudio vio expuesto con Claude Code. Prefiera una clave de API dedicada, que revoca al destruir el servidor.Cerrar la entrada con ufw
sudo ufw default deny incomingsudo ufw limit 22/tcpsudo ufw enable
Mantenga abierta una segunda sesión SSH durante la activación.ufwfiltra por dirección y puerto, no por nombre de host ni por usuario: por eso la salida se configura en los dos pasos siguientes, con un proxy ynftables.Instalar un proxy de salida con lista blanca
sudo apt install tinyproxy
En/etc/tinyproxy/tinyproxy.conf, configureListen 127.0.0.1,Port 8888,FilterDefaultDeny Yes,Filter "/etc/tinyproxy/allowlist"y escriba después la lista:printf '%s\n' '^registry\.npmjs\.org$' '^api\.anthropic\.com$' | sudo tee /etc/tinyproxy/allowlistsudo systemctl restart tinyproxy
Cada línea es una expresión regular sobre el nombre de host. Sustituya la API de ejemplo por la de su proveedor, tomada de su documentación, y añada^github\.com$solo si el proyecto lo exige. La documentación de tinyproxy describe un filtrado por dominio, pero no dice que se aplique al host de los túneles HTTPS (CONNECT): no está documentado, así que hay que probarlo. No dé este filtro por supuesto antes de la prueba del paso 8.Prohibir al agente cualquier otra salida con nftables
Añada al final de
/etc/nftables.conf, después de la líneaflush ruleset: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}}
El usuarioagentya solo puede alcanzar el proxy local; el resto se registra y se descarta. Compruebe la sintaxis antes de activar:sudo nft -c -f /etc/nftables.confsudo systemctl enable --now nftablesComprobar que el filtro aguanta
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
El primer comando debe agotar el tiempo de espera, porque el cortafuegos descarta la conexión directa. El segundo debe fallar por un rechazo del proxy (con curl, un mensaje del tipoCONNECT tunnel failed; el código exacto figura en el registro de tinyproxy). El tercero debe responder 200. Son los resultados esperados: compruébelos en su propia VM. Si el segundo comando tiene éxito, el filtro no cubre los túneles HTTPS: pase a un proxy cuya documentación cubra CONNECT y corríjalo antes de lanzar el agente.Prever la destrucción del servidor
Guarde estos comandos en un script versionado. Un VPS sospechoso se elimina y se recrea desde la imagen original, y usted revoca el token del proveedor. Para su propio acceso al servidor, pase por un bastión en lugar de una exposición directa: la guía sobre el bastion host, enlazada al pie de la página, cubre este punto.
Cada salvaguarda acota algo concreto
Desplace la tabla
| Salvaguarda | Lo que acota | Lo que deja pasar |
|---|---|---|
| Validación humana de los comandos | Cada comando ejecutado | Un comando de apariencia inocua validado por costumbre |
| Sandbox de la herramienta | Las acciones del agente, según su configuración | Lo que depende de un ajuste olvidado o propio de la herramienta |
| Usuario sin privilegios | Derechos de root, archivos del sistema, otras cuentas | Los archivos del propio usuario, incluido su token |
| Directorio de proyecto estrecho | Los archivos de instrucciones cargados por error | El contenido del propio proyecto |
| Salida de red filtrada | Una instalación desde un host no previsto, un envío a un host desconocido (una vez verificado el filtro) | Todo lo que pasa por un host autorizado, registro o API |
| Ninguna clave de producción en la VM | El acceso a los servidores de producción y a otros clientes | Lo que exponen el token del proveedor y el repositorio del proyecto |
| VPS dedicado y recreable | La persistencia tras una intrusión, el puesto de trabajo | Los datos del cliente presentes en la VM |
Revisar los archivos de instrucciones antes de que un agente los siga
Un archivo de instrucciones actúa como código, a través del agente: revíselo como tal. Para hacer el inventario, ejecute find /srv/agents -type f \( -name AGENTS.md -o -name CLAUDE.md \) -not -path '*/node_modules/*' . Para detectar un cambio, conserve una huella:find /srv/agents -type f \( -name AGENTS.md -o -name CLAUDE.md \) -not -path '*/node_modules/*' -exec sha256sum {} + | sort -k2 > ~/instructions.sha256
y compare antes de cada sesión con find /srv/agents -type f \( -name AGENTS.md -o -name CLAUDE.md \) -not -path '*/node_modules/*' -exec sha256sum {} + | sort -k2 | diff ~/instructions.sha256 - . En un repositorio, git log -p -- AGENTS.md CLAUDE.md muestra lo que ha cambiado desde su última revisión.
No todas las herramientas buscan sus archivos en el mismo sitio (directorio actual, carpetas padre, carpeta personal): lea la documentación de la suya y mantenga esas ubicaciones vacías, o conocidas. No lance nunca un agente en ~/Downloads ni en una carpeta padre amplia: es la primera mitigación del estudio.
El caso del paquete function-color: lo que el filtro cambia y lo que no
En el caso documentado, el archivo trampa pedía instalar el paquete npm «function-color». El informe no precisa desde qué registro. Nuestra lectura, que es una mitigación y no un resultado de la investigación: una salida restringida a una lista de hosts debería bloquear o señalar, una vez verificado el filtro en el paso 8, la instalación desde un registro o un servidor no previsto, y el registro del proxy lo muestra.
No bloquea un paquete publicado en el registro autorizado, ya que registry.npmjs.org está en la lista. Frente a ese caso, cuente con la revisión del archivo de instrucciones, con una lista de paquetes aprobados y, sobre todo, con la ausencia de secretos que robar. La guía sobre la cadena de suministro de npm, enlazada más abajo, detalla esa parte.
Contar los rechazos y cortar los scripts de instalación
Lea cada día los rechazos: sudo journalctl -k --since today | grep agent-egress-dropsudo grep -i filtered /var/log/tinyproxy/tinyproxy.log
Un rechazo repetido hacia un host desconocido es una señal que hay que entender antes de añadir el host a la lista: anote el nombre, busque por qué lo llama la herramienta y decida después. En npm, npm config set ignore-scripts true (ejecutado como agent) impide la ejecución automática de los scripts de instalación de las dependencias.
Solución de problemas: los errores que va a encontrar
Los mensajes siguientes son los que producen los componentes de este montaje. Cada uno tiene un remedio que nunca pasa por debilitar el filtro.
- Un rechazo de sudo para
agent— comportamiento esperado: el usuario no tiene ningún privilegio, y el texto exacto del rechazo depende de la versión de sudo. Haga la operación con su cuenta de administración, sin añadir nuncaagental gruposudo. - Un fallo del túnel
CONNECT(con curl, un mensaje del tipoCONNECT tunnel failed) — el proxy probablemente rechazó el host; el código exacto depende de tinyproxy y figura en su registro. Lea su nombre en/var/log/tinyproxy/tinyproxy.logy añádalo a la lista solo si la herramienta lo necesita. - Según la herramienta, un tiempo de espera agotado o un fallo de red (por ejemplo
ETIMEDOUTofetch failed) — la herramienta intenta una conexión directa quenftablesdescarta. Compruebe queHTTPS_PROXYestá exportada en la sesión del agente. Algunos clientes ignoran esta variable: fallan entonces de forma cerrada, y su documentación indica si aceptan un proxy. Permission deniedal leer la carpeta de otro usuario — es el aislamiento funcionando. Si el agente necesita un archivo, cópielo en el directorio del proyecto.- Conexión SSH cortada tras
ufw enable— faltaba la regla del puerto 22, o SSH escucha en otro. Desde la consola del VPS, ejecutesudo ufw statusy despuéssudo ufw allow <puerto>/tcp.
Lo que este servidor no resuelve
El VPS acota el radio de acción, no elimina el riesgo. El token del proveedor está en la máquina: un agente desviado puede usarlo mientras no lo haya revocado. El código del cliente también está allí, y una fuga sigue siendo posible por un host autorizado, puesto que un registro o un repositorio Git también acepta datos de entrada. Los investigadores recomiendan mantener las solicitudes de aprobación y el sandbox de la herramienta: este montaje se suma a ellos, no los sustituye. Por último, un servidor de desarrollo que sirve tráfico público necesita su propio endurecimiento, tratado en la lista de verificación enlazada más abajo.
Para una agencia: un VPS por cliente
El aislamiento más fácil de explicar a un cliente es físico: un VPS por cliente, con su usuario, su token y su cortafuegos. Ninguna clave de producción ni ningún otro cliente figura allí, así que un agente desviado no sale de ese perímetro. Conserve un único script de puesta en marcha para todos los servidores: una regla de salida que cambia se reaplica en todas partes. Al final de una misión, elimina el VPS, revoca el token y sigue adelante sin residuos en su puesto. Un VPS de ServOrbit.com ofrece acceso root, una IPv4 dedicada y la elección del sistema operativo: es la base de este esquema.
La documentación oficial
Informe de BleepingComputer sobre la investigación de Forever Security: https://www.bleepingcomputer.com/news/security/ai-coding-agents-hijacked-using-agentsmd-file-in-downloads-folder/
Documentación de nftables: https://wiki.nftables.org/wiki-nftables/index.php/Quick_reference-nftables_in_10_minutes
Documentación de tinyproxy: https://tinyproxy.github.io/
Página de manual de adduser (Debian): https://manpages.debian.org/stable/adduser/adduser.8.en.html
Configuración de npm: https://docs.npmjs.com/cli/using-npm/config