Ir al contenido

Agentes de IA: 3 patrones que están exponiendo credenciales

Tu archivo .env era seguro mientras solo lo leían devs humanos. Con agentes de IA en tu stack, se convirtió en el vector de exposición número uno. Anatomía del problema y la arquitectura que lo previene.

Tu archivo .env tiene ocho credenciales en texto plano. Tu repo de configuración tiene tres YAMLs más con tokens de API. Está todo en .gitignore y vos sabés que solo gente autorizada toca esos archivos. Durante años funcionó perfecto.

Y entonces le diste a tu agente de IA acceso a la terminal.

Lo que era una práctica aceptable se convirtió, sin que nadie lo notara, en el vector número uno de exposición de credenciales en equipos que están adoptando trabajo agéntico. No es opinión ni alarmismo: es un patrón reproducible que aparece en cada equipo, con cada agente (Claude Code, Cursor en modo agéntico, Devin, OpenDevin, Aider, Continue), y con cada stack. En este artículo vas a ver los tres anti-patterns concretos, la regla operativa que los corta, y la arquitectura que los previene estructuralmente.

El cambio silencioso bajo tus pies

Imaginá la escena. Tu equipo lleva semanas integrando un agente con acceso a tu CLI, a tu Git, al CLI de tu proveedor de DNS, al de tu plataforma de billing, al de tu proveedor de identidad. El agente hace en minutos lo que antes te tomaba horas: despliegues, validaciones cruzadas, rotaciones de tokens, smoke tests post-deploy.

Un día, durante una verificación rutinaria, el agente ejecuta un comando que imprime las variables de entorno del proceso. Es algo que vos como dev humano harías sin pensar para confirmar que la configuración cargó bien. Pero esa salida —con todos los valores en claro— entra al contexto del modelo. Y desde ahí, queda persistida en la transcripción de la sesión.

Cuando alguien se da cuenta, no hay forma de "borrar" esos valores. Ya viajaron por canales que vos no controlás. Las credenciales que tu agente acaba de ver hay que tratarlas como expuestas, rotarlas todas, propagar los valores nuevos a cada consumidor.

Lo cínico es que cada operación de rotación que involucra al agente puede gatillar más exposiciones. Una búsqueda en un archivo de configuración. Un comando "para verificar" que el cambio se aplicó. Cada uno suma valores nuevos a la pila. Un incidente que empieza con dos credenciales termina con quince.

Principio: En la era agéntica, la primera vez que un agente lee un secreto en texto plano es la última vez que ese secreto puede considerarse privado.

Por qué los agentes son diferentes (y por qué tu intuición de "siempre lo hicimos así" deja de aplicar)

Tu config con credenciales en plaintext era segura por una razón muy concreta: las personas que podían leerla eran un conjunto cerrado y conocido. Devs con acceso al repositorio. Operadores de infraestructura. Personas auditables que, si veían un secreto, lo veían una vez y no lo retransmitían a ningún sistema persistente.

Un agente de IA opera distinto. Cuando ejecutás una herramienta a través suyo, la salida de esa herramienta entra a su ventana de contexto. Y la ventana de contexto se persiste en la transcripción de la sesión, en los logs del runtime, en la memoria semántica si la tenés activada, en los caches locales que el cliente del agente mantiene. Cada uno de esos lugares es un destino más para tus credenciales.

El cambio conceptual es este: con devs humanos, leer un secreto era una acción puntual sin rastro persistente fuera del propósito inmediato. Con agentes, leer un secreto es una acción que se replica automáticamente en al menos un canal que vos no estás auditando.

Esa diferencia vuelve obsoleta una regla operativa que la industria daba por buena: "está bien que los secretos vivan en archivos de configuración, mientras solo gente autorizada acceda a esos archivos". La regla que la reemplaza es: si un agente puede leer un archivo con secretos en claro, esos secretos hay que considerarlos expuestos desde el momento en que ese agente decida leerlo. Y vos no controlás exactamente cuándo lo va a decidir.

Principio: La seguridad de un archivo de configuración no es propiedad del archivo, es propiedad del conjunto de actores que pueden leerlo. Sumar un agente al conjunto cambia el cálculo entero.

