En esta guía te muestro los guardrails de seguridad en Claude Code con código funcional, no con conceptos.

La escribí pensando en quien ya entendió que los guardrails hacen falta, pero se quedó sin saber qué escribir ni cómo. Toco 4 temas: el diseño de permissions en settings.json, el bloqueo de comandos peligrosos con el hook PreToolUse, la implementación de puertas de confirmación y la prevención de fugas de información confidencial. En cada sección te dejo ejemplos que puedes copiar y pegar.

La seguridad en Claude Code se monta con guardrails: reglas allow/deny/ask en settings.json, hooks que interceptan los comandos antes de ejecutarlos y puertas de confirmación que devuelven el control al humano antes de una acción irreversible.

Flujo de guardrails de seguridad en Claude Code: permisos, hook PreToolUse y confirmación humana
Los tres puntos donde puedes intervenir antes de que Claude Code toque algo irreversible.

Para quién es esto

  • Para quienes ya metieron Claude Code en su trabajo o en proyectos personales y quieren montar un sistema que funcione de forma segura.
  • Para quienes sienten inquietud de dejarle al agente operaciones irreversibles (eliminar archivos, envíos externos, commits).
  • Para quienes, desde un rol de seguridad, buscan ejemplos reales de control de riesgos en el desarrollo con IA.

Entorno de ejecución y requisitos previos

  • Claude Code (basado en las especificaciones vigentes a partir de junio de 2026).
  • macOS / Linux (los scripts asumen el uso de bash).
  • Una estructura donde la configuración global vive bajo ~/.claude/ y la del proyecto bajo .claude/ directamente en el repositorio.

El código de ejemplo está para explicar cómo funciona. En la operación real, personalízalo según tu entorno y tu diseño de permisos. Sobre todo con los scripts de hook, verifica de antemano el comportamiento de exit 2 (puede cambiar según la versión de Claude Code).

Por qué se necesitan guardrails

Claude Code trabaja combinando de forma autónoma la lectura/escritura de archivos, la ejecución de Bash y las llamadas a API externas. Esto dispara la velocidad de desarrollo, pero también abre la puerta a que haga algo que no debía sin que intervenga la revisión humana. Los accidentes más comunes se resumen en tres tipos:

  • Ejecución de operaciones destructivas — eliminar directorios enteros con rm -rf, un UPDATE directo a la base de datos de producción, etc.
  • Envíos no intencionados al exterior — commits de salidas que incluyen API keys o tokens de autenticación, envíos a servicios externos.
  • Toma excesiva de permisos — acceso a archivos que excede el mínimo necesario u operaciones de red no intencionadas.

Todos son accidentes que no se pueden deshacer una vez hechos, la misma clase de riesgo que el desarrollo de software seguro previene por diseño. El objetivo de los guardrails no es limitar a la IA, sino crear un espacio donde el humano pueda decidir antes de una operación irreversible.

1. Diseñar los permisos mínimos con permissions en settings.json

El comportamiento de Claude Code se controla con ~/.claude/settings.json (global) y .claude/settings.json (proyecto). Usando allow y deny dentro de permissions, diseñas explícitamente los recursos que el agente puede tocar.

Estructura básica

{
  "permissions": {
    "allow": [],
    "deny": []
  }
}

Combinas dos enfoques: allow preaprueba las reglas que listas —Claude Code las ejecuta sin pedirte confirmación— y deny bloquea patrones concretos. Lo que no cae en ninguna lista no queda rechazado de entrada: pasa por el flujo normal de permisos, que puede pedirte confirmación. Y si quieres que una operación sensible pida confirmación en lugar de permitirla o bloquearla, tienes las reglas ask.

Ejemplo de configuración global

Primero declaro en la configuración global lo que no quiero dejarle hacer bajo ningún concepto. La del proyecto se combina con esta capa global, no la reemplaza: un deny global no se puede pisar con un allow del proyecto, porque deny se evalúa primero.

