Image for post Tu Agente de IA Puede Filtrar tus API Keys: Guia de Seguridad Practica

Tu Agente de IA Puede Filtrar tus API Keys: Guia de Seguridad Practica


La semana pasada un compañero me escribió alarmado: «Sergio, Claude Code acaba de meter mi API key de Gemini en un commit y la subió a GitHub». No era un novato. Llevaba meses usando agentes de código a diario. El problema es que confiamos en estas herramientas como si fueran extensiones de nuestro IDE, pero en realidad tienen acceso al sistema de archivos, pueden ejecutar comandos shell y, en el peor caso, enviar información a servicios externos.

TL;DR: Los agentes de código con IA (Claude Code, Cursor, Copilot) leen automáticamente archivos .env, pueden exponer secrets en commits, logs o llamadas a herramientas externas. Este artículo te da un checklist práctico para proteger tus credenciales sin dejar de usar vibe coding en tu día a día.

¿Por qué tus secrets están en riesgo ahora?

Un agente de código no es un autocompletado glorificado. Cuando le das acceso a tu terminal, le estás dando las llaves del castillo. La diferencia entre Copilot sugiriendo una línea y Claude Code ejecutando docker compose config es la misma que entre alguien que mira tu casa por la ventana y alguien que tiene copia de la llave.

En enero de 2026, The Register reportó que Claude Code podía leer archivos .env incluso cuando existían reglas en .claudeignore diseñadas para bloquearlo. El CEO de Knostic, Gadi Evron, descubrió que Claude Code había incluido su API key de Gemini en un archivo de test y la subió a un branch. No fue un ataque, fue el agente «ayudando».

Los vectores de exposición son tres:

  • Lectura directa: el agente accede a .env, .env.local o archivos de configuración con secrets en texto plano.
  • Exposición indirecta: comandos aparentemente seguros como docker compose config o env resuelven variables y muestran valores reales.
  • Filtración vía herramientas: MCP servers, logs, o incluso el propio contexto de conversación pueden transmitir secrets a servicios externos.

¿Qué es la superficie de ataque de un agente de código?

La superficie de ataque de un agente de código es el conjunto de recursos del sistema (archivos, red, procesos) a los que el agente puede acceder directa o indirectamente durante una sesión de trabajo. A diferencia de un IDE tradicional, un agente autónomo combina lectura de archivos, ejecución de comandos y comunicación con APIs externas en un mismo flujo.

Pensemos en capas, como una cebolla:

  1. Capa de archivos: todo lo que el agente puede leer. Incluye .env, ~/.ssh/, ~/.aws/credentials, docker-compose.yml con variables interpoladas.
  2. Capa de ejecución: comandos shell que el agente ejecuta. Un printenv o docker compose config expone secrets que nunca están en archivos.
  3. Capa de red: MCP servers, webhooks, o cualquier herramienta que envíe datos fuera de tu máquina.
  4. Capa de contexto: el propio historial de conversación del agente, que puede contener secrets leídos anteriormente y persistir entre sesiones.

Los 5 incidentes reales que debes conocer

No estoy hablando de escenarios teóricos. Estos son casos documentados de 2025-2026:

IncidenteVectorImpacto
CEO de Knostic: API key en commitLectura de .envKey de Gemini expuesta en GitHub
Claude Code ignora .claudeignoreBypass de reglasSecrets accesibles pese a configuración
docker compose configComando «seguro»Variables de entorno resueltas en texto plano
OpenClaw Issue #9627Config rewrite${API_KEY} resuelta a valor real en JSON
Moltbook: 1.5M API keysFrontend vibe-codedKeys de Supabase visibles en código fuente

El patrón común: nadie hizo nada «mal» de forma obvia. Los desarrolladores usaron buenas prácticas (variables de entorno, .gitignore) pero no contaron con que un agente autónomo operaría a nivel de sistema, no solo a nivel de código.

Checklist de seguridad: protege tus secrets en 4 pasos

En mi entorno de trabajo, después del susto de mi compañero, implementé estas medidas. Llevo usándolas dos meses sin fricción significativa en el flujo de desarrollo.

Paso 1: Configura permissions.deny en settings.json

El .claudeignore no es fiable. A febrero de 2026, la documentación oficial de Claude Code recomienda usar permissions.deny en .claude/settings.json:

{
  "permissions": {
    "deny": [
      "Read(.env)",
      "Read(.env.*)",
      "Read(.env.local)",
      "Read(secrets/**)",
      "Read(**/*.pem)",
      "Read(**/*.key)",
      "Read(**/credentials*)",
      "Bash(cat .env*)",
      "Bash(printenv*)",
      "Bash(docker compose config*)",
      "Bash(env)"
    ]
  }
}

