Inyección de prompts en agentes de programación
Un agente ejecuta texto que puede escribir un atacante. Identifica las superficies de inyección en el servidor y prioriza defensas que reducen el daño real.
Qué es la inyección de prompts contra un agente de programación
La inyección de prompts contra un agente de programación se puede describir de forma sencilla: el texto que lee el agente se trata como una instrucción que el agente sigue. El agente abre un archivo, un comentario de una pull request, una página web o el resultado de una llamada a una herramienta. Todo llega como el mismo tipo de texto que la solicitud del usuario. Si un atacante controla parte de ese texto, está escribiendo en la sesión.
Todos los productos de agentes actuales tienen esta propiedad. El modelo recibe una única secuencia de tokens. La solicitud del usuario, el prompt del sistema, el contenido de los archivos y los resultados de las herramientas se unen, y el modelo predice qué aparece a continuación. No existe un bit de privilegio asociado a un token. El formato no indica qué parte ha autorizado el usuario ni cuál procede del archivo README de un tercero.
Esta página presenta el modelo de amenazas: cómo llega el texto controlado por un atacante a un agente que se ejecuta en un servidor, qué obtiene el atacante en cada punto y qué defensas justifican el esfuerzo. Las recomendaciones de contención de nuestras otras guías sólo tienen sentido cuando se sabe de qué se está conteniendo al agente.
Por qué el modelo no puede separar el contenido de las instrucciones
El entrenamiento ayuda, pero no resuelve el problema. Los modelos actuales se entrenan para tratar con desconfianza el texto recuperado y rechazan muchos intentos rudimentarios. Un rechazo es una probabilidad, no una regla. Un atacante puede reformular el contenido, volver a intentarlo y ocultar el texto en un formato que nadie haya previsto. Además, nada limita cuántas formulaciones puede probar.
El proyecto OWASP GenAI registra este problema como LLM01:2025 Inyección de prompts y lo divide en dos categorías. La inyección directa consiste en que el propio prompt del usuario cambia el comportamiento del modelo. La inyección indirecta consiste en que contenido externo, como un sitio web o un archivo, cambia el comportamiento cuando el modelo lo procesa. La inyección indirecta es la que importa en un servidor, porque un agente lee mucho más texto del que se escribe manualmente.
El primer estudio sistemático es el de Greshake y sus colaboradores, No es lo que esperaba (2023). Esta es la conclusión que debe conservarse: cuando una aplicación introduce texto recuperado en un modelo que puede llamar a herramientas, procesar ese texto se aproxima a la ejecución arbitraria de código.
La condición que convierte una lectura en una intrusión
Leer texto malicioso no causa daños por sí solo. El daño necesita una vía de salida de la máquina.
Simon Willison denominó a esta combinación la tríada letal en junio de 2025. Un agente que contiene datos privados, está expuesto a contenido no confiable y puede enviar datos al exterior puede ser manipulado para sacar los primeros mediante el tercer elemento.
Un agente de programación en su VPS cumple las tres condiciones desde el primer día. Los datos privados son su código fuente, su archivo .env, sus claves SSH y su historial del shell. El contenido no confiable es cada repositorio, página y resultado de herramienta que lee. La vía de salida es git push, curl, npm publish, el cuerpo de una solicitud de incorporación de cambios o un enlace que se muestra en su terminal y en el que hace clic.
No puede eliminar la segunda condición, porque leer texto no confiable es la función para la que contrató al agente. Por eso, toda defensa práctica actúa sobre las otras dos.
Dónde llega texto no confiable a un agente de codificación en un servidor
El repositorio en el que trabaja el agente
Cada archivo del checkout es una entrada. Comentarios del código fuente, README.md, registros de cambios, fixtures de pruebas, código incluido de terceros y los propios archivos de instrucciones del agente: CLAUDE.md, AGENTS.md y sus equivalentes. Un agente al que se le pide entender una base de código los lee, porque eso es lo que se le ha solicitado.
Lo que obtiene un atacante es alcance sobre todas las personas que clonan el repositorio y dirigen un agente hacia él. Los archivos de instrucciones son la vía más directa, porque existen para leerse como instrucciones. Una pull request que añade cuatro líneas útiles a CLAUDE.md y una línea que redirige al agente es un cambio que un revisor humano puede pasar por alto.
Issues, pull requests y comentarios de revisión de código
Cualquier texto que un desconocido pueda escribir en el sistema de seguimiento llega al agente en cuanto se le pide clasificarlo. En mayo de 2025, Invariant Labs publicó un hallazgo de GitHub MCP con esta estructura exacta. El agente de un desarrollador tenía acceso a un repositorio público y a otros privados. Un atacante creó un issue en el repositorio público. Cuando el desarrollador pidió al agente que revisara los issues abiertos, el agente leyó contenido de repositorios privados y lo escribió en una pull request del lado público.
Ese informe describe un problema arquitectónico, no un defecto de código en el servidor MCP. El agente tenía un token de acceso amplio, leía de una bandeja de entrada pública y tenía permisos de escritura. No había una configuración incorrecta en el sentido habitual, por lo que la solución consiste en limitar el alcance, no en aplicar un parche.
Páginas web que obtiene el agente
Documentación, respuestas de foros, una página de un proveedor o un resultado de búsqueda. Cualquiera de ellos puede contener texto escrito para el agente y no para usted. Convertir HTML en texto ofrece más espacio a un atacante, porque el contenido que un navegador nunca muestra sigue llegando al modelo.
Un atacante obtiene control en el momento en que menos se supervisa al agente. Nadie lee el texto completo de una página que el agente ha obtenido mientras buscaba información.
Salida de herramientas MCP
MCP (model context protocol) es la forma habitual de conectar agentes con herramientas externas. Los resultados vuelven como texto y pasan directamente a la ventana de contexto. Aquí hay dos superficies, no una. La primera es la información que devuelve una herramienta. La segunda es el nombre y la descripción de la propia herramienta, que el modelo lee para decidir cuándo llamarla. Un servidor que usted no controla puede cambiar cualquiera de las dos entre llamadas.
Un atacante que introduce texto en la salida de una herramienta alcanza todas las demás herramientas disponibles para el agente. Así, una inyección en algo de poco valor puede terminar controlando algo de alto valor.
Registros de CI, salida de compilación y metadatos de dependencias
npm install imprime texto procedente de paquetes que usted no ha escrito. Un fallo de prueba imprime un mensaje de aserción de una biblioteca. El registro de un trabajo de integración continua (CI) contiene miles de líneas de salida de terceros. Si pide a un agente que corrija una compilación fallida, leerá todo ese contenido.
Aquí un atacante obtiene acceso a la máquina de compilación, que normalmente contiene credenciales de despliegue y tokens de registro, y recibe menos atención que un portátil.
Lo que realmente obtiene un atacante
Conviene prepararse para cuatro resultados.
Robo de credenciales. Todo lo que el proceso del agente pueda leer está dentro del alcance: variables de entorno, ~/.aws/credentials, ~/.ssh, un token de gh o un archivo de configuración de Docker. Para exfiltrarlos no hace falta curl. Un commit en una rama, la descripción de una pull request, un paquete publicado en un registro o una consulta DNS para un nombre que controle el atacante pueden sacar datos del servidor.
Cambios de código que aprueba. Escribir código es la función del agente, por lo que conseguir que escriba una línea sutilmente incorrecta es el resultado más fácil de alcanzar. Puede ser una dependencia añadida o una llamada de registro que incluya un token en un registro que después envíe a otro lugar.
Persistencia. Un archivo escrito una vez sigue funcionando sin intervención del modelo: un hook en .git/hooks, un script de postinstall en package.json, una línea añadida a un archivo de inicio del shell o una línea adicional en CLAUDE.md. El siguiente comando lo ejecuta.
Movimiento dentro de la red. El agente se ejecuta donde usted lo coloque. Si ese servidor puede acceder a una base de datos en loopback, a un servicio de administración interno, al servicio de metadatos de su proveedor cloud o a otro host de la red privada, también puede hacerlo cualquier proceso que controle el agente.
Los modos de aprobación automática eliminan la última comprobación
En el modo predeterminado, Claude Code solicita confirmación antes de ejecutar un comando o editar un archivo. Esa solicitud es la comprobación humana entre cada una de las superficies anteriores y una acción real. Los modos que eliminan la solicitud también eliminan la comprobación.
La documentación es clara sobre bypassPermissions: úselo sólo en entornos aislados, como contenedores o máquinas virtuales, donde Claude Code no pueda causar daños. El modo automático es menos restrictivo y aprueba automáticamente las llamadas a herramientas mediante comprobaciones de seguridad en segundo plano que verifican que las acciones coincidan con su solicitud. Estas comprobaciones detectan muchos problemas. Aun así, se basan en el criterio del modelo sobre la salida del modelo, por lo que debe tratarlas como un filtro y no como un límite de seguridad.
Un administrador puede eliminar ambas comprobaciones. Establezca permissions.disableBypassPermissionsMode o permissions.disableAutoMode en "disable" en un archivo de configuración y coloque ese archivo en la configuración administrada para impedir que un proyecto descargado lo sustituya. Nuestra guía sobre el modo automático y las reglas de permisos de Claude Code explica dónde se aplica cada regla.
Defensas, ordenadas según lo que aportan
Ninguna de estas medidas soluciona el problema por sí sola. Cada una limita lo que el agente puede conservar o lo que puede hacer con ello.
- Una máquina que pueda destruir y reconstruir, para que una intrusión le cueste una hora en lugar de convertirse en un incidente.
- Credenciales separadas de las suyas, limitadas a un repositorio y con una vida útil corta.
- Ningún secreto de larga duración en el entorno que heredan los comandos del agente.
- Controles del sistema operativo sobre las conexiones salientes y el acceso a archivos, aplicables a todos los procesos que inicia el agente.
- Solicitudes de aprobación activadas para las escrituras y las conexiones de red.
- Hooks como respaldo determinista para las acciones concretas que pueda identificar.
- Revisar el diff antes de integrarlo.
El orden importa. Los puntos 1 a 4 siguen siendo válidos aunque el modelo esté completamente bajo el control de un atacante. Los puntos 5 a 7 dependen de que una persona preste atención, que es precisamente lo que deja de ocurrir durante una ejecución prolongada del agente.
Ejecute el agente en una máquina que pueda desechar
Un VPS que contiene un checkout y un token limitado es un objetivo mucho menos valioso que un portátil con sus claves. Ejecute el agente con su propio usuario sin privilegios. No use su cuenta de inicio de sesión ni root. Nuestras guías sobre una VM desechable para agentes de programación y usuarios con privilegios mínimos en un VPS explican la configuración. ejecutar Claude Code de forma segura en un VPS explica el funcionamiento diario.
Retire los secretos del entorno
Todas las variables de entorno son legibles para cada proceso hijo. Esto incluye todos los comandos que ejecuta el agente. El sandbox de Claude Code puede anular variables con nombre antes de cada comando dentro del sandbox. En Linux, el sandbox necesita primero dos paquetes:
sudo apt-get install bubblewrap socatDespués, en ~/.claude/settings.json:
{
"sandbox": {
"enabled": true,
"network": {
"allowedDomains": ["github.com", "*.npmjs.org"]
},
"credentials": {
"envVars": [
{ "name": "GITHUB_TOKEN", "mode": "deny" },
{ "name": "NPM_TOKEN", "mode": "deny" }
]
}
}
}Una entrada deny anula esa variable antes de ejecutar cada comando dentro del sandbox. allowedDomains contiene los comandos dentro del sandbox dirigidos a los hosts que indique. El bloque credentials necesita Claude Code v2.1.187 o posterior, comprobado en August 2026. Ejecute /sandbox en una sesión para ver qué capas están activas y qué dependencias faltan. Decidir qué secretos deben existir en esa máquina es una parte importante del trabajo. mantener los secretos fuera del alcance de un agente de IA explica este proceso.
Limite las conexiones salientes desde el sistema operativo
Una regla de firewall no depende de lo que decida el modelo. Ejecute el agente con un usuario agent dedicado y descarte las conexiones que envíe ese usuario:
table inet agentcage {
chain output {
type filter hook output priority filter; policy accept;
meta skuid "agent" ct state established,related accept
meta skuid "agent" oif lo accept
meta skuid "agent" counter drop
}
}Así, el usuario agent sólo conserva el acceso a loopback. Su tráfico debe pasar por un proxy que ejecute en la misma máquina, y el proxy contiene la lista de hosts permitidos. Con https_proxy apuntando a ese proxy, el cliente envía una solicitud CONNECT y el proxy resuelve el nombre. Por tanto, el agente no necesita su propio DNS saliente (domain name system). Compruebe el resultado con sudo nft list ruleset y observe cómo aumenta el contador de la regla de descarte cuando el agente intenta acceder a algo nuevo.
Mantenga abierta una segunda sesión SSH mientras aplica cambios en el firewall. Compruebe también cómo el runtime de contenedores modifica estas reglas. Docker escribe sus propias cadenas, y los puertos publicados de Docker omiten ufw explica el comportamiento inesperado que esto provoca.
Hooks: la comprobación que el modelo no puede eludir con argumentos
Claude Code aplica las reglas de permisos y los hooks, no el modelo. La documentación lo indica claramente: las instrucciones del prompt o de CLAUDE.md determinan lo que Claude intenta hacer, pero no cambian lo que Claude Code permite. Esa diferencia es el valor principal. Una línea en CLAUDE.md que diga "never run curl" es una sugerencia que un párrafo inyectado puede rebatir. Un hook es un proceso que devuelve un código de salida.
Registre un hook PreToolUse en .claude/settings.json:
{
"hooks": {
"PreToolUse": [
{
"matcher": "Bash",
"hooks": [
{
"type": "command",
"command": "${CLAUDE_PROJECT_DIR}/.claude/hooks/no-egress.sh"
}
]
}
]
}
}El hook recibe la llamada de la herramienta como JSON por la entrada estándar. El código de salida 2 bloquea la llamada y muestra a Claude el motivo escrito en la salida de error estándar. El código de salida 0 permite que la llamada continúe por el flujo normal de permisos.
#!/usr/bin/env bash
# PreToolUse: stdin holds the tool call, exit 2 blocks it.
cmd=$(jq -r '.tool_input.command // ""')
if printf '%s' "$cmd" | grep -qE '(^|[;&|]|\s)(curl|wget|nc|ncat)(\s|$)'; then
echo "Blocked: this repository does not allow outbound network commands." >&2
exit 2
fi
exit 0Ahora, la parte importante. Esto es una lista de denegación aplicada a una cadena de shell, y las listas de denegación sobre cadenas de shell tienen fugas. python3 -c abre un socket sin usar la palabra curl. Un destino make deploy oculta la misma llamada un nivel más abajo. Escriba hooks para los errores que pueda identificar y coloque el límite en el kernel o en la red, que es donde realmente se basa su seguridad.
Las reglas de denegación de permisos tienen una limitación de coincidencia que conviene conocer. Las reglas de denegación Read y Edit cubren las herramientas de archivos propias de Claude y los comandos de archivos que reconoce en Bash, como cat, head, tail y sed. No cubren un script de Python o Node que abra el archivo directamente. Las reglas se evalúan primero como deny, después como ask y por último como allow. Por tanto, una regla de denegación no puede incluir una excepción mediante una lista de permitidos.
{
"permissions": {
"deny": [
"Read(.env)",
"Read(./secrets/**)",
"Bash(git push *)"
]
}
}Supervise lo que sale y revise el diff
Una ejecución del agente produce un diff y un conjunto de conexiones de red. Ambos deben revisarse antes de integrar o desplegar cualquier cambio. Una revisión de seguridad autoalojada del diff detecta una clase de cambios distinta de la que detecta una revisión superficial humana. saber qué envía un agente de programación fuera de la máquina permite conocer el aspecto del tráfico normal, de modo que una solicitud extraña destaque.
Qué sigue sin resolverse
Hoy no existe una separación fiable entre el contenido y las instrucciones. Cada defensa disponible es un filtro con una tasa de fallos o un límite sobre las consecuencias. Ningún elemento de la pila marca un fragmento de texto como datos que nunca deben obedecerse.
Los filtros son útiles, pero también fallan. Un clasificador que detecta la mayoría de los intentos de inyección debe acertar siempre, mientras que el atacante sólo necesita acertar una vez. Esta asimetría explica por qué la tasa de éxito publicada para una defensa es un punto de partida para el siguiente intento, no una garantía.
El trabajo más prometedor se sitúa en el nivel del diseño, no en el del modelo. CaMeL, de Derrotar las inyecciones de prompts mediante el diseño (Debenedetti y sus colaboradores, 2025), extrae primero el flujo de control y el flujo de datos de la solicitud de confianza. Así, los datos no confiables no pueden cambiar el comportamiento del programa. Después aplica comprobaciones de capacidades cuando se llaman herramientas. Las propias cifras del artículo en el benchmark AgentDojo muestran el coste de este enfoque.
The data behind this chart
[
{
"label": "Undefended agent",
"tasks_solved_pct": 84
},
{
"label": "CaMeL",
"tasks_solved_pct": 77
}
]El agente sin protección resolvió el 84 por ciento de las tareas. CaMeL resolvió el 77 por ciento con una garantía de seguridad asociada. Son las cifras publicadas en el artículo para un benchmark concreto, no una medición de su carga de trabajo. La diferencia entre ambas cifras representa aproximadamente lo que cuesta hoy una garantía real.
Hasta que un diseño de este tipo esté disponible en las herramientas que usa a diario, dé por hecho que el agente quedará comprometido en algún momento y haga que ese incidente sea poco relevante. Ese es el fundamento de usar una máquina desechable, credenciales con permisos limitados, tráfico saliente controlado y el hábito de revisar el diff.
FAQ
¿Puedo detener la inyección de prompts indicando al agente que ignore las instrucciones de los archivos?
No. Esa frase está en la misma ventana de contexto que el ataque y compite con el texto del atacante en igualdad de condiciones. La documentación de Claude Code establece claramente la diferencia: las instrucciones del prompt o CLAUDE.md determinan lo que el agente intenta hacer, pero no cambian lo que la herramienta permite. Trate un archivo de instrucciones como una declaración de intención y coloque todo aquello de lo que dependa en reglas de permisos, un hook PreToolUse o una regla de firewall.
¿La inyección de prompts supone un riesgo real si el agente sólo accede a mi propio repositorio?
Sí, porque el repositorio contiene mucho texto que usted no escribió. Los archivos README de las dependencias, las URL de los lockfiles, los dispositivos de prueba, el código incluido de terceros y la salida de npm install se incorporan durante una tarea normal. Todo lo que se obtiene de un sistema de seguimiento de incidencias o de un sitio de documentación llega de la misma forma. El riesgo aumenta cuanto más contenido lee el agente, y un agente útil lee mucho.
¿Ejecutar el agente en un contenedor resuelve el problema?
Limita los daños, pero sólo si también retira las credenciales. Un contenedor con el SSH agent reenviado, credenciales de servicios cloud en el entorno y acceso de red sin restricciones entrega a un atacante casi todo lo que tendría el host. Lo que realmente aporta el contenedor es un sistema de archivos que puede eliminar y un entorno limpio donde aplicar reglas de salida. Combínelo con un token limitado a un único repositorio.
¿Qué cambio único reduce más el riesgo?
Retire las credenciales de larga duración del entorno que heredan los comandos del agente y, después, aplique a esa máquina una política de salida con denegación predeterminada. Juntos, estos cambios rompen la tercera condición de la tríada letal: el texto aún puede secuestrar al agente, pero los datos a los que accede no tienen ningún destino útil. Los prompts de aprobación y la revisión de diferencias también ayudan, pero dependen de que una persona se mantenga alerta durante una ejecución larga. Por eso ocupan un nivel inferior a esos dos cambios.