Anti-pattern 1: el smoke test que imprime el entorno

El más común. Tu equipo escribe una validación post-deploy que verifica que el proceso levantó con las variables correctas. La forma rápida se ve así, en cualquier lenguaje:

# Python
python -c "import os
for k, v in os.environ.items():
    print(f'{k}={v}')"

# Node
node -e "for (const k in process.env) console.log(`${k}=${process.env[k]}`)"

# Bash
env

Inofensivo cuando lo corre un dev humano en una terminal local. Catastrófico cuando lo corre un agente como paso de validación: cada valor de cada variable acaba de entrar al contexto del modelo y de ahí al transcript persistido.

La versión segura imprime nombres y un booleano de "está seteado", nunca el valor. Te sirve igual para verificar que la configuración cargó, sin convertir el comando en un dump:

# Python
python -c "import os
print({k: bool(v) for k, v in os.environ.items()})"

# Node
node -e "console.log(Object.fromEntries(Object.entries(process.env).map(([k,v]) => [k, !!v])))"

# Bash (lista nombres, oculta valores)
env | awk -F= '{print $1}'

Principio: Si necesitás verificar que un secreto está cargado, comprobá su presencia. Nunca su contenido.

Anti-pattern 2: el grep que muestra las líneas con valores

Cuando el equipo busca dónde está configurado un secreto, el primer reflejo es buscarlo por nombre:

grep -iE "secret|token|key|password" config/*.yml

El problema es que grep imprime las líneas que matchean, incluyendo el valor a la derecha del nombre. Una sola invocación sobre un archivo de configuración medianamente completo expone decenas de credenciales.

La versión segura busca estructura, no contenido. yq y jq tienen modos que devuelven solo paths:

# YAML: lista paths que tienen valores escalares, sin imprimir los valores
yq 'paths(scalars) | join(".")' config/secrets.yml

# JSON: idem
jq 'paths(scalars) | join(".")' config/secrets.json

Si necesitás el valor concreto de UNO solo, sacalo a un archivo temporal y consumilo desde ahí, sin imprimirlo a la salida estándar:

yq -r '.database.password' config/secrets.yml > /tmp/pw.txt
mi-comando --password-file /tmp/pw.txt
rm /tmp/pw.txt

Principio: Buscar credenciales por nombre es legítimo. Imprimirlas como efecto colateral no lo es. Usá herramientas que separen las dos cosas.

Anti-pattern 3: el "verifica que se aplicó el cambio" que dumpea el archivo

El más sutil y el que más equipos terminan pagando caro. Después de aplicar un cambio en un archivo de configuración, parece razonable confirmar que el cambio entró. Estos comandos lo hacen:

cat /etc/myapp/config.env
sed -E 's/=VALOR_VIEJO/=VALOR_NUEVO/' /etc/myapp/config.env  # sin -i imprime el archivo modificado
head -50 /etc/myapp/config.env
tail -20 /etc/myapp/config.env

Todos ellos imprimen el contenido completo del archivo. Si ese archivo tiene treinta credenciales además de la que cambiaste, las treinta acaban de viajar al contexto del agente. Y esta variante es la peor porque ocurre durante una rotación: justo cuando estás reduciendo el blast radius de UN secreto, exponés TODOS los demás del mismo archivo.

La versión segura usa operaciones in-place que no emiten contenido a la salida estándar, y verifica con conteos o hashes:

# Modifica in-place, no imprime
sed -i 's/VALOR_VIEJO/VALOR_NUEVO/' /etc/myapp/config.env

# Verifica con un número, no con líneas
grep -c 'VALOR_NUEVO' /etc/myapp/config.env

# Confirma que el archivo cambió, sin exponer su contenido
sha256sum /etc/myapp/config.env

# Verifica que el servicio consumió el nuevo valor (status, no contenido)
systemctl is-active myapp
curl -o /dev/null -w '%{http_code}\n' http://localhost:8080/healthz

Principio: Cuando confirmes un cambio en un archivo con secretos, leé el efecto, no el archivo. Hashes, conteos y status code te dicen lo que necesitás saber sin exponer lo que no.

La regla operativa que cubre los tres anti-patterns

Si te fijás, los tres comparten una raíz común: son comandos que emiten contenido de archivos con secretos a la salida estándar. La regla que los corta a los tres es una sola:

Ningún comando ejecutado por o a través del agente puede emitir contenido de archivos con secretos a stdout. Da igual el vector: local, SSH, dentro de un contenedor, vía SSM run-command de AWS, vía kubectl exec, vía docker exec, vía API remota. Si el contenido sale a stdout, va al contexto del agente.

Esto se traduce a una tabla concreta de comandos prohibidos versus permitidos cuando tocás archivos con secretos:

Prohibidos (emiten contenido) Permitidos (in-place, count o hash)
cat archivo.envgrep -c 'PATTERN' archivo.env
head/tail archivo.envwc -l archivo.env
sed -E 's/.../.../' archivo.env (sin -i)sed -i 's/.../.../' archivo.env
awk '{print}' archivo.envawk 'END{print NR}' archivo.env
less / more / viewsha256sum archivo.env
Get-Content seguido de echo(Get-Item).Length
cat archivo && ...test -f archivo.env

Implementás esta regla en tres capas. Primero, deny rules en la configuración del runtime de tu agente que bloqueen comandos peligrosos sobre archivos sensibles. Segundo, hooks PreToolUse que inspeccionen cada comando antes de ejecutarlo y rechacen patrones de emisión. Tercero, criterio operativo del equipo: cuando escribís un nuevo procedimiento que va a correr el agente, la primera revisión es "¿algún paso emite contenido de archivo con secretos?".

Principio: No es "qué archivos no leer". Es "qué comandos no emiten contenido". El framing correcto cambia las soluciones que se te ocurren.

Por qué las reglas operativas no son suficientes

Si aplicás solo la regla operativa, cubrís el 95% de los casos. Pero el 5% restante eventualmente te alcanza, porque la regla depende de que vos hayas enumerado todos los vectores posibles antes de que el agente los descubra. Eso es imposible: cada vez que adoptás un servicio nuevo, una herramienta nueva o un protocolo de gestión nuevo, aparece un vector nuevo.

Un equipo puede tener perfectamente cubierto el caso local —deny rules sobre cat, head, tail y sed— y sufrir una fuga al primer comando que ejecuta una operación remota tipo aws ssm send-command o docker exec. El comando se parametriza con strings, los filtros locales no aplican al payload remoto, y el dump del archivo viaja igual al contexto del agente.

La solución estructural no es enumerar mejor. Es quitarle al agente la capacidad de ver el valor en primer lugar, en cualquier vector. Eso requiere mover los secretos fuera del filesystem donde el agente opera.

Principio: Si dependés de que el agente sea cuidadoso, vas a perder. Diseñá para que la operación segura sea la única posible.

El modelo que escala: vault + tokens de agente + inyección en subproceso

El threat model correcto para la era agéntica tiene tres componentes que trabajan juntos. Si te falta uno, los otros dos no compensan.

1. Vault centralizado

Los secretos viven cifrados en un servicio dedicado, no en archivos plaintext del sistema operativo. Las opciones técnicas son varias y la elección importa menos que la propiedad común: el material en claro nunca toca el disco accesible.

  • Self-hosted: HashiCorp Vault, OpenBao (fork OSS de Vault)
  • Cloud-native: AWS Secrets Manager, GCP Secret Manager, Azure Key Vault
  • Managed con agentes IA en mente: herramientas más nuevas que abstraen el modelo de tokens de agente con audit nativo

2. Tokens de agente con scope mínimo

Cada agente tiene su propia identidad, con permisos a un subconjunto explícito de secretos, auditable a nivel de cada acceso. Si el agente compromete su token, vos podés revocar solo ese y rotar solo los secretos que ese token tenía permiso de leer. No hay propagación viral.

La regla operativa: cero permisos por default, agregás scope a medida que el agente necesita ejecutar tareas concretas, registrás cada grant. Nunca reutilices un token humano para un agente.

3. Inyección en subproceso, no en agente

Acá es donde mucha gente se queda corta. No alcanza con tener un vault: si el agente lee el secreto del vault y después lo pasa como argumento al comando, el secreto pasó por el contexto del agente y volvió al mismo problema.

El patrón correcto es:

# En lugar de:
SECRET=$(vault read -field=value secret/db-password)
mi-comando --password "$SECRET"   # el value pasó por el shell del agente

# Hacés:
vault exec --secret secret/db-password=DB_PASSWORD -- mi-comando
# o
secrevo run --secret DB_PASSWORD -- mi-comando

El wrapper resuelve el valor desde el vault, lo coloca en el entorno del subproceso mi-comando, y nunca lo retorna al agente. El agente solo ve el resultado del comando, no el material que el comando usó para autenticarse.

Con este modelo en su lugar, los tres anti-patterns de las secciones anteriores pierden su objeto: las variables del subproceso no son las variables del agente, los archivos plaintext ya no existen, y los servicios arrancan a través del wrapper de vault que les pasa material fresco en cada ejecución.

Principio: El secreto nunca está en el agente. Está en el vault. Y el subproceso lo consume sin pasar por intermediarios.

Cómo empezar mañana: cinco pasos prácticos

Migrar el stack completo toma semanas si ya estás en producción. Pero podés empezar en una tarde, por las credenciales que más duelen si se exponen, y avanzar incremental:

  1. Inventariá tus secretos por blast radius. Listá cada credencial que tu stack usa hoy y respondé: si se expone, ¿cuánto cuesta rotarla y cuánto daño puede hacer alguien con ella? Priorizá las de mayor valor (claves de billing en producción, tokens con permisos amplios de cloud o identidad, masters de servicios compartidos).
  2. Elegí un vault centralizado y migrá las top 10. No intentes mover todo de una. Empezá por las diez de mayor blast radius. Cada una que migrás reduce tu superficie de exposición agéntica de manera medible.
  3. Creale a tu agente su propia identidad con scope mínimo. No reutilices tokens humanos. No le des "admin" para empezar y "lo restringimos después". Empezás con cero permisos y le sumás lo que necesita para cada tarea, registrando cada grant.
  4. Reemplazá EnvironmentFile=/etc/... por wrappers de vault. En cada unit de systemd, cada Dockerfile, cada deployment de Kubernetes, el patrón es el mismo: el proceso arranca a través del wrapper, no leyendo un archivo. Esa única refactorización cubre la mayoría de tus servicios.
  5. Sumá deny rules locales y hooks PreToolUse al runtime de tu agente. Como defensa en profundidad. La capa estructural hace el trabajo pesado; la capa operativa captura los descuidos puntuales antes de que se vuelvan incidente.

Principio: No tenés que rehacer todo en un día. Empezás por el secreto que más duele si se va, y construís hacia adentro desde ahí.

Una opción que existe (si preferís no construirlo todo)

Si llegaste hasta aquí y tu reacción es "esto es demasiado" o "yo quiero enfocarme en mi producto, no en construir infraestructura de secretos para agentes", es una reacción completamente válida. La mayoría de los equipos llega a esa conclusión la primera vez que ven el costo de migrar.

Existen plataformas que abstraen este modelo para que no tengas que armarlo capa por capa. HashiCorp Vault y los servicios managed (AWS Secrets Manager, GCP Secret Manager, Azure Key Vault) cubren el vault y los tokens. En Ganemo construimos Secrevo con este threat model en mente desde el día cero: cifrado at-rest, tokens de agente con scope explícito, propagación a runtime vía secrevo run, audit de cada acceso a cada secreto. Es uno de los caminos posibles, y vale para los equipos que prefieren no construir esta capa de cero.

Independientemente de qué herramienta elijas, el principio que define si tu stack está listo para trabajo agéntico es uno solo. Si tu agente puede leer el valor de un secreto en algún archivo, asumí que va a leerlo en algún momento, y diseñá para que eso no importe. El equipo que entiende esto temprano se ahorra las semanas de rotaciones forzadas que sufre el que lo descubre tarde.

Agentes de IA: 3 patrones que están exponiendo credenciales
Wilfredo Fernando Pastor Avila 14 de mayo de 2026
Compartir
Archivo
Iniciar sesión dejar un comentario
Como contratar ChatGPT Codex para tu empresa en pocos pasos
Aprende a diferenciar entreGuia paso a paso para configurar el plan business y comenzar a trabajar en equipo con inteligencia artificial agéntica