SSD Nodes Learn Hosting plans →
Guías Matt ConnorPor Matt Connor

Cómo mantener secretos fuera de agentes de IA

Una clave de API en el entorno del agente puede filtrarse en una llamada de herramienta. Use tokens breves y limitados mediante una pasarela de credenciales.

Qué implica mantener los secretos fuera de los agentes de IA

Un agente de IA es un proceso normal de Linux que ejecuta comandos. El código que ejecuta puede leer todas las variables de entorno que contiene ese proceso. Por tanto, una clave de API en el entorno del agente es una clave que el agente puede enviar a cualquier host al que tenga acceso. Mantener los secretos fuera del agente significa proporcionarle un identificador en lugar de la clave: un token con alcance limitado y duración breve, o un marcador de posición que otro componente sustituye por el valor real en el límite de red.

Esto no trata de que un modelo se vuelva malicioso. El mecanismo es más simple. Un agente lee una página web, un README o un comentario de una incidencia que contiene instrucciones y las sigue, porque para un modelo de lenguaje no hay diferencia entre el texto que usted escribió y el texto que obtuvo de otra fuente. Eso es una inyección de instrucciones. Cuando ocurre, el alcance del daño queda limitado por un único factor: lo que el proceso puede leer. Si todavía no ha establecido un límite, ejecutar un agente de programación de forma segura en un servidor explica los niveles de aislamiento sobre los que se basa esta guía.

El modelo de amenazas en términos sencillos

Ejecute esto como el usuario con el que se ejecuta el agente.

tr '\0' '\n' < /proc/self/environ | grep -iE 'key|token|secret|password'

Cada línea que muestra equivale a una petición HTTP para el servidor de un tercero. Ahora revise qué hay en el disco cerca del agente.

grep -rIl --exclude-dir=.git -e 'API_KEY' -e 'SECRET' ~/projects
find ~ -maxdepth 3 -name '.env' -o -name 'credentials' -o -name '*.pem'

Un agente con una shell no necesita un exploit sofisticado para extraer esos datos. Cuatro vías habituales son suficientes, y las cuatro parecen trabajo normal en el registro:

  • Un curl o fetch saliente a cualquier host, con el valor en una cadena de consulta.
  • Un git commit y un git push a un repositorio en el que el agente tenga permisos de escritura.
  • Un script de instalación de paquetes, que ejecuta código arbitrario como el usuario del agente.
  • Una búsqueda DNS de un nombre de host que contenga el valor, que también funciona aunque se bloquee el tráfico HTTP saliente.

No puede resolver este problema mediante revisiones. La solución consiste en asegurarse de que no haya nada valioso al alcance.

Un secreto en el árbol de trabajo es un secreto dentro de la ventana de contexto

Un agente lee archivos. Un archivo .env del repositorio en el que trabaja se leerá y, una vez leído, quedará en la ventana de contexto. Esto significa que aparecerá en la transcripción, en cualquier registro que conserve y en todo lo que el agente escriba después.

Antes, con la clave dentro del árbol en el que trabaja el agente:

cd ~/projects/billing
cat .env
# STRIPE_SECRET_KEY=sk_live_...
# DATABASE_URL=postgres://app:hunter2@db.internal:5432/billing

Después, con el archivo fuera de su alcance:

sudo install -d -m 750 -o root -g agent-review /etc/agent-review
sudo install -m 640 -o root -g agent-review ~/projects/billing/.env /etc/agent-review/billing.env
rm ~/projects/billing/.env

El usuario del agente ya no puede abrir el archivo porque el árbol de trabajo ya no lo contiene. Las reglas de denegación de la configuración del propio agente son una segunda capa, no la primera. Claude Code lee las reglas de permisos de .claude/settings.json en el proyecto:

{
  "permissions": {
    "deny": ["Read(./.env)", "Read(./secrets/**)", "Read(./**/*.pem)"]
  }
}