Las reglas de deny se evalúan primero, antes que allow o ask. Esto bloquea tanto la lectura directa como los comandos que resolverían variables.

Paso 2: Saca los secrets fuera del directorio del proyecto

Si el secret no está en el árbol de archivos del proyecto, el agente no puede leerlo. Es así de simple.

# En lugar de .env en la raiz del proyecto:
# Crea un directorio externo para secrets
mkdir -p ~/.secrets/mi-proyecto

# Mueve tus variables sensibles
mv .env ~/.secrets/mi-proyecto/.env

# En tu aplicacion, carga desde la ruta externa
# docker-compose.yml
# env_file:
#   - ${HOME}/.secrets/mi-proyecto/.env

# Verifica que .env ya no existe en el proyecto
ls -la .env  # No deberia existir

Para Docker Compose, usa la directiva secrets en lugar de environment. Los secrets se montan en /run/secrets/ como archivos, nunca como variables de entorno visibles con docker compose config.

# docker-compose.yml - forma segura
services:
  api:
    image: mi-api:latest
    secrets:
      - db_password
      - api_key

secrets:
  db_password:
    file: ${HOME}/.secrets/mi-proyecto/db_password.txt
  api_key:
    file: ${HOME}/.secrets/mi-proyecto/api_key.txt

Paso 3: Usa sandboxing para sesiones autónomas

Si necesitas que el agente trabaje sin supervisión (modo headless, Agent Teams), el sandboxing es obligatorio. Claude Code soporta aislamiento a nivel de SO usando bubblewrap en Linux:

{
  "sandbox": {
    "enabled": true,
    "network": {
      "allowOutbound": false,
      "allowedDomains": ["registry.npmjs.org", "pypi.org"]
    },
    "filesystem": {
      "writablePaths": ["./src", "./tests"],
      "denyPaths": ["~/.ssh", "~/.aws", "~/.secrets"]
    }
  }
}

Según la documentación de Anthropic, el sandboxing reduce los prompts de permisos en un 84% internamente. Pero ojo: si usas Docker sandboxes, el template por defecto lanza Claude Code con --dangerously-skip-permissions. Revisa y ajusta antes de confiar.

Paso 4: Añade escaneo de secrets al pipeline

La última línea de defensa es automática. Un pre-commit hook que detecte secrets antes de que lleguen al repositorio:

# Instala detect-secrets (pip install detect-secrets)
# Genera baseline
detect-secrets scan > .secrets.baseline

# Hook de pre-commit (.pre-commit-config.yaml)
# repos:
#   - repo: https://github.com/Yelp/detect-secrets
#     rev: v1.5.0
#     hooks:
#       - id: detect-secrets
#         args: ['--baseline', '.secrets.baseline']
# Prueba rapida: intenta commitear un archivo con un secret
echo "OPENAI_API_KEY=sk-fake1234567890abcdef" > test_leak.txt
git add test_leak.txt
git commit -m "test"
# Output esperado: detect-secrets bloquea el commit
# ERROR: Potential secret detected in test_leak.txt

Aplicación práctica: securizando un proyecto FastAPI

Vamos a verlo con un caso real. Tengo un proyecto FastAPI que usa una API key de OpenAI para un endpoint de generación de texto. Antes de aplicar estas medidas, mi estructura era:

mi-proyecto/
  .env                    # OPENAI_API_KEY=sk-...
  docker-compose.yml      # env_file: .env
  app/
    main.py               # os.getenv("OPENAI_API_KEY")

Después:

mi-proyecto/
  .claude/
    settings.json          # permissions.deny configurado
  docker-compose.yml       # usa secrets, no env_file
  .pre-commit-config.yaml  # detect-secrets hook
  app/
    main.py                # lee desde /run/secrets/openai_key

~/.secrets/mi-proyecto/
  openai_key.txt           # el secret vive fuera del proyecto

El código de la app cambia mínimo:

import os
from pathlib import Path

def get_secret(name: str) -> str:
    """Lee un secret desde archivo (Docker secrets) o variable de entorno."""
    secret_path = Path(f"/run/secrets/{name}")
    if secret_path.exists():
        return secret_path.read_text().strip()
    # Fallback para desarrollo local
    value = os.getenv(name.upper())
    if not value:
        raise ValueError(f"Secret '{name}' no encontrado")
    return value

# Uso
openai_key = get_secret("openai_key")

En mi experiencia, esta migración me llevó unas dos horas para un proyecto con 5 secrets. La mayor parte del tiempo fue actualizar los scripts de CI/CD para inyectar secrets desde el vault del proveedor de cloud en lugar de pasarlos como variables de entorno.

