API keys y agente de IA: cómo evitar que tu key se filtre
Viernes por la tarde. Un test de integración falla con un 401 y le pides a tu agente de IA que lo arregle. Hace lo que haría cualquier developer con prisa: comprobar si las API keys están cargadas.
Ejecuta un env. La salida entera, con tu key de Anthropic, la de Stripe y el token de GitHub, entra en el contexto y queda en el transcript de la sesión. Luego, para que el test pase, escribe la key en un fixture. Revisas el diff en diagonal, commit y push.
Nadie ha hecho nada malicioso. No hubo prompt injection ni atacante. Solo una cadena de cuatro pasos donde ninguno parecía grave.
En corto: las API keys se escapan de un agente de IA por cinco vías: lectura del .env, salida de terminal, código hardcodeado, commit y chat. Ninguna defensa cubre las cinco: combina reglas de lectura del agente, sandbox o limpieza del entorno, secretos inyectados en runtime, gitleaks en pre-commit y push protection. Y si una key se filtra, revócala antes de tocar el historial de git.
¿Qué es una fuga de secretos con un agente de IA?
Una fuga de secretos con un agente de IA es cualquier camino por el que una credencial pasa de su sitio seguro a un lugar que no controlas: el contexto del modelo, un transcript guardado en disco, un log, el código fuente o el historial de git.
No hace falta que acabe en GitHub: en cuanto entra en el contexto, viaja al proveedor del modelo y queda en el historial de la sesión.
El problema tiene cifras. El informe State of Secrets Sprawl 2026 de GitGuardian, publicado en marzo de 2026, contó 28,65 millones de secretos nuevos en commits públicos de GitHub durante 2025, un 34 % más que el año anterior.
Según el informe State of Secrets Sprawl 2026 de GitGuardian, los commits asistidos por Claude Code filtraron secretos en un 3,2 % de los casos, frente al 1,5 % de base en GitHub público. Y hallaron 24.008 secretos en ficheros de configuración de MCP.
Ojo: GitGuardian vende escáneres de secretos y no publica la metodología de esa comparación.
Las cinco vías por las que una API key sale de tu máquina
Cada vía por la que se filtran las API keys de un agente de IA tiene su defensa, y cada defensa tiene un agujero.
| Vía de fuga | Defensa concreta | Limitación o riesgo |
|---|---|---|
1. El agente lee .env y el valor entra en el contexto |
permissions.deny con Read(./.env) y Read(./.env.*) |
No cubre grep -r ni scripts de Python o Node que abren el fichero por su cuenta |
2. La key aparece en la salida de terminal (env, printenv, logs, stack traces) |
Sandbox con credentials en deny o mask, o CLAUDE_CODE_SUBPROCESS_ENV_SCRUB=1 |
El sandbox no funciona en Windows nativo; el scrub deja GITHUB_TOKEN; tu propio código puede seguir logueando headers |
| 3. El agente hardcodea la key en código o en un fixture | Secretos inyectados en runtime (op run) y revisión del diff |
Si la key existe en el entorno, el agente puede copiarla igual |
| 4. Commit y push | gitleaks en pre-commit + push protection de GitHub | Falsos negativos con keys troceadas; ambos se pueden saltar |
| 5. Tú pegas la key en el chat | Disciplina, y keys con scope mínimo y límite de gasto | Ninguna herramienta te protege de ti mismo |
Proteger .env en Claude Code: qué bloquea permissions.deny y qué no
La primera capa es decirle a Claude Code qué no puede leer, como en el ejemplo de la referencia de settings:
{
"permissions": {
"deny": [
"Read(./.env)",
"Read(./.env.*)",
"Read(./secrets/**)"
]
}
}
Si tienes un .claudeignore, bórralo: la doc de permisos dice que no tiene ningún efecto y que hay que pasar sus entradas a reglas Read de deny.
El matiz que casi nadie lee: mucha gente da por hecho que denegar Read no impide un cat .env. En versiones recientes, la doc (consultada en octubre de 2026) dice otra cosa: el deny de Read y Edit se aplica a los comandos de Bash que Claude Code reconoce, como cat, head, tail, sed y tee, y a las redirecciones.
No cubre comandos que leen ficheros sin nombrarlos, como grep -r pattern ., ni un script de Python o Node que abre el fichero por su cuenta. Para eso, la doc remite al sandbox, que restringe a nivel de sistema operativo.
Si quieres lógica propia, como bloquear todo comando con printenv, eso va en un hook PreToolUse. Cuándo usar cada pieza lo explica hooks vs permissions en Claude Code.
Sandbox y limpieza del entorno: la key fuera del alcance del shell
La vía 2 es la más traicionera: la key no está en un fichero que puedas denegar, sino en las variables de entorno que heredan los comandos.
En el issue #80153 de anthropics/claude-code, quien lo reporta cuenta que un agente autónomo ejecutó un comando compuesto con un env suelto en medio. El entorno entero acabó en el transcript y hubo que rotar unas diez credenciales.
La doc del sandbox es explícita: por defecto, los comandos sandboxed heredan las variables de entorno de Claude Code, "incluidos los secretos". Para evitarlo tienes sandbox.credentials:
{
"sandbox": {
"enabled": true,
"credentials": {
"files": [
{ "path": "~/.aws/credentials", "mode": "deny" }
],
"envVars": [
{ "name": "ANTHROPIC_API_KEY", "mode": "deny" },
{ "name": "STRIPE_SECRET_KEY", "mode": "deny" }
]
}
}
}
Con deny, la variable desaparece antes de cada comando. Con mask, el comando ve un marcador y el proxy pone el valor real solo hacia hosts permitidos; exige terminación TLS.
Dos avisos de la doc: no hay lista de credenciales por defecto, solo se protege lo que declaras. Y credentials afecta solo a Bash sandboxed. Para limpiar credenciales de todos los subprocesos, incluidos hooks y servidores MCP por stdio, usa CLAUDE_CODE_SUBPROCESS_ENV_SCRUB=1. Ojo: detecta las credenciales por el nombre o la forma del valor, y deja GITHUB_TOKEN y GH_TOKEN a propósito para que gh siga funcionando. Para quitarlas, añádelas como deny en sandbox.credentials.
Que la API key no viva en el disco del repo
Todo lo anterior es de Claude Code. Si alternas con Codex o Cursor, necesitas una capa independiente del agente: que el .env no contenga secretos.
Con la CLI de 1Password, el .env guarda referencias en lugar de valores:
# .env — solo referencias, se puede leer sin miedo
ANTHROPIC_API_KEY="op://dev/anthropic/credential"
STRIPE_SECRET_KEY="op://dev/stripe/secret_key"
# Los valores reales existen solo mientras dura el proceso
op run --env-file="./.env" -- npm run test:integration
Si el agente lee ese .env, ve rutas op://, no secretos. Doppler o dotenvx resuelven lo mismo con otro modelo; elige uno y no mezcles.
Y no olvides tu carpeta de configuración. En el hilo de Hacker News sobre usar Bubblewrap para aislar el .env, con 187 puntos, Simon Willison avisó de que ~/.claude contiene los transcripts de todas tus sesiones anteriores, que pueden incluir datos sensibles. Si una key pasó por el chat, sigue ahí.
gitleaks en pre-commit y push protection: la red cuando todo falla
Las capas anteriores reducen la probabilidad. Esta reduce el daño: si la key llega al commit, no sale de tu máquina.
La prueba: un repo vacío con gitleaks 8.30.1 como hook de pre-commit y una key de Anthropic falsa, con formato realista, dentro de un fixture de test. El hook:
#!/bin/sh
# .githooks/pre-commit — bloquea el commit si hay secretos en staging
gitleaks git --pre-commit --staged --redact --no-banner -v
Git no lee .githooks/ por su cuenta. Actívalo una vez por clon:
# Activa la carpeta de hooks del repo
git config core.hooksPath .githooks
chmod +x .githooks/pre-commit
Salida real al intentar el commit (sin marcas de tiempo):
Finding: const apiKey = 39;REDACTED39;
Secret: REDACTED
RuleID: anthropic-api-key
Entropy: 5.453328
File: src/client.test.ts
Line: 4
Fingerprint: src/client.test.ts:anthropic-api-key:4
INF 0 commits scanned.
INF scanned ~308 bytes (308 bytes) in 173ms
WRN leaks found: 1
El commit no se creó. Usa siempre --redact: sin él, gitleaks imprime la key, y si el commit lo lanza el agente, la metes en el contexto por otra puerta.
Si usas el framework pre-commit, esta es la configuración del README de gitleaks, con la versión actualizada a la 8.30.1. El hook gitleaks ya ejecuta --redact por defecto:
repos:
- repo: https://github.com/gitleaks/gitleaks
rev: v8.30.1
hooks:
- id: gitleaks
Última línea: GitHub. Según la doc de push protection, la protección para usuarios viene activada por defecto y bloquea pushes con secretos a repositorios públicos. La protección a nivel de repositorio viene desactivada y requiere GitHub Secret Protection: en tus repos privados, por defecto, nadie para el push.
Qué hacer si una API key se filtra: revoca primero
El instinto es borrar el commit. Orden equivocado: la guía de GitHub para eliminar datos sensibles dice que el primer paso es revocar o rotar el secreto.
Limpiar el historial no basta: el commit sigue accesible en clones, forks, vistas cacheadas por SHA y pull requests. Y GitGuardian encontró que más del 64 % de los secretos válidos de 2022 seguían funcionando en enero de 2026.
En los primeros minutos:
- Revoca la key en el panel del proveedor y genera una nueva.
- Revisa el uso de la key comprometida en ese periodo.
- Actualiza la key nueva en tu gestor de secretos, no en el
.env. - Solo entonces limpia el historial con
git-filter-repo(2.47 o superior, con--sensitive-data-removal).
Para acotar el daño, usa keys con alcance mínimo y límite de gasto. En Anthropic, los workspaces admiten keys propias y un límite de gasto mensual. Una key de desarrollo para el agente, en su workspace y con tope bajo, convierte una fuga en una molestia.
Límites de este enfoque
Ninguna capa es hermética. Esto es lo que no resuelven.
gitleaks tiene falsos negativos. En el mismo repo de prueba, la key troceada en dos literales concatenados (prefix + '...') dio este resultado: no leaks found y commit creado. Las reglas buscan patrones, no entienden el código.
Todo hook se puede saltar. git commit --no-verify omite el pre-commit, y el README de gitleaks documenta SKIP=gitleaks. Un agente con permiso para ejecutar git puede hacerlo para cumplir la tarea.
El sandbox no llega a todas partes. Funciona en macOS, Linux y WSL2, no en Windows nativo, y si no puede arrancar, por defecto los comandos se ejecutan sin sandbox; activa sandbox.failIfUnavailable si prefieres que falle. Los servidores MCP locales, los hooks y tus comandos con ! corren fuera de él. Un servidor MCP es una dependencia más; trátalo como cualquier paquete de npm que instalas.
El agente necesita algunas keys. Si ejecuta tests de integración, la key tiene que existir. Lo realista es una key de desarrollo limitada, no una de producción bien escondida.
No cubre un ataque deliberado. Si un documento malicioso ordena al agente exfiltrar credenciales, el problema es otro: lo cubre el post sobre inyección indirecta de prompts.
Y si piensas filtrar comandos peligrosos con una lista negra, la blocklist típica deja pasar 16 de 20 casos.
Por dónde empezar con las API keys y tu agente de IA
Para proteger tus API keys del agente de IA con veinte minutos, en este orden: gitleaks en pre-commit con --redact, reglas Read de deny para .env en ~/.claude/settings.json (con .env en subcarpetas, usa Read(**/.env) y Read(**/.env.*)), y la key del agente en un workspace propio con límite de gasto.
op run, sandbox y limpieza del entorno van en la segunda tanda. La idea es una: no confíes en que el agente no mire; haz que no haya nada que ver.
El ebook gratuito El Developer Agéntico dedica una parte a la seguridad de agentes. En el curso Construye con IA llevas Claude Code a un producto completo.
Y si te preocupa revisar lo que escribe el agente antes de que llegue a main, de eso va el workshop SDD + Agentic Engineering.
Preguntas frecuentes
¿Basta con añadir .env al .gitignore?
No. Evita que el fichero entre en git, pero no que el agente lo lea, que la key salga en un log o que la copie a un fichero versionado, como un fixture.
¿Read(./.env) en deny impide que Claude Code ejecute cat .env?
Según la doc de Claude Code (octubre de 2026), sí: el deny de Read cubre comandos que Claude Code reconoce, como cat, head o tail. No cubre grep -r ni scripts que abren el fichero por su cuenta; para eso está el sandbox.
¿gitleaks detecta todas las API keys?
No. Busca patrones conocidos y alta entropía. En una prueba real, una key de Anthropic troceada en dos literales pasó sin aviso. Es una red, no una garantía.
¿Push protection de GitHub está activada por defecto?
Para usuarios, sí, pero solo bloquea pushes a repositorios públicos. A nivel de repositorio viene desactivada y requiere GitHub Secret Protection.
¿Qué hago primero si el agente ha subido una key a GitHub?
Revocarla y generar una nueva. Limpiar el historial va después: borrar el commit no invalida la key, y sigue accesible en clones y forks.
¿Esto sirve para Codex o Cursor?
Los permisos y el sandbox son de Claude Code. El resto sirve igual: op run, gitleaks, push protection y keys con scope mínimo y límite de gasto.
Por Bezael Pérez — Developer senior con más de 15 años de experiencia y fundador de Dominicode.
¿Te resultó útil este artículo?
Compártelo con tu comunidad y ayuda a otros desarrolladores.
