Lo esencial
Claude Code ve todo lo que hay en la carpeta del proyecto, y por un git push descuidado puede mostrárselo a todo internet por accidente. Configurar tu security stack es como ponerle cerradura a la puerta antes de mudarte, no después del primer robo.
Conceptos clave
.env: el archivo con los secretos; vive en tu computadora y nunca llega a Git.gitignore: la lista de lo que Git no empaca en el repositorio- Los permisos de
settings.json: listas allow/deny para las herramientas de Claude; son las reglas deny las que ocultan archivos a Claude (en Claude Code no existe el archivo.claudeignore) - Pre-commit hook: una verificación automática antes del commit que detiene la fuga
- Secretos de Cloudflare / Vercel: almacenes de secretos para producción
- 1Password CLI: la bóveda maestra de donde sale todo
- Seudonimización de PII: los datos personales de los clientes nunca van crudos a un LLM
Teoría
Por qué es crítico: Claude Code ve todo
Al trabajar, Claude Code lee los archivos de la carpeta actual. Cuando le pides "revisa este proyecto", se asoma a todo: código fuente, configuraciones, a veces .env. Si después se hace git commit y git push, todo lo que no excluiste se va al repositorio.
El costo real de un error:
- Clave de AWS en un repo público → los bots la encuentran en minutos → una factura de miles de dólares en una noche
- Clave de la API de Anthropic → alguien se gasta todo tu límite en un fin de semana → el proyecto se cae
- Clave de producción de Stripe → acceso directo al dinero de los clientes
- URL de la base de datos → una copia completa de la base, con los datos de los clientes incluidos
La regla que hay que recordar de una vez y para siempre: un secreto que llegó aunque sea una vez al historial de Git se considera público. Aunque hagas git rm en el siguiente commit, queda para siempre en el historial. La única solución: cambiar la clave.
Estructura de .env: bien desde la primera vez
Estructura básica de un archivo .env:
# .env — NUNCA hagas commit en Git
# === AI / LLM ===
ANTHROPIC_API_KEY=sk-ant-api03-...
OPENAI_API_KEY=sk-proj-...
PERPLEXITY_API_KEY=pplx-...
# === Pagos ===
STRIPE_SECRET_KEY=sk_live_...
STRIPE_WEBHOOK_SECRET=whsec_...
# === Base de datos ===
DATABASE_URL=postgresql://user:password@localhost:5432/mydb
# === Telegram / Bots ===
TELEGRAM_BOT_TOKEN=7123456789:AAF...
# === Configuración del entorno ===
NODE_ENV=development
LOG_LEVEL=infoJunto a .env se crea .env.example: una plantilla sin valores que sí va a Git:
# .env.example — HAZ COMMIT DE ESTE ARCHIVO (sin valores, solo los nombres)
ANTHROPIC_API_KEY=sk-ant-your-key-here
OPENAI_API_KEY=sk-proj-your-key-here
STRIPE_SECRET_KEY=sk_live_your-key-here
DATABASE_URL=postgresql://user:password@host:5432/dbname
TELEGRAM_BOT_TOKEN=your-token-here
NODE_ENV=developmentEs tu documentación para los colegas (y para ti dentro de seis meses): qué variables se necesitan para que el proyecto arranque.
Estructura de archivos del proyecto:
project/
├── .env ← claves reales (NO va a Git)
├── .env.example ← plantilla sin valores (sí va a Git)
├── .env.test ← claves falsas para pruebas (no va a Git)
├── .gitignore ← protección
├── .claude/
│ └── settings.json ← reglas: qué no lee ni ejecuta Claude
└── src/.gitignore: la primera línea de defensa
Créalo antes del primer git init, o por lo menos antes del primer git add:
# .gitignore
# Secretos — NUNCA en Git
.env
.env.local
.env.*.local
.env.production
.env.staging
# Claves y certificados
*.pem
*.key
*.p12
*.pfx
id_rsa
id_ed25519
# Dependencias
node_modules/
__pycache__/
*.pyc
.venv/
# Del sistema
.DS_Store
Thumbs.db
# IDE
.cursor/
.idea/
*.swpVerificación antes de cada commit:
git status
# .env no debe aparecer en la lista; si aparece, detenteSi .env ya llegó a Git (ya pasó):
git rm --cached .env
git commit -m "Remove .env from tracking"
# Cambia sin falta TODAS las claves de ese archivo: están comprometidasCómo ocultar archivos a Claude: permissions.deny (no .claudeignore)
Un error frecuente: muchos artículos y respuestas de IA recomiendan crear un archivo .claudeignore. Ese archivo no existe en Claude Code: no hace nada y da una falsa sensación de seguridad. La forma que funciona es una sola: reglas deny en .claude/settings.json (ver la siguiente sección). Git y Claude son herramientas distintas, cada una con sus límites: .gitignore le dice a Git qué no rastrear, y deny le dice a Claude qué no leer.
{
"permissions": {
"deny": [
"Read(./.env)",
"Read(./.env.*)",
"Read(./secrets/**)",
"Read(**/*.pem)",
"Read(**/*.key)"
]
}
}Qué más ayuda:
- No tengas secretos de producción en la carpeta del proyecto: para desarrollar solo necesitas una clave de desarrollo, y las claves de producción viven en el almacén de la plataforma (más abajo, en la sección sobre producción).
- Un deny sobre
Readbloquea la herramienta de lectura integrada. Para cerrar los atajos por línea de comandos (cat .env), activa el sandbox (/sandbox, disponible en las plataformas compatibles) y agrega deny para los comandos necesarios. - Las carpetas con datos de clientes (
data/clients/,exports/,backups/) ciérralas con las mismas reglas o, mejor aún, mantenlas fuera del proyecto.
Permisos en settings.json: listas allow/deny
Truco #30 de la lección 32 trucos de Claude Code: settings.json permite fijar de forma estricta lo que Claude puede y no puede hacer, sin importar lo que escribas en el chat.
Archivo: .claude/settings.json
{
"permissions": {
"allow": [
"Bash(git add *)",
"Bash(git commit *)",
"Bash(git status)",
"Bash(git log *)",
"Bash(npm install)",
"Bash(npm run *)",
"Bash(node *)",
"Bash(python *)"
],
"deny": [
"Bash(rm -rf *)",
"Bash(git push --force *)",
"Bash(git push -f *)",
"Bash(DROP *)",
"Bash(DELETE FROM *)",
"Read(./.env)",
"Read(./.env.*)",
"Read(**/*.pem)",
"Read(**/*.key)",
"Read(./secrets/**)"
]
}
}Qué te da esto:
deny: ["Read(./.env)"]: Claude no lee.envaunque se lo pidasdeny: ["Bash(rm -rf *)"]: protección contra borrar todo por accidentedeny: ["Bash(git push --force *)"]: el force push solo a mano, no a través de Claude- La lista
allow: la lista explícita de lo permitido; lo demás pide confirmación (en el modo auto, que desde la versión 2.1.283 es el modo inicial por defecto, un modelo clasificador aparte revisa además las acciones, pero las reglas deny siguen siendo la barrera principal)
Importante: settings.json se sube al repositorio (sin secretos, solo reglas). Así la configuración viaja con el proyecto.
Pre-commit hook: verificación automática
Se crea en .git/hooks/pre-commit:
#!/bin/bash
# Pre-commit security check
# Se instala con: chmod +x .git/hooks/pre-commit
echo "Buscando secretos antes del commit..."
# Patrones que no deben llegar al commit
PATTERNS=(
"sk-ant-"
"sk-proj-"
"sk_live_"
"sk_test_"
"AKIA[0-9A-Z]{16}"
"whsec_"
"xoxb-"
"xoxp-"
"ghp_"
"glpat-"
"password\s*=\s*['\"][^'\"]{8,}"
"secret\s*=\s*['\"][^'\"]{8,}"
"DATABASE_URL\s*=\s*postgresql"
)
FOUND=0
for PATTERN in "${PATTERNS[@]}"; do
if git diff --cached | grep -iE "$PATTERN" > /dev/null 2>&1; then
echo ""
echo "ALTO: ¡se encontró un secreto en el commit!"
echo "Patrón: $PATTERN"
echo ""
git diff --cached | grep -iE --color "$PATTERN"
FOUND=1
fi
done
if [ $FOUND -eq 1 ]; then
echo ""
echo "Commit bloqueado. Quita los secretos de los archivos en staging."
echo "Usa: git reset HEAD <archivo> para sacar el archivo del staging"
exit 1
fi
echo "No se encontraron secretos. Commit permitido."
exit 0Instalación:
chmod +x .git/hooks/pre-commitO con pre-commit (una opción más potente):
pip install pre-commit
# .pre-commit-config.yaml
repos:
- repo: https://github.com/gitleaks/gitleaks
rev: v8.x.x # revisa la etiqueta vigente en el repositorio de gitleaks
hooks:
- id: gitleakspre-commit install
# Desde ahora corre automáticamente en cada git commitVerifica que el hook funciona:
# Agrega temporalmente una línea con un "secreto" a un archivo
echo "ANTHROPIC_API_KEY=sk-ant-test123" >> test.txt
git add test.txt
git commit -m "test"
# Debe bloquearse
git reset HEAD test.txt
rm test.txtSecretos en producción: Cloudflare, Vercel, Railway
En producción no existe un archivo .env. Los secretos se guardan en los almacenes cifrados de la plataforma.
Cloudflare Workers Secrets:
# Guardar un secreto (wrangler te pedirá el valor de forma interactiva)
wrangler secret put ANTHROPIC_API_KEY
> Enter a secret value: [escribes la clave; no se ve en pantalla]
# Lista de secretos guardados (solo los nombres; los valores no se pueden ver)
wrangler secret list
# Borrar
wrangler secret delete OLD_KEYEn el código de un Cloudflare Worker, los secretos están disponibles en el objeto env:
export default {
async fetch(request, env) {
// env.ANTHROPIC_API_KEY — viene de wrangler secret
const client = new Anthropic({ apiKey: env.ANTHROPIC_API_KEY });
}
};Vercel Environment Variables:
# Con la CLI
vercel env add ANTHROPIC_API_KEY production
vercel env add ANTHROPIC_API_KEY preview
vercel env add ANTHROPIC_API_KEY development
# O desde el dashboard: vercel.com → Project → Settings → Environment VariablesRailway:
# Con la CLI (revisa la sintaxis en la documentación de Railway; ha cambiado)
railway variables set ANTHROPIC_API_KEY=sk-ant-...
# O desde el dashboard: railway.app → Project → VariablesPrincipio: entornos distintos, claves distintas. La clave de desarrollo con un límite de gasto pequeño. La de producción con el límite de trabajo. Un bug en desarrollo no debe gastar el presupuesto de producción.
1Password CLI: la bóveda maestra
Estructura del vault en 1Password:
1Password → AI Projects (un vault aparte)
├── Newsletter Automation
│ ├── Anthropic API Key (prod)
│ ├── Anthropic API Key (dev) ← una aparte, con límite
│ └── Stripe Keys
├── Telegram Bot Project
│ └── Bot Token
└── Shared Infrastructure
├── Cloudflare API Token
└── GitHub Token1Password CLI: llenar .env automáticamente:
# Instalación
brew install 1password-cli
op signin
# En .env.example pon referencias a 1Password
ANTHROPIC_API_KEY=op://AI-Projects/Newsletter/anthropic-prod-key
STRIPE_SECRET_KEY=op://AI-Projects/Newsletter/stripe-secret
# Llena .env automáticamente desde la bóveda
op inject -i .env.example -o .envYa no copias claves a mano: solo op inject y listo.
Revisión de seguridad con Claude
La forma más rápida de encontrar problemas en un proyecto existente es pedirle a Claude una auditoría:
Haz una revisión de seguridad de este proyecto: 1. ¿Hay claves de API o contraseñas escritas directamente en el código (fuera de .env)? 2. ¿Todos los archivos sensibles están en .gitignore? 3. ¿Hay un .env.example sin valores reales? 4. ¿Hay PII de clientes en los logs o en cadenas escritas en el código? 5. ¿Están bien configurados los permisos en .claude/settings.json? Muéstrame la lista de problemas encontrados con el archivo y la línea.
El resultado es una lista concreta: src/api.js:23 — STRIPE_KEY escrita en el código. Los corriges uno por uno. Para los cambios en una rama también existe el comando integrado /security-review: revisa las diferencias entre tu rama y la principal en busca de vulnerabilidades típicas. Es una ayuda, no un sustituto de la revisión humana.
PII y datos de clientes: seudonimización
La PII (Personally Identifiable Information, información de identificación personal), es decir, nombres, teléfonos, correos, pasaportes, números de identificación fiscal y direcciones, no debe ir cruda a Claude.
Antes: lo que no se debe hacer:
# Pasamos datos reales a Claude: una violación
prompt = f"""
Analiza este cliente:
Nombre: Juan Pérez
Teléfono: +52 55 1234 5678
Email: [email protected]
Presupuesto: $80,000
"""Después: lo correcto:
# Seudonimización antes de enviar al LLM
def pseudonymize(client):
return {
"id": f"Client_{client['id']}",
"budget_usd": client['budget'],
"region": client['city'], # solo la región, no la dirección exacta
"property_type": client['type']
}
client_data = pseudonymize(raw_client)
prompt = f"""
Analiza este cliente:
ID: {client_data['id']}
Presupuesto: ${client_data['budget_usd']:,}
Región: {client_data['region']}
Tipo de inmueble: {client_data['property_type']}
"""Reglas para la PII:
- Nombres →
Client_42,User_789 - Teléfonos → no enviarlos si la tarea no los necesita
- Correos → no enviarlos si no hacen falta
- Presupuesto → se puede enviar por rangos (
$50K-100K) si la cifra exacta no hace falta - Direcciones → solo ciudad o región, no la dirección exacta
No es paranoia: es el GDPR en Europa, la Ley Orgánica de Protección de Datos en España, las leyes de protección de datos personales de cada país de América Latina y el sentido común en todas partes. Más sobre regulación: AI Regulation & Compliance 2026.
Práctica
Tarea: configurar un security stack básico
Paso 1: Protección básica
mkdir secure-project && cd secure-project
git init
# Crea .gitignore
cat > .gitignore << 'EOF'
.env
.env.*
*.pem
*.key
node_modules/
__pycache__/
.DS_Store
EOF
# Crea .env con datos de prueba
cat > .env << 'EOF'
ANTHROPIC_API_KEY=sk-ant-test-placeholder
STRIPE_SECRET_KEY=sk_live_test-placeholder
DATABASE_URL=postgresql://localhost:5432/testdb
EOF
# Crea .env.example (va a Git)
cat > .env.example << 'EOF'
ANTHROPIC_API_KEY=sk-ant-your-key-here
STRIPE_SECRET_KEY=sk_live_your-key-here
DATABASE_URL=postgresql://user:password@host:5432/dbname
EOF
# Verificación: .env no debe aparecer en git status
git status
# Salida: solo .gitignore y .env.example, NO .envPaso 2: Saca lo que sobra de la carpeta del proyecto
Revisa que en la carpeta del proyecto solo esté la clave de desarrollo. Deja las claves de producción en el almacén de la plataforma y en tu gestor de contraseñas. No hace falta crear un archivo .claudeignore: no funciona; los archivos se ocultan con las reglas del siguiente paso.
Paso 3: settings.json: ocultar archivos y bloquear comandos peligrosos
mkdir -p .claude
cat > .claude/settings.json << 'EOF'
{
"permissions": {
"allow": [
"Bash(git add *)",
"Bash(git commit *)",
"Bash(git status)",
"Bash(git log *)",
"Bash(npm install)",
"Bash(npm run *)",
"Bash(node *)"
],
"deny": [
"Bash(rm -rf *)",
"Bash(git push --force *)",
"Read(./.env)",
"Read(./.env.*)",
"Read(**/*.pem)",
"Read(**/*.key)"
]
}
}
EOFPaso 4: Pre-commit hook
cat > .git/hooks/pre-commit << 'EOF'
#!/bin/bash
echo "Security check..."
PATTERNS=("sk-ant-" "sk-proj-" "sk_live_" "whsec_" "AKIA" "ghp_")
FOUND=0
for P in "${PATTERNS[@]}"; do
if git diff --cached | grep -E "$P" > /dev/null 2>&1; then
echo "ALTO: se encontró un secreto (patrón: $P)"
FOUND=1
fi
done
[ $FOUND -eq 1 ] && exit 1
echo "OK: no se encontraron secretos"
exit 0
EOF
chmod +x .git/hooks/pre-commitPrueba que el hook funciona:
echo "ANTHROPIC_API_KEY=sk-ant-realkey123" >> test-leak.txt
git add test-leak.txt
git commit -m "test"
# Debe bloquearse
git reset HEAD test-leak.txt && rm test-leak.txtPaso 5: Auditoría de seguridad con Claude
Abre Claude Code en la carpeta del proyecto y escribe:
Haz una auditoría de seguridad de este proyecto: 1. ¿Hay secretos escritos directamente en archivos .js/.py? 2. ¿Está bien configurado .gitignore? 3. ¿Los archivos sensibles están ocultos con reglas deny en .claude/settings.json? 4. Revisa .claude/settings.json: ¿las restricciones son suficientes? Dame una lista de problemas concretos con archivos y líneas.
Paso 6: El primer commit seguro
git add .gitignore .env.example .claude/settings.json
git commit -m "Security stack: gitignore, settings.json deny rules, pre-commit hook"
# Verifica que .env no se coló
git show HEAD --name-only | grep .env
# La salida debe estar vacíaResultado: tres capas de protección (.gitignore + pre-commit hook + settings.json deny) reducen bastante el riesgo de filtrar un secreto a Git por accidente. No son una garantía total: las claves igual hay que guardarlas bien y cambiarlas a la menor sospecha.
Herramientas y recursos
- gitleaks: escáner de secretos filtrados en repositorios Git,
brew install gitleaks - git-secrets: hook pre-commit de AWS,
brew install git-secrets - pre-commit: framework para hooks pre-commit,
pip install pre-commit - 1Password CLI:
op injectpara llenar.envautomáticamente - Cloudflare Workers Secrets:
wrangler secret put - Vercel Environment Variables: desde el dashboard o con
vercel env add - dotenv (Node.js):
npm install dotenv - python-dotenv (Python):
pip install python-dotenv - GitHub Secret Scanning: activado por defecto; te avisa si una clave llegó a un repo público
- Documentación de Claude Code: Permissions: documentación oficial de las reglas de acceso
Conclusiones clave
Un secreto que llegó al historial de Git se considera público para siempre, aunque el repositorio sea privado. La única solución: cambiar la clave.
Tres capas de protección:
.gitignore(no lo rastrea), pre-commit hook (bloquea el commit),settings.json deny(Claude no lo lee). Juntas reducen mucho el riesgo de una fuga.
El archivo
.claudeignoreno existe en Claude Code..gitignorele dice a Git qué no rastrear, y las reglas deny desettings.jsonle dicen a Claude qué no leer.
Los datos PII de los clientes se seudonimizan antes de enviarlos a un LLM.
Client_42en lugar de "Juan Pérez".
Desarrollo y producción: siempre claves distintas. Un bug en desarrollo no debe costar el presupuesto de producción.
Siguiente lección
→ AI Ethics & Safety: alucinaciones, ataques y sesgos, o cómo no confiar ciegamente en la IA
La marca se guarda solo en este navegador y no se envía a ningún sitio. Mi progreso