En Producción

Lo que funciona en un tutorial no siempre sobrevive al contacto con producción. Estas son las consideraciones que he aprendido:

Rendimiento: leer secrets desde archivo (/run/secrets/) en lugar de variables de entorno añade latencia despreciable, del orden de microsegundos. Pero cárgalos al inicio de la app, no en cada request.

Rotación de secrets: las variables de entorno requieren reiniciar el contenedor para actualizar un secret. Los archivos montados desde Docker secrets pueden actualizarse con un redeploy del servicio sin rebuild de imagen. Si usas un vault (HashiCorp Vault, GCP Secret Manager), la rotación es automática.

Costes: GCP Secret Manager cobra ~0,06 €/mes por 10.000 accesos. Para la mayoría de proyectos pequeños o medianos, el coste es insignificante. HashiCorp Vault self-hosted es gratis pero necesitas mantener la infraestructura.

Escalabilidad: con Docker Swarm o Kubernetes, los secrets nativos de la plataforma escalan con el clúster. No reinventes la rueda: usa kubectl create secret o los secrets de tu orquestador.

Lo que cambia respecto al tutorial: en producción real no puedes asumir que todos los developers del equipo configurarán su settings.json correctamente. La defensa tiene que estar en capas: permisos del agente + secrets fuera del proyecto + pre-commit hooks + escaneo en CI. Si falla una capa, otra lo atrapa.

Limitación honesta: el sandboxing de Claude Code en Linux usa bubblewrap, que funciona bien en la mayoría de distribuciones. En entornos corporativos con SELinux o AppArmor restrictivo, necesitarás ajustar políticas. No he probado esto en más de 3 configuraciones distintas de SELinux, así que tu experiencia puede variar.

Errores comunes y depuración

Error: .claudeignore configurado pero el agente sigue leyendo .env
Causa: .claudeignore no bloquea de forma fiable a febrero de 2026. Los system reminders pueden filtrar contenido de archivos modificados.
Solución: usa permissions.deny en .claude/settings.json y mueve el archivo fuera del directorio del proyecto.

Error: docker compose config muestra secrets en texto plano
Causa: este comando resuelve todas las variables de entorno y las muestra interpoladas. Es su comportamiento esperado.
Solución: añade Bash(docker compose config*) a permissions.deny. Migra a Docker secrets montados en archivo.

Error: el agente funciona en sandbox pero no puede instalar dependencias
Causa: network.allowOutbound: false bloquea todo el tráfico saliente, incluidos los registros de paquetes.
Solución: añade los dominios necesarios a allowedDomains (npm, PyPI, etc.).

Preguntas frecuentes

¿Es seguro usar Claude Code con proyectos que tienen API keys?

Sí, pero necesitas configuración explícita. Por defecto, Claude Code lee archivos .env automáticamente. Configura permissions.deny en .claude/settings.json para bloquear el acceso a archivos sensibles y mueve los secrets fuera del directorio del proyecto. Sin estas medidas, tus keys están expuestas.

¿El sandboxing reemplaza la necesidad de proteger secrets manualmente?

No. El sandboxing es una capa adicional, no un sustituto. Restringe lo que el agente puede acceder a nivel de SO, pero si tus secrets están dentro de las rutas permitidas, el agente los leerá igualmente. La defensa en profundidad (deny rules + secrets externos + sandbox + pre-commit) es el enfoque correcto.

¿Estas medidas aplican solo a Claude Code o a otros agentes también?

El problema es común a todos los agentes de código con acceso al sistema de archivos: Cursor, Copilot Workspace, Codex CLI, Windsurf. Cada herramienta tiene su propio mecanismo de permisos, pero el principio es idéntico: no confíes en el agente para proteger tus secrets, protégelos tú a nivel de infraestructura.

Conclusión: confianza cero, productividad máxima

Tres puntos clave de este artículo:

  1. Los agentes de código leen más de lo que crees. Archivos .env, salida de comandos shell, contexto de conversación. Asume que cualquier dato accesible será leído.
  2. La defensa es en capas, no en confianza. permissions.deny + secrets fuera del proyecto + sandbox + pre-commit hooks. Si falla una capa, otra atrapa la filtración.
  3. No tienes que dejar de usar agentes. Solo tienes que tratarlos como lo que son: un colaborador con acceso a tu terminal que no entiende qué información es sensible.

¿Has tenido algún susto con secrets expuestos por un agente de IA? ¿Qué medidas usas en tu equipo? En el próximo artículo exploraremos cómo configurar Claude Agent Teams de forma segura para que múltiples agentes trabajen en paralelo sin comprometer credenciales compartidas.