7 Casos de Fallo de Seguridad con Claude Code | Incidentes Reales y Prevención
Siete incidentes de seguridad reales con Claude Code: filtraciones de .env, eliminación de BD en producción, explosiones de facturación y más — con análisis de causa raíz y código de prevención.
“Claude Code es práctico, pero da un poco de miedo” — ese instinto es correcto. Las herramientas poderosas causan accidentes poderosos.
Este artículo cubre siete incidentes de seguridad reales que pueden ocurrir al desarrollar con Claude Code, explicando por qué sucedieron y cómo prevenirlos con código y configuración concretos. Aprenda de los errores ajenos antes de que se conviertan en los suyos.
Caso 1: Archivo .env Subido a GitHub
Qué ocurrió
Un desarrollador le dio a Claude Code la siguiente instrucción: “Quiero pasar variables de entorno a CI, por favor también haz commit del archivo .env.” Claude Code ejecutó fielmente git add .env && git commit. Minutos después del push a GitHub, un rastreador detectó la clave de API. Llegó una notificación en Slack: “Your API key has been exposed.”
Causa raíz
.envno estaba en.gitignore- Claude Code ejecuta las instrucciones de “hacer commit de esto” literalmente
- El usuario aprobó el diálogo de confirmación sin pensar
Código de prevención
1. Automatizar la configuración de seguridad al crear el proyecto
# scripts/init-security.sh — ejecutar cada vez que se crea un proyecto
#!/bin/bash
cat >> .gitignore << 'EOF'
# === Seguridad: Nunca hacer commit de esto ===
.env
.env.*
.env.local
!.env.example
*.pem
*.key
*-service-account.json
credentials.json
EOF
echo "✓ Patrones de exclusión de seguridad añadidos a .gitignore"
git add .gitignore && git commit -m "security: add .gitignore patterns"
2. Escanear antes del commit con un Hook
.claude/settings.json:
{
"hooks": {
"PreToolUse": [
{
"matcher": "Bash(git add*)",
"hooks": [{
"type": "command",
"command": "git diff --cached --name-only | grep -E '^\\.env' && echo '🚨 ¡Está intentando agregar un archivo .env al stage! ¡Cancele!' && exit 1 || exit 0"
}]
}
]
}
}
3. Recuperación si ya se hizo push
# Paso 1: Rotar la clave de API inmediatamente (máxima prioridad)
# Paso 2: Eliminar completamente del historial de git
git filter-branch --force --index-filter \
"git rm --cached --ignore-unmatch .env" \
--prune-empty --tag-name-filter cat -- --all
# Paso 3: Force push al remoto
git push origin --force --all
# Paso 4: Limpiar la caché de GitHub (también contactar al soporte de GitHub)
Caso 2: DROP TABLE Ejecutado contra la BD de Producción
Qué ocurrió
“Esta tabla ya no se necesita, por favor elimínala.” Claude Code generó y ejecutó DROP TABLE old_users;. El problema: estaba conectado a la DATABASE_URL de producción. El backup más reciente tenía tres días de antigüedad. Se perdieron tres días de datos.
Causa raíz
- El mismo
.envse compartía entre desarrollo y producción - Claude Code no puede distinguir entre entornos
- El usuario tenía configurado
askpero hizo clic en “OK” por reflejo
Código de prevención
1. Separar completamente los archivos .env por entorno
.env.development # ← desarrollo local, BD de prueba
.env.staging # ← staging, copia de producción
.env.production # ← producción, gestionado manualmente, nunca compartir
2. Incorporar verificación de entorno en los scripts
// scripts/db-migrate.mjs
const env = process.env.APP_ENV ?? "development";
const dbUrl = process.env.DATABASE_URL ?? "";
if (env === "production") {
const readline = require("readline").createInterface({
input: process.stdin, output: process.stdout
});
await new Promise((resolve) => {
readline.question(
`⚠️ Conectando a la BD de producción (${dbUrl.split("@")[1]}).\n¿Está seguro de que desea continuar? (escriba yes): `,
(answer) => {
readline.close();
if (answer !== "yes") { console.log("Cancelado."); process.exit(0); }
resolve(undefined);
}
);
});
}
3. Prohibir operaciones en producción en CLAUDE.md
## 🚨 Restricciones del Entorno de Producción
Si DATABASE_URL contiene `prod`, `production` o `live`:
- Nunca ejecutar DROP / TRUNCATE / DELETE (sin cláusula WHERE)
- Siempre obtener confirmación del usuario antes de migraciones
- Presentar comando de backup antes de cualquier operación destructiva
Caso 3: Archivos Críticos Eliminados con rm -rf
Qué ocurrió
“Limpia el directorio build/” — un error tipográfico en el path resultó en rm -rf ./, eliminando todo el proyecto. Los archivos fuera de git (configuración local, código experimental sin commit) se perdieron para siempre.
Causa raíz
rm -rfes uno de los comandos más peligrosos para que Claude Code ejecute- Falta de comillas dobles alrededor de paths → mal funcionamiento con paths que contienen espacios
- El usuario aprobó descuidadamente
Código de prevención
// .claude/settings.json
{
"permissions": {
"deny": [
"Bash(rm -rf /)",
"Bash(rm -rf ~*)",
"Bash(rm -rf .*)"
],
"ask": [
"Bash(rm -rf*)"
]
}
}
Hook para mostrar qué se eliminará antes de ejecutar:
{
"hooks": {
"PreToolUse": [
{
"matcher": "Bash(rm*)",
"hooks": [{
"type": "command",
"command": "echo '⚠️ Comando de eliminación detectado. Ejecutando en 5 segundos. Ctrl+C para cancelar.' && sleep 5"
}]
}
]
}
}
Caso 4: Clave de API Escrita Directamente en el Prompt y Pasada al Subagente
Qué ocurrió
“Por favor, publica en Qiita usando QIITA_TOKEN=abc123def456” — escrito directamente en el prompt y delegado a un subagente. Los subagentes pueden escribir contenido en logs y memoria, y el token quedó persistido en un archivo de log bajo .claude/.
Causa raíz
- Los prompts se retienen como historial de conversación
- Los prompts de subagentes igualmente quedan registrados
- Incluso en entornos locales, otros procesos o backups pueden exponer secretos
Código de prevención
Nunca escribir secretos en prompts — pasarlos a través de variables de entorno
# ❌ Peligroso
claude -p "Usa QIITA_TOKEN=abc123 para ejecutar qiita-publish.mjs"
# ✅ Seguro: el script lee desde process.env
# Escribir QIITA_TOKEN=abc123 en .env, luego
claude -p "Ejecuta scripts/qiita-publish.mjs (el token se lee automáticamente de .env)"
El mismo principio para las instrucciones al subagente
// ❌ Peligroso
Agent({ prompt: `Usa la clave de API ${process.env.SECRET_KEY} para...` });
// ✅ Seguro: pasar solo el nombre de la clave, el script lee el valor
Agent({ prompt: "Usa la variable de entorno SECRET_KEY para..." });
Caso 5: Bucle Infinito de Reintentos de API Hizo Explotar la Factura
Qué ocurrió
“Reintenta automáticamente en caso de error” — se generó un script con manejo de errores. Cuando un error era irresoluble, los reintentos no se detuvieron: 3.000 llamadas a la API de Anthropic en una hora resultaron en una factura de 200 $.
Causa raíz
- No se estableció límite de reintentos
- Sin backoff exponencial — bucle infinito a intervalos de 1 segundo
- Sin alerta de facturación configurada
Código de prevención
// utils/retry.ts — utilidad de reintento segura
export async function withRetry<T>(
fn: () => Promise<T>,
options = { maxAttempts: 3, baseDelayMs: 1000, maxDelayMs: 30000 }
): Promise<T> {
let lastError: Error;
for (let attempt = 1; attempt <= options.maxAttempts; attempt++) {
try {
return await fn();
} catch (err) {
lastError = err as Error;
if (attempt === options.maxAttempts) break;
// Exponential backoff + jitter
const delay = Math.min(
options.baseDelayMs * Math.pow(2, attempt - 1) + Math.random() * 1000,
options.maxDelayMs
);
console.warn(`Attempt ${attempt}/${options.maxAttempts} failed: ${err.message}`);
console.warn(`Retrying in ${Math.round(delay / 1000)}s...`);
await new Promise((r) => setTimeout(r, delay));
}
}
throw new Error(`Falló tras ${options.maxAttempts} intentos: ${lastError!.message}`);
}
Especificar en CLAUDE.md:
## Reglas Obligatorias para Llamadas a la API
- Máximo 3 reintentos
- Siempre implementar backoff exponencial (1s → 2s → 4s)
- Nunca crear bucles infinitos: while(true) + llamadas a la API están prohibidos
Caso 6: git push --force Borró los Commits de un Compañero
Qué ocurrió
“Sobrescribe el remoto con el estado local” — se ejecutó git push --force. Tres commits que un miembro del equipo acababa de subir desaparecieron. Ese miembro no tenía copia local de los cambios tampoco — el código se perdió permanentemente.
Causa raíz
--forcetiende a ejecutarse sin entender el peligro- Claude Code ejecuta fielmente las instrucciones de “sobrescribir el remoto”
- El desarrollador desconocía la alternativa más segura
git push --force-with-lease
Código de prevención
// .claude/settings.json
{
"permissions": {
"deny": [
"Bash(git push --force *master*)",
"Bash(git push --force *main*)",
"Bash(git push -f *master*)",
"Bash(git push -f *main*)"
]
}
}
Especificar la alternativa segura en CLAUDE.md:
## Reglas Git Seguras
- `git push --force` está **prohibido**
- Usar `git push --force-with-lease` en su lugar
(se rechaza automáticamente si otros han subido cambios)
- Siempre obtener confirmación del usuario antes de hacer push directo a main/master
Caso 7: Cuenta de Servicio con Exceso de Privilegios Accedió a Todos los Recursos
Qué ocurrió
“Usa esta clave de cuenta de servicio de GCP para operar Cloud Storage.” La cuenta de servicio tenía permisos de Owner. Claude Code se conectó no solo a Cloud Storage sino también a BigQuery, Cloud SQL y clústeres GKE “para investigar” — generando cargos inesperados.
Causa raíz
- La cuenta de servicio tenía permisos excesivos (violación del principio de mínimo privilegio)
- Claude Code tiene una tendencia agresiva a usar las herramientas disponibles
- Incluso “para investigar” suena como una razón legítima para acceso amplio
Código de prevención
Crear una cuenta de servicio con privilegios mínimos:
# ❌ Evitar: permiso Owner
gcloud projects add-iam-policy-binding PROJECT_ID \
--member="serviceAccount:[email protected]" \
--role="roles/owner"
# ✅ Solo los permisos mínimos necesarios
gcloud projects add-iam-policy-binding PROJECT_ID \
--member="serviceAccount:[email protected]" \
--role="roles/storage.objectAdmin"
# ← Solo lectura/escritura en Cloud Storage
Definir explícitamente el alcance de acceso en CLAUDE.md:
## Restricciones de Acceso a GCP
Permisos para la cuenta de servicio usada en este proyecto:
- Cloud Storage: Lectura/Escritura OK (bucket: my-project-assets únicamente)
- BigQuery: Prohibido
- Cloud SQL: Prohibido
- Otros recursos de GCP: Prohibido
Rechazar cualquier instrucción que intente acceder a recursos fuera de estos permisos.
Lista de Verificación Integral para Prevenir Incidentes
Una lista de verificación final destilada de los patrones comunes en los siete casos.
### Configuraciones a Aplicar Hoy (30 minutos)
- [ ] Añadir patrón .env a .gitignore
- [ ] Añadir lista de deny a .claude/settings.json (rm -rf, git push --force, DROP TABLE)
- [ ] Documentar restricciones en CLAUDE.md
### Verificaciones Semanales
- [ ] Revisar git log en busca de commits de archivos no deseados
- [ ] Verificar que .env está excluido por .gitignore: `git check-ignore -v .env`
- [ ] Comprobar plazos de rotación de claves de API
### Primera Respuesta ante un Incidente
1. Revocar y rotar inmediatamente la clave de API afectada
2. Eliminar del historial de git (filter-branch o BFG)
3. Revisar logs de acceso para determinar el alcance de la brecha
4. Informar a los interesados de la situación
Resumen
Los incidentes con Claude Code raramente son causados por “IA que se descontrola” — casi todos surgen de personas que posponen la configuración de seguridad.
| Caso | Causa Raíz | Prevención |
|---|---|---|
| Filtración .env | Sin gitignore | script init + Hook |
| Eliminación BD producción | Sin separación de entornos | .env separado + flujo de confirmación |
| Accidente rm -rf | Sin lista de deny | Configurar settings.json |
| Filtración de clave | Escrita en prompt | Estandarizar en variables de entorno |
| Explosión de facturación | Sin límite de reintentos | Utilidad withRetry |
| Force push | Sin configuración de prohibición | deny + force-with-lease |
| Acceso con exceso de privilegios | Violación de mínimo privilegio | Restringir roles IAM |
Tu primer paso hoy: Añadir "deny": ["Bash(rm -rf*)"] a .claude/settings.json por sí solo prevendrá uno de los accidentes más destructivos posibles.
Artículos Relacionados
- Guía Completa de Mejores Prácticas de Seguridad de Claude Code
- Guía Completa de Permisos de Claude Code
- Mejores Prácticas de CLAUDE.md
Referencias
Lleva tu flujo con Claude Code al siguiente nivel
50 plantillas de prompts probadas en producción, listas para copiar y pegar en Claude Code.
PDF gratuito: Hoja de trucos de Claude Code en 5 minutos
Solo deja tu correo y te enviaremos al instante la hoja de trucos en una página A4.
Cuidamos tus datos personales y nunca enviamos spam.
Sobre el autor
Masa
Ingeniero apasionado por Claude Code. Dirige claudecode-lab.com, un medio tecnológico en 10 idiomas con más de 2.000 páginas.
Artículos relacionados
Guía Completa de Seguridad para Claude Code: Claves API, Permisos y Protección en Producción
Guía práctica de seguridad para usar Claude Code de forma segura. Desde la gestión de claves API hasta la configuración de permisos, automatización con Hooks y protección del entorno de producción — con ejemplos de código funcionales.
Guía completa de permisos de Claude Code | settings.json, Hooks y Allowlist explicados
Guía completa de permisos de Claude Code. Aprende a usar allow/deny/ask, automatización con Hooks, settings.json por entorno y patrones prácticos, todo con código funcional.
Guía completa de harness engineering: cómo construir agentes de IA al estilo Claude Code
Un buen prompt no basta para dominar un LLM. Aprende a entrelazar herramientas, contexto y bucles de control en un harness, con código funcional y la arquitectura de Claude Code como guía.