Esto evita que el agente abra accidentalmente un archivo mientras explora. Además, las reglas se aplican más allá de las herramientas de archivos: Claude Code también las comprueba en los comandos de archivos que reconoce en Bash, como cat y head, y en los destinos de redirecciones como < .env. No impide que una instrucción inyectada ejecute grep -r KEY . dentro del directorio del proyecto ni que un script corto de Python abra .env por su cuenta, porque la documentación de permisos indica que las reglas de denegación no se aplican a los comandos que leen archivos sin nombrarlos ni a los subprocesos que abren archivos por su cuenta. Para esos casos, la documentación remite al sandbox de Claude Code, que aplica el bloqueo en el nivel del sistema operativo. Que se solicite confirmación antes de ejecutar un comando de ese tipo depende del modo de permisos de la sesión. El modo automático es ahora el modo inicial integrado para las sesiones interactivas de Claude Code, por lo que un servidor que no esté supervisando ejecutará más comandos de ese tipo sin solicitar confirmación. La misma limitación se aplica a todo lo que moldea los hábitos del agente en lugar de sus permisos: una skill que obliga al agente a aplicar el cambio mínimo que funciona evita que una ejecución explore archivos que no tenía motivos para abrir, pero sigue siendo una recomendación que el modelo puede ignorar si se le convence. Considere la configuración como una barrera de seguridad y los permisos del sistema de archivos como la pared. La misma separación se aplica dentro de los contenedores: los archivos de entorno y los secretos en Docker Compose cubren la versión de este problema situada un nivel más abajo.

Asigne a cada agente su propio usuario sin privilegios

Si el agente se ejecuta con su usuario, hereda sus claves SSH, sus credenciales de nube y el historial de su shell. Un usuario independiente requiere un solo comando y elimina todo eso.

sudo adduser --disabled-password --gecos "" agent-review
sudo chmod 700 /home/agent-review
sudo -u agent-review cat ~/.ssh/id_ed25519

La última línea debe fallar con cat: /home/you/.ssh/id_ed25519: Permission denied. Si muestra una clave, el usuario del agente puede acceder al archivo. Para abrir un archivo por nombre se necesita permiso de búsqueda (x) en todos los directorios superiores, no permiso de lectura. Por eso, un directorio personal con modo 711 que nadie más puede listar sigue permitiendo el acceso a la clave si el propio archivo de clave se puede leer. chmod 700 ~ cierra la ruta y chmod 600 ~/.ssh/id_ed25519 corrige la clave. No añada el usuario del agente a sudo ni le asigne una regla de NOPASSWD más amplia que el único comando que realmente necesita. Usuarios con privilegios mínimos en un VPS explica los detalles de los grupos y sudoers. Mantenga esta separación cuando ejecute más de una sesión en el equipo, porque una sesión de Claude Code puede enviar texto directamente a otra y todo lo que conserve la primera sesión puede cruzar ese canal en un solo mensaje.

También conviene añadir otro límite en un VPS en la nube. El servicio de metadatos de la instancia responde en una dirección local de enlace fija y a menudo entrega credenciales de roles a cualquier proceso que las solicite. El endpoint no comprueba qué usuario local realiza la llamada. En EC2, AWS documenta que las llamadas a IMDSv1 no necesitan token. Su propia recomendación para impedir que un usuario acceda al servicio consiste en una regla de firewall local basada en el ID de ese usuario.

sudo iptables -I OUTPUT 1 -p tcp -m owner --uid-owner agent-review -d 169.254.169.254 -j REJECT

Inserte la regla al principio con -I en lugar de añadirla al final con -A. En un host que ejecuta ufw con su política predeterminada de permitir las conexiones salientes, la última regla de la cadena OUTPUT de ufw acepta todas las conexiones TCP nuevas. Por tanto, una regla añadida al final nunca se alcanza. Las reglas de iptables también desaparecen al reiniciar si no las conserva. Si ha habilitado el endpoint IPv6 del servicio de metadatos, [fd00:ec2::254], añada la misma regla con ip6tables.

Compruébelo desde el lado del agente. sudo -u agent-review curl -s --max-time 3 http://169.254.169.254/ no debe mostrar nada y debe terminar con un código distinto de cero, porque el paquete se rechaza antes de salir del equipo. Si muestra un listado, la regla no se está alcanzando.

Inyecte la credencial en el límite

El patrón que resuelve este problema es la inyección de credenciales. El agente nunca almacena una clave real. Envía la solicitud a través de una gateway local, y la gateway sustituye un marcador de posición por el secreto real al salir. El secreto se almacena en la gateway, en un proceso diferente y propiedad de otro usuario.

