Cómo mantener las claves fuera de los agentes de IA
Las API keys en variables de entorno pueden filtrarse en una llamada. Usa tokens con alcance y caducidad breve mediante un gateway de credenciales.
Qué significa 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 API key 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 darle 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 un modelo que se vuelve hostil. 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. Eso es una inyección de instrucciones. Cuando ocurre, el alcance del daño queda limitado exactamente por una cosa: 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 describe los niveles de aislamiento sobre los que se basa esta guía.
El modelo de amenazas en términos sencillos
Ejecuta esto como el usuario con el que se ejecuta tu agente.
tr '\0' '\n' < /proc/self/environ | grep -iE 'key|token|secret|password'Cada línea que imprime equivale a una solicitud HTTP a un servidor ajeno. Ahora revisa 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 un shell no necesita un exploit sofisticado para extraer esos datos. Cuatro rutas habituales son suficientes, y las cuatro parecen trabajo normal en el registro:
- Un
curlofetchsaliente a cualquier host, con el valor en una cadena de consulta. - Un
git commitygit pusha 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 contiene el valor, que se realiza incluso cuando la salida HTTP está bloqueada.
No puedes resolver esto mediante una revisión exhaustiva. La solución es asegurarte de que no haya nada valioso al alcance.
Un secreto en el árbol de trabajo es un secreto en la ventana de contexto
Un agente lee archivos. Leerá un archivo .env del repositorio en el que trabaja y, una vez leído, estará en la ventana de contexto. Esto significa que estará en la transcripción, en cualquier registro que conserve y en todo lo que el agente escriba después.
Antes, con la clave ubicada en el á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/billingDespué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/.envEl 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 propia configuración del 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. No evita que una instrucción inyectada ejecute base64 .env, porque es un comando de shell y no una lectura de archivo. Trate la configuración como una barrera de protección y los permisos del sistema de archivos como el muro. La misma separación se aplica dentro de los contenedores: archivos de entorno y secretos en Docker Compose cubre la versión de este problema una capa 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 separado requiere un 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_ed25519La última línea debe fallar con cat: /home/you/.ssh/id_ed25519: Permission denied. Si muestra una clave, el directorio principal tiene permisos de lectura para el grupo o para otros usuarios, y chmod 700 ~ lo corrige. No añada el usuario del agente a sudo ni le conceda 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 grupos y sudoers.
En un VPS en la nube también conviene añadir otro límite. El servicio de metadatos de la instancia responde en una dirección local de enlace fija y, a menudo, entrega credenciales de rol a cualquier proceso que las solicite.
sudo iptables -A OUTPUT -m owner --uid-owner agent-review -d 169.254.169.254 -j REJECTComprué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 estado distinto de cero, porque el paquete se rechaza antes de salir del sistema.
Inyectar la credencial en el límite
El patrón que realmente resuelve este problema es la inyección de credenciales. El agente nunca contiene una clave real. Envía la solicitud mediante una pasarela local, y la pasarela sustituye un marcador de posición por el secreto real al salir. El secreto se almacena en la pasarela, en un proceso diferente y propiedad de otro usuario.
OneCLI es una implementación de código abierto de este patrón, con licencia Apache-2.0, y se ejecuta como un contenedor junto al agente. En julio de 2026, el proyecto documenta esta configuración:
git clone https://github.com/onecli/onecli.git
cd onecli
docker compose -f docker/docker-compose.yml up -d --waitEl panel de control escucha en el puerto 10254 y la pasarela en el puerto 10255. Se almacena la credencial real una sola vez. Después, se proporciona a cada agente un valor de marcador de posición en lugar de la clave y su propio token de acceso con alcance limitado, que envía en una cabecera Proxy-Authorization. La pasarela identifica la solicitud saliente por el host y la ruta, descifra la credencial correspondiente y la sustituye. El entorno del agente no contiene nada que valga 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. Se consulta un único registro de auditoría en lugar de intentar determinar 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 solo puede leer ese servicio. El directorio se expone como %d en el archivo de unidad y como $CREDENTIALS_DIRECTORY dentro del proceso. El valor nunca aparece en /proc/<pid>/environ, por lo que ps eww no puede mostrarlo. El directorio desaparece cuando se detiene el servicio.
Cifre primero la credencial para la 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_keyEl último comando muestra sk-example-value. Esto demuestra que el archivo cifrado se puede descifrar 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-workerEl código del agente abre el archivo en $AGENT_KEY_FILE cuando necesita el valor. La lectura de un archivo dura un instante. Una variable de entorno permanece durante toda la vida del proceso y está disponible para cada proceso hijo que este cree.
Prefiera tokens de corta duración a claves de larga duración
Una clave que nunca caduca sigue siendo válida cuando aparece, meses después, en un registro o una transcripción. Si el servicio ofrece un token de sesión, use el token de sesión y establezca el período de validez más corto que permita el trabajo.
aws sts assume-role \
--role-arn arn:aws:iam::123456789012:role/agent-readonly \
--role-session-name agent-review \
--duration-seconds 900Quince minutos es el mínimo que acepta AWS STS (security token service), y normalmente es suficiente para una tarea de agente. En GitHub, asigne al usuario del agente su propio inicio de sesión gh con un token de permisos específicos 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.
Verifica y sigue verificando
Conviene ejecutar tres comprobaciones después de cualquier cambio en la configuración de un agente. Ejecútalas como el usuario del agente, no como tu 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/userLa primera no debería mostrar nada. La segunda debería mostrar ls: cannot open directory '/home/you/': Permission denied. La tercera indica qué identidad presenta la ruta de red del agente. Esa es la pregunta que debe responder el patrón de gateway: 401 significa que el agente no lleva ninguna credencial propia de GitHub, mientras que 200 significa que lleva una. Por tanto, debes saber qué token utiliza. Si ejecutas agentes sin supervisión, controlar los costos de los agentes de IA en un VPS explica los límites de presupuesto que complementan estos límites de acceso.
FAQ
¿Puedo confiar simplemente 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 sistemas de seguimiento 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 peligrosas las variables de entorno para los secretos del agente?
Son peligrosas de una forma concreta: se heredan. Cada proceso secundario que inicia el agente recibe una copia, incluidos un script de compilación, un ejecutor de pruebas y cualquier hook 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. Un archivo leído en el momento de uso, mediante LoadCredential= o una gateway, limita la exposición a ese momento.
¿Guardar los secretos en un vault resuelve el problema por sí solo?
Solo en parte. Un vault resuelve el almacenamiento. No resuelve el último paso, en el que algo extrae el secreto del vault y se lo entrega al agente como una variable de entorno. Eso te devuelve al punto de partida. Lo importante es quién realiza la sustitución. Si el agente obtiene el secreto, el agente tiene el secreto. Si una gateway o el sistema init realiza la sustitución fuera del proceso del agente, el agente nunca lo tiene.
¿Cómo sé si un agente ya ha filtrado algo?
Normalmente no puedes saberlo después de los hechos. Ese es el argumento a favor de la gateway. Sin una, las pruebas quedan dispersas entre el historial del shell, la transcripción del agente y los registros de conexiones salientes que probablemente no conservas. Con una gateway de credenciales, cada uso de una credencial queda registrado en una línea con la identidad del agente y una marca de tiempo. Si sospechas que se ha producido una filtración, rota la clave primero e investiga después. La rotación es barata y la certeza no lo es.
¿Qué es lo mínimo que debería hacer hoy?
Mueve todos los archivos .env fuera de los directorios en los que trabajan tus agentes y crea un usuario sin privilegios para cada agente. Esos 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 motivo para estar junto al código. La gateway 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.