Biblioteca · Lanzamiento: pagos, secretos, normas y registros

Seguridad en Claude Code: .env, secretos y protección de datos

Usuario con confianza55 minActualizado: octubre de 2026
85 de 105 en la biblioteca

Módulo: Práctica profesional | Tiempo: ~25 min de teoría + 30 min de práctica


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

🎨 Imagínalo así: Claude Code es un contratista muy listo al que le diste las llaves de toda la oficina. Puede entrar a cualquier cuarto y leer cualquier documento. Normalmente eso es una ventaja. Pero si dejaste por error las llaves de la caja fuerte sobre una mesa a la vista de todos, el problema ya no es el contratista.

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

🎨 Imagínalo así: .env es la caja fuerte de la trastienda. El aparador (el código) es para todos. La caja fuerte (los secretos), solo para el cajero. El dinero no se guarda en el aparador.

Estructura básica de un archivo .env:

bash
# .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=info

Junto a .env se crea .env.example: una plantilla sin valores que sí va a Git:

bash
# .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=development

Es 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:

Código
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

🎨 Imagínalo así: .gitignore es la lista de cosas que no pones en el clóset compartido. El equipo ve el clóset. El pasaporte y las llaves de la caja fuerte se quedan en tu bolsillo.

Créalo antes del primer git init, o por lo menos antes del primer git add:

bash
# .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/
*.swp

Verificación antes de cada commit:

bash
git status
# .env no debe aparecer en la lista; si aparece, detente

Si .env ya llegó a Git (ya pasó):

bash
git rm --cached .env
git commit -m "Remove .env from tracking"
# Cambia sin falta TODAS las claves de ese archivo: están comprometidas

Có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.

json
{
  "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 Read bloquea 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.

🎨 Imagínalo así: si .gitignore es la lista de lo que no se manda a los colegas, las reglas deny son la lista de lo que no se le enseña a un consultor temporal. El consultor no necesita ver los contratos con los clientes para configurar la contabilidad. Y recuerda: un letrero de "no pasar" en una puerta sin cerradura (.claudeignore) no protege nada.


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

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 .env aunque se lo pidas
  • deny: ["Bash(rm -rf *)"]: protección contra borrar todo por accidente
  • deny: ["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)

🎨 Imagínalo así: es como la lista de acceso de una obra. El guardia solo deja pasar a quien está en la lista, aunque la persona diga "el director me dio permiso". La lista pesa más que las palabras.

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

🎨 Imagínalo así: un pre-commit hook es como el arco detector de metales a la salida de una instalación restringida. No puedes salir sin pasar la revisión. Si se te olvidó una llave en el bolsillo, el arco suena antes de que llegues a la calle.

Se crea en .git/hooks/pre-commit:

bash
#!/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 0

Instalación:

bash
chmod +x .git/hooks/pre-commit

O con pre-commit (una opción más potente):

bash
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: gitleaks
bash
pre-commit install
# Desde ahora corre automáticamente en cada git commit

Verifica que el hook funciona:

bash
# 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.txt

Secretos 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:

bash
# 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_KEY

En el código de un Cloudflare Worker, los secretos están disponibles en el objeto env:

javascript
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:

bash
# 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 Variables

Railway:

bash
# 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 → Variables

Principio: 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

🎨 Imagínalo así: 1Password es la caja de seguridad del banco donde se guarda el original. .env es la copia de trabajo que traes en el bolsillo. Los secretos de Cloudflare son la copia en la oficina. El original es uno solo, en la caja del banco.

Estructura del vault en 1Password:

Código
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 Token

1Password CLI: llenar .env automáticamente:

bash
# 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 .env

Ya 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:

Escribe esto en el chat
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

🎨 Imagínalo así: una doctora presenta un caso clínico en un congreso, pero nunca dice el nombre del paciente. "Hombre de 45 años" en lugar de "Juan Pérez". El mismo principio aplica a los datos de clientes en un LLM.

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:

python
# Pasamos datos reales a Claude: una violación
prompt = f"""
Analiza este cliente:
Nombre: Juan Pérez
Teléfono: +52 55 1234 5678
Email: juan.perez@example.com
Presupuesto: $80,000
"""

Después: lo correcto:

python
# 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

bash
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 .env

Paso 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

bash
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)"
    ]
  }
}
EOF

Paso 4: Pre-commit hook

bash
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-commit

Prueba que el hook funciona:

bash
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.txt

Paso 5: Auditoría de seguridad con Claude

Abre Claude Code en la carpeta del proyecto y escribe:

Escribe esto en el chat
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

bash
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ía

Resultado: 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 inject para llenar .env automá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 .claudeignore no existe en Claude Code. .gitignore le dice a Git qué no rastrear, y las reglas deny de settings.json le dicen a Claude qué no leer.

Los datos PII de los clientes se seudonimizan antes de enviarlos a un LLM. Client_42 en 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