OneCLI es una implementación de código abierto de este patrón. Tiene licencia Apache-2.0, excepto sus directorios ee/, que tienen una licencia empresarial independiente y requieren una suscripción para uso en producción. Se ejecuta en contenedores junto al agente. En octubre de 2026, el proyecto documenta esta configuración de Docker Compose, que lee tres secretos generados desde un archivo .env situado junto al archivo de Compose:

git clone https://github.com/onecli/onecli.git && cd onecli/docker
cat > .env <<EOF
SECRET_ENCRYPTION_KEY=$(head -c 32 /dev/urandom | base64)
GATEWAY_INTERNAL_SECRET=$(head -c 32 /dev/urandom | base64)
BETTER_AUTH_SECRET=$(head -c 32 /dev/urandom | base64)
COMPOSE_PROFILES=runner
EOF
chmod 600 .env
docker compose up -d --wait

Haga una copia de seguridad del archivo .env, porque si pierde SECRET_ENCRYPTION_KEY, todos los secretos almacenados con él quedan huérfanos. El dashboard escucha en el puerto 10254 y la gateway en el puerto 10255. De forma predeterminada, ambas están vinculadas a 127.0.0.1. Almacene la credencial real una sola vez. Después, proporcione a cada agente un valor de marcador de posición en lugar de la clave, junto con su propio token de acceso con alcance limitado. El agente envía ese token en una cabecera Proxy-Authorization. La gateway identifica la solicitud saliente mediante el host y la ruta, descifra la credencial correspondiente y la sustituye. El entorno del agente no contiene nada que merezca la pena robar.

El valor de este enfoque no está en el cifrado. Está en que la pregunta «qué utilizó este agente y cuándo» se convierte en una consulta de registros. Puede consultar un único registro de auditoría en lugar de intentar averiguar cuál de los seis entornos contenía una copia de la clave.

Entregue el secreto al proceso, no al entorno

Si ejecuta el agente con systemd, no necesita variables de entorno. LoadCredential= coloca el secreto en un directorio privado que sólo puede leer el usuario del servicio. En el archivo de unidad, se expone como %d y dentro del proceso, como $CREDENTIALS_DIRECTORY. Esta restricción se aplica al usuario, no al servicio. Por tanto, asigne a cada servicio de agente su propio usuario. Cualquier otro proceso que se ejecute con el mismo usuario puede abrir el directorio, a menos que la unidad también use el aislamiento del espacio de nombres del sistema de archivos, como PrivateMounts=yes, que lo oculta de otros servicios. El valor nunca aparece en /proc/<pid>/environ, por lo que ps eww no puede mostrarlo. El directorio desaparece cuando el servicio se detiene.

Cifre primero la credencial para esa máquina. Estos comandos proceden de la documentación de systemd y funcionan con systemd 250 o posterior, lo que incluye Ubuntu 24.04 y Debian 13:

echo -n 'sk-example-value' > /tmp/plain.txt
sudo systemd-creds encrypt --name=api_key /tmp/plain.txt /etc/credstore/api_key.cred
shred -u /tmp/plain.txt
sudo systemd-run -P --wait -p LoadCredentialEncrypted=api_key:/etc/credstore/api_key.cred \
  systemd-creds cat api_key

El último comando muestra sk-example-value. Esto confirma que el archivo cifrado se descifra en este host. Después, haga referencia a él desde la unidad:

[Service]
User=agent-review
LoadCredentialEncrypted=api_key:/etc/credstore/api_key.cred
Environment=AGENT_KEY_FILE=%d/api_key
ExecStart=/usr/local/bin/agent-worker

El código del agente abre el archivo en $AGENT_KEY_FILE cuando necesita el valor. Una lectura de archivo dura un instante. Una variable de entorno permanece durante toda la vida del proceso y está disponible para cada proceso hijo que este inicie.

Prefiera tokens de corta duración a claves de larga duración

Una clave que nunca caduca sigue siendo válida cuando vuelve a aparecer, meses después, en un registro o una transcripción. Si el servicio ofrece un token de sesión, utilice el token de sesión y establezca la duración más corta que permita el trabajo.

aws sts assume-role \
  --role-arn arn:aws:iam::123456789012:role/agent-readonly \
  --role-session-name agent-review \
  --duration-seconds 900

Quince minutos es el mínimo que acepta AWS STS (security token service), y normalmente basta para una tarea de un agente. En GitHub, asigne al usuario del agente su propio gh login con un token de permisos detallados limitado al único repositorio en el que trabaja, de modo que gh auth token dentro de esa sesión devuelva algo que no pueda acceder a nada más. Limite primero por recurso y después por tiempo.

Verifique y siga verificando

Después de cualquier cambio en la configuración de un agente, conviene ejecutar tres comprobaciones. Ejecútelas como el usuario del agente, no como su propio usuario.

sudo -u agent-review env | grep -iE 'key|token|secret'
sudo -u agent-review ls -la /home/you/ 2>&1 | head -3
sudo -u agent-review curl -s -o /dev/null -w '%{http_code}\n' --max-time 5 https://api.github.com/user

La primera no debería mostrar ninguna salida. La segunda debería mostrar ls: cannot open directory '/home/you/': Permission denied, aunque eso sólo demuestra que el directorio no se puede listar; la prueba cat anterior es la que demuestra que no se puede leer un archivo dentro de él. La tercera indica qué identidad presenta la ruta de red del agente. Esa es la pregunta que el patrón de gateway debe responder: 401 significa que la solicitud llegó a GitHub sin una credencial que GitHub acepte, ya sea porque no hay ninguna o porque es inválida o ha caducado; 200 significa que lleva una credencial válida, por lo que debe saber qué token es. Si ejecuta agentes sin supervisión, controlar los costes de agentes de IA en un VPS explica los límites de presupuesto que acompañan a estos límites de acceso.

FAQ

¿Puedo confiar en que el modelo no filtrará mis claves?

No, porque el modelo no es el atacante en este modelo de amenazas. El agente lee texto de páginas web, repositorios y gestores de incidencias, y ese texto puede contener instrucciones. El modelo no tiene una forma fiable de distinguir sus instrucciones del texto que ha obtenido. Cualquier control que dependa de que el modelo elija correctamente falla la primera vez que una instrucción inyectada resulta convincente. Por eso, el control debe estar en el sistema operativo o en la red.

¿Son realmente tan problemáticas las variables de entorno para los secretos del agente?

Lo son de una forma concreta: se heredan. Cada proceso hijo que inicia el agente recibe una copia, incluidos un script de compilación, un ejecutor de pruebas y cualquier enlace de instalación de paquetes. El mismo usuario también puede leer las variables mediante /proc/<pid>/environ. Por tanto, cualquier proceso que ejecute el agente puede leerlas sin que el agente se las transfiera. Leer un archivo en el momento de usar el secreto, mediante LoadCredential= o una pasarela, limita la exposición a ese momento.

¿Guardar los secretos en un vault resuelve el problema por sí solo?

Sólo en parte. Un vault resuelve el almacenamiento. Si aloja ese vault usted mismo, necesita su propia fase de endurecimiento, porque un servidor de Vaultwarden suele verse comprometido a través de su token de administración o su archivo de copia de seguridad, no a través de los elementos cifrados que almacena. Esto no resuelve el último paso: algo extrae el secreto del vault y se lo entrega al agente como una variable de entorno, lo que le devuelve a la situación inicial. Lo importante es quién realiza la sustitución. Si el agente obtiene el secreto, el agente tiene el secreto. Si una pasarela o el sistema de inicio realiza la sustitución fuera del proceso del agente, el agente nunca lo contiene.

¿Cómo puedo saber si un agente ya ha filtrado algo?

Por lo general, no puede saberlo a posteriori. Ese es el motivo para usar una pasarela. Sin ella, las pruebas están dispersas entre el historial del shell, la transcripción del agente y los registros de conexiones salientes que probablemente no conserva. Con una pasarela de credenciales, cada uso de una credencial queda registrado en una línea con la identidad del agente y una marca de tiempo. Si sospecha que se ha producido una filtración, rote la clave primero e investigue después. La rotación es barata y la certeza no.

¿Qué es lo mínimo que debería hacer hoy?

Mueva todos los archivos .env fuera de los directorios en los que trabajan sus agentes y cree un usuario sin privilegios para cada agente. Estos dos cambios requieren unos diez minutos y cierran la vía más habitual: que un agente lea un archivo de credenciales que no tenía ningún motivo para estar junto al código. La pasarela y los tokens de corta duración son el siguiente paso, no el primero. El mismo punto de partida se aplica a cualquier entorno de ejecución de agentes, incluido ejecutar un agente autónomo de forma segura en un VPS.

¿Te resultó útil este artículo?