{
  "permissions": {
    "deny": [
      "Bash(rm -rf *)",
      "Bash(sudo rm *)",
      "Bash(git push --force *)",
      "Bash(git push -f *)",
      "Bash(chmod 777 *)",
      "Bash(curl * | bash)",
      "Bash(wget * | bash)",
      "Bash(eval *)"
    ]
  }
}

Aquí listo en deny los patrones de comandos que no quiero que se ejecuten. Los objetivos principales son los del tipo rm -rf, el push forzado, la apertura total de permisos y la ejecución directa de scripts como curl | bash.

Ejemplo de configuración del proyecto

La configuración específica del proyecto se escribe en .claude/settings.json, directamente bajo el repositorio. Además del deny global, aquí delimito qué permito en este proyecto.

{
  "permissions": {
    "allow": [
      "Bash(npm run *)",
      "Bash(npx *)",
      "Bash(git status)",
      "Bash(git diff *)",
      "Bash(git add *)",
      "Bash(git commit *)",
      "Bash(git log *)",
      "Bash(git checkout *)",
      "Bash(git branch *)"
    ],
    "deny": [
      "Bash(git push *)",
      "WebFetch(domain:internal.example.com)",
      "WebFetch(domain:staging.example.com)"
    ]
  }
}

El punto clave es que hago un deny explícito a git push a nivel de proyecto. Trazo una frontera: le dejo a su cargo hasta el commit, pero el push lo hace un humano después de verificarlo. Entre el commit y el push está la frontera natural para parar: la misma que trazas cuando dominas los comandos básicos de Git.

Enfoque de diseño

Al diseñar los permissions, conviene preguntarse uno por uno: “¿Me causaría problemas si esta operación se inicia desde Claude Code?”.

Tipo de operaciónCriterio de decisión para allow / deny
De lectura (git log, cat, find)Básicamente allow
De escritura local (git add, npm install)allow por proyecto
De envío externo (git push, curl POST)Por principio deny, ejecutar manualmente
De eliminación o sobrescritura (rm, git reset --hard)Por principio deny
De cambio de permisos de ejecución (chmod, chown)Por principio deny

2. Bloquear comandos peligrosos con el hook PreToolUse

El deny de settings.json se aplica desde el sistema de permisos de Claude Code. Los hooks te permiten añadir una validación externa más fina antes de que una herramienta se ejecute: no son una capa inferior del shell, sino un comando tuyo que recibe el contexto en JSON en un evento del ciclo de vida. PreToolUse se dispara justo antes de que Claude llame a una herramienta. Si el script devuelve exit 2, Claude Code bloquea esa llamada.

Nota de vigencia: exit 2 sigue siendo válido y es la vía más simple para bloquear. Además, hoy Claude Code admite un método más estructurado: salir con exit 0 y devolver por stdout un JSON con hookSpecificOutput.permissionDecision: "deny" (y un motivo), que permite decisiones más ricas (allow, ask, deny). En esta guía uso exit 2 para bloquear por ser directo, y más abajo uso la salida JSON con "ask" cuando lo que quiero es pedir confirmación.

Configuración del hook

Especifica la ruta del script del hook en .claude/settings.json.

{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Bash",
        "hooks": [
          {
            "type": "command",
            "command": "bash .claude/hooks/guard-dangerous-bash.sh"
          }
        ]
      }
    ]
  }
}

Ejemplo de script para bloquear comandos peligrosos

#!/usr/bin/env bash
# PreToolUse hook: Bloquea comandos Bash peligrosos
# Si devuelve exit 2, Claude Code cancela la llamada a la herramienta
# Recibe JSON desde stdin
INPUT=$(cat)
# Extrae tool_input.command
COMMAND=$(echo "$INPUT" | python3 -c "
import sys, json
data = json.load(sys.stdin)
print(data.get('tool_input', {}).get('command', ''))
" 2>/dev/null)
if [ -z "$COMMAND" ]; then
  exit 0
fi
# Patrones prohibidos (expresiones regulares)
DANGEROUS_PATTERNS=(
  'rm[[:space:]]+-[a-zA-Z]*r[a-zA-Z]*f'
  'rm[[:space:]]+-[a-zA-Z]*f[a-zA-Z]*r'
  'sudo[[:space:]]+rm'
  ':(){:|:&};:'
  'mkfs\.'
  'dd[[:space:]]+if='
  '>[[:space:]]*/dev/sd'
  'curl[^|]*\|[[:space:]]*(ba)?sh'
  'wget[^|]*\|[[:space:]]*(ba)?sh'
  'chmod[[:space:]]+777'
  'git[[:space:]]+push[[:space:]]+(-f|--force)'
)
for PATTERN in "${DANGEROUS_PATTERNS[@]}"; do
  if echo "$COMMAND" | grep -qE "$PATTERN"; then
    # Los mensajes a stderr se muestran en los logs de Claude Code
    echo "Guard: Se bloqueó la ejecución porque se detectó un patrón de comando peligroso: $COMMAND" >&2
    echo "Guard: Patrón: $PATTERN" >&2
    exit 2
  fi
done
exit 0

El script recibe el JSON desde stdin y hace coincidir la cadena del comando con los patrones. Si coincide con un patrón prohibido, cancela con exit 2 y escribe el motivo en stderr. Como el contenido de stderr se muestra en la salida de Claude Code, puedes verificar por qué se detuvo.

Comprueba que el guard funciona

Antes de confiar en el hook, pruébalo a mano: pásale por stdin el mismo JSON que le enviaría Claude Code y mira el código de salida.

# Debe bloquear un comando peligroso (espera exit 2)
echo '{"tool_input":{"command":"rm -rf /"}}' | bash .claude/hooks/guard-dangerous-bash.sh
echo "exit: $?"   # 2 = bloqueado

# Debe dejar pasar un comando inofensivo (espera exit 0)
echo '{"tool_input":{"command":"git status"}}' | bash .claude/hooks/guard-dangerous-bash.sh
echo "exit: $?"   # 0 = permitido

Si la primera devuelve 2 y la segunda 0, tu patrón funciona. Es la forma más rápida de iterar sobre la lista de patrones sin tener que provocar el comando real dentro de Claude Code.

Terminal mostrando el hook PreToolUse que bloquea un comando peligroso en Claude Code
El guard en acción: rm -rf frenado antes de tocar nada, con su exit 2.

Información adicional sobre la lista de patrones

En el ejemplo anterior incluí los patrones más representativos. Otros patrones que podrías agregar o quitar según tu entorno:

  • Ejecución directa de SQL que incluya DROP TABLE / TRUNCATE.
  • Combinaciones como leer el archivo .env y enviarlo a una URL externa.
  • Sobrescritura del crontab.

Hay un equilibrio: más patrones prohibidos no significan más seguridad, y si bloqueas demasiado, ya no puedes usar Claude Code de forma efectiva. Es más fácil de mantener si asumes que los patrones se van desarrollando poco a poco con el uso en la práctica.

3. Pedir confirmación antes de operaciones irreversibles

Bloquear no es la única respuesta. Para operaciones que a veces sí quieres ejecutar —pero solo después de verlas— está el patrón de pedir confirmación humana antes de ejecutar. En lugar de exit 2 (bloqueo directo), el hook devuelve permissionDecision: "ask" y Claude Code muestra su diálogo de confirmación nativo; tú apruebas o cancelas ahí.

Ejemplo de puerta de confirmación (hook dinámico)

#!/usr/bin/env bash
# PreToolUse hook: Pide confirmación antes de una operación irreversible
# Devuelve permissionDecision: "ask" para que Claude Code muestre su diálogo nativo
INPUT=$(cat)
COMMAND=$(echo "$INPUT" | python3 -c "
import sys, json
data = json.load(sys.stdin)
print(data.get('tool_input', {}).get('command', ''))
" 2>/dev/null)
if [ -z "$COMMAND" ]; then
  exit 0
fi
# Patrones de operaciones que requieren confirmación
CONFIRM_PATTERNS=(
  'git[[:space:]]+push'
  'git[[:space:]]+reset[[:space:]]+--hard'
  'git[[:space:]]+clean[[:space:]]+-f'
  'git[[:space:]]+stash[[:space:]]+drop'
  'npm[[:space:]]+publish'
  'docker[[:space:]]+push'
)
for PATTERN in "${CONFIRM_PATTERNS[@]}"; do
  if echo "$COMMAND" | grep -qE "$PATTERN"; then
    # Salida JSON estructurada: pide confirmación al usuario
    python3 -c "
import json
print(json.dumps({
    'hookSpecificOutput': {
        'hookEventName': 'PreToolUse',
        'permissionDecision': 'ask',
        'permissionDecisionReason': 'Operación potencialmente irreversible: requiere confirmación humana'
    }
}, ensure_ascii=False))
"
    exit 0
  fi
done
exit 0

El hook detecta el patrón y, en lugar de bloquear, devuelve por stdout un JSON con permissionDecision: "ask" y un motivo. Con eso, Claude Code muestra su propio diálogo de confirmación (con opción de “sí, no volver a preguntar”) y espera tu decisión.

Importante: no intentes pedir el y/N tú mismo escribiendo a /dev/tty desde el hook. Desde la versión 2.1.139, los hooks corren sin terminal controladora —así lo recoge la documentación oficial de hooks—, así que abrir /dev/tty falla o se cuelga. El camino soportado es el de arriba: devolver "ask" y dejar que Claude Code muestre su diálogo.

Diálogo de confirmación de Claude Code pidiendo aprobar un git push antes de ejecutarlo
Aquí decides tú: la puerta ask devuelve el control al humano antes del push.

Alternativa declarativa con permissions.ask

Si los patrones son fijos, ni siquiera necesitas el script: declara las reglas ask directamente en settings.json y Claude Code pedirá confirmación por ti.

{
  "permissions": {
    "ask": [
      "Bash(git push *)",
      "Bash(git reset --hard *)",
      "Bash(npm publish *)",
      "Bash(docker push *)"
    ]
  }
}

Deja el hook dinámico para cuando necesites lógica que una regla estática no cubre (por ejemplo, decidir según el contenido del comando).

Cuándo usar el bloqueo y cuándo la confirmación

Naturaleza de la operaciónRespuesta recomendada
No se quiere dejar ejecutar bajo ninguna circunstancia (rm -rf, etc.)Bloquear con exit 2 en el hook
Normalmente innecesario, pero requerido según la situación (git push, etc.)Pedir confirmación con ask
De uso frecuente, pero requiere verificar el contenido (migración de BD, etc.)Pedir confirmación con ask
Operaciones rutinarias sin problemas (git status, etc.)Sin restricciones

Al separar lo que no se ejecuta nunca de lo que solo pasa tras confirmar, mantienes únicamente las puertas necesarias sin imponer restricciones excesivas.

4. Proteger las API keys y evitar fugas de información confidencial

La fuga de información confidencial llega tarde: cuando te das cuenta, el secreto ya está escrito. Es la versión automatizada de saber qué no subir a GitHub: en lugar de recordarlo tú, lo vigilan los hooks. Lo abordo con una estrategia de dos pasos.

4-1. Evitar que los lea con permissions.deny (reglas Read)

Claude Code no usa un archivo .claudeignore: es una convención de la comunidad que no existe en la configuración oficial. El mecanismo vigente para que el agente no pueda leer archivos sensibles es permissions.deny con reglas de tipo Read(...) en settings.json. Desde el principio evito que lea los .env o cualquier archivo que pueda contener secretos:

{
  "permissions": {
    "deny": [
      "Read(./.env)",
      "Read(./.env.*)",
      "Read(./secrets/**)",
      "Read(./credentials/**)",
      "Read(./*.pem)",
      "Read(./*.key)",
      "Read(./*.p12)",
      "Read(./*.pfx)",
      "Read(~/.aws/credentials)",
      "Read(~/.ssh/**)",
      "Read(./*.token)"
    ]
  }
}

Con esto, esos archivos quedan fuera del acceso normal de Claude Code: no aparecen en búsquedas y las operaciones de lectura sobre esas rutas quedan denegadas. Mantén además esas rutas en tu .gitignore: son dos capas distintas y complementarias. El .gitignore evita que el archivo llegue al repositorio; permissions.deny evita que Claude Code acceda a esos archivos desde su flujo normal de herramientas.

4-2. Escanear el contenido de los commits con el hook PostToolUse

Justo después de que se completa un git add, reviso el contenido preparado (staged). Puedo disparar el hook también tras un git commit, pero este script mira git diff --cached, así que su utilidad real está antes del commit, mientras los cambios siguen en staging. Recuerda que PostToolUse corre después de que la herramienta tuvo éxito.

{
  "hooks": {
    "PostToolUse": [
      {
        "matcher": "Bash",
        "hooks": [
          {
            "type": "command",
            "command": "bash .claude/hooks/scan-staged-secrets.sh"
          }
        ]
      }
    ]
  }
}
#!/usr/bin/env bash
# PostToolUse hook: Escanea si hay información confidencial en los archivos preparados (staged)
# Como PostToolUse ya se ejecutó, exit 2 no tiene sentido
# Aquí solo emitiremos una advertencia
# Verifica si es el contexto donde se ejecutó git add
INPUT=$(cat)
COMMAND=$(echo "$INPUT" | python3 -c "
import sys, json
data = json.load(sys.stdin)
print(data.get('tool_input', {}).get('command', ''))
" 2>/dev/null)
# Si no es un comando relacionado con git add o git commit, omitir
if ! echo "$COMMAND" | grep -qE 'git[[:space:]]+(add|commit)'; then
  exit 0
fi
# Escanea el contenido preparado
STAGED_DIFF=$(git diff --cached 2>/dev/null)
if [ -z "$STAGED_DIFF" ]; then
  exit 0
fi
# Patrones de información confidencial
SECRET_PATTERNS=(
  'AKIA[0-9A-Z]{16}'
  'sk-[a-zA-Z0-9]{32,}'
  'ghp_[a-zA-Z0-9]{36}'
  'xoxb-[0-9a-zA-Z-]+'
  'Bearer[[:space:]]+[a-zA-Z0-9._-]{20,}'
  'password[[:space:]]*=[[:space:]]*[^$\n]{8,}'
  'secret[[:space:]]*=[[:space:]]*[^$\n]{8,}'
  'api_key[[:space:]]*=[[:space:]]*[^$\n]{8,}'
)
FOUND=false
for PATTERN in "${SECRET_PATTERNS[@]}"; do
  if echo "$STAGED_DIFF" | grep -qiE "$PATTERN"; then
    echo "SecretScan: Advertencia — Se detectó un patrón que podría ser información confidencial en un archivo preparado" >&2
    echo "SecretScan: Patrón: $PATTERN" >&2
    echo "SecretScan: Elimínalo del stage con git reset HEAD <nombre_del_archivo> y luego verifícalo" >&2
    FOUND=true
  fi
done
if [ "$FOUND" = true ]; then
  # En PostToolUse, exit 2 no tiene efecto, por lo que solo es una advertencia
  # Agrega la lógica para hacer un unstage automático si es necesario
  exit 1
fi
exit 0

En la etapa de PostToolUse, el git add ya se completó. No puedes detener la operación con exit 2. El papel de este script es emitir una advertencia para que el usuario se dé cuenta. Si quieres detener el commit, inserta una puerta de confirmación en el momento del git commit usando PreToolUse.

4-3. Escribirlo en CLAUDE.md como instrucción para el agente

Además de los hooks y la configuración de permisos, también sirve escribir reglas directamente en CLAUDE.md (el archivo de configuración que lee Claude Code).

Manejo de información confidencial

  • No incluyas el contenido del archivo .env tal cual en el código, los commits ni la salida.
  • Maneja las API keys, tokens de autenticación y contraseñas haciendo referencia a variables de entorno (como process.env.API_KEY) y no escribas los valores directamente.
  • No envíes archivos que puedan contener secretos a URLs externas.
  • No incluyas información confidencial en los mensajes de los commits.

Los LLM siguen las instrucciones hasta cierto punto. Combinar esto con la prevención mecánica de los hooks da una configuración más difícil de saltarse.

5. Consejos de operación: haz que los guardrails se noten

Después de implementar un guardrail, no saber por qué se detuvo algo te cuesta tiempo cada vez. Es más fácil de mantener si lo diseñas para que el humano se dé cuenta en cuanto algo se detiene.

Dejar un log

Si escribes en un archivo de log los eventos que bloqueó el script del hook, podrás revisarlos más tarde.

# ... (Agrega lo siguiente al script mencionado anteriormente)
LOG_DIR="${HOME}/.claude/logs"
mkdir -p "$LOG_DIR"
LOG_FILE="${LOG_DIR}/guardrail-$(date +%Y-%m-%d).log"

log_event() {
  local level="$1"
  local message="$2"
  echo "$(date -Iseconds) [$level] $message" >> "$LOG_FILE"
}

# Cuando se bloquea
log_event "BLOCKED" "Comando: $COMMAND / Patrón: $PATTERN"
echo "Guard: Se ha bloqueado: $COMMAND" >&2
exit 2

Especificar claramente las excepciones de los guardrails

Si escribes en la documentación (CLAUDE.md) qué se está limitando y por qué, reduces la confusión al usarlo en equipo.

Operaciones que se detienen automáticamente (bloqueo mediante hook). Las siguientes operaciones se bloquean automáticamente por el hook. Si se detienen, ejecútalas manualmente.

  • Comandos del tipo rm -rf.
  • git push --force.
  • Ejecución por tubería de curl / wget (como curl ... | bash, etc.).
  • chmod 777.

Operaciones donde aparece la puerta de confirmación. Para las siguientes operaciones se muestra un diálogo de confirmación antes de la ejecución.

  • git push (incluido un push normal).
  • npm publish.
  • git reset --hard.

Revisarlos periódicamente

Los guardrails no son algo de “lo implementas y listo”. A medida que operas, te das cuenta de cosas como “esta restricción en realidad era innecesaria” o “este patrón todavía no estaba incluido”. Si te acostumbras a mirar los logs y verificar qué se está deteniendo alrededor de una vez al mes, es más fácil descubrir tanto las restricciones excesivas como las omisiones.

Los 4 guardrails de seguridad en Claude Code, de un vistazo

Estas son las mejores prácticas de seguridad en Claude Code que organicé en 4 temas.

TemaPunto de partida de la implementación
Diseño de permisos mínimosEmpezar con permissions.deny en settings.json
Bloqueo de comandos peligrososHook PreToolUse + exit 2
Puerta de confirmación para operaciones irreversiblesRegla ask o hook que devuelve permissionDecision: "ask"
Prevención de fuga de información confidencialpermissions.deny (Read) + escaneo en PostToolUse + estipulación explícita en CLAUDE.md

No hace falta introducir todo a la vez. Con solo escribir 3 líneas de deny en settings.json ya marcas una gran diferencia frente a no tener guardrails. A partir de ahí, la forma más realista de avanzar es ir desarrollándolo poco a poco mientras miras los logs de lo que realmente se ha detenido.

Un guardrail bien puesto no le quita autonomía a Claude Code: te devuelve la decisión en el punto exacto que importa. Cuando eres tú quien traza la frontera entre lo que le dejas hacer y lo que confirmas, la confiabilidad del desarrollo con IA cambia por completo.

Categorizado en:

Herramientas y DevOps,