Hooks en Claude Code: bloquea comandos peligrosos con un script de 20 líneas
Si usas Claude Code a diario, seguramente tienes reglas en tu CLAUDE.md del tipo "nunca leas el .env" o "no hagas git push --force". El problema es que una instrucción en texto es una sugerencia: el modelo la sigue casi
Si usas Claude Code a diario, seguramente tienes reglas en tu CLAUDE.md del tipo "nunca leas el .env" o "no hagas git push --force". El problema es que una instrucción en texto es una sugerencia: el modelo la sigue casi siempre, pero "casi" no basta cuando hablamos de secretos o de borrar carpetas.
Para eso existen los hooks: comandos de shell que Claude Code ejecuta automáticamente en momentos concretos. A diferencia de un prompt, un hook siempre se ejecuta.
Los cuatro eventos que más vas a usar
-
PreToolUse: antes de que Claude use una herramienta (Bash, Read, Edit...). Ideal para validar o bloquear. -
PostToolUse: después de que la herramienta termina. Ideal para formatear o pasar el linter. -
Notification: cuando Claude necesita tu atención. -
Stop: cuando Claude termina de responder.
El hook recibe por entrada estándar un JSON con, entre otros campos, tool_name y tool_input. Y la regla clave: si el script sale con código 2, la acción se bloquea y lo que escribas en stderr le llega a Claude como explicación, para que busque otra alternativa.
El script
Guárdalo como .claude/hooks/proteger.py en tu proyecto:
#!/usr/bin/env python3
"""Bloquea comandos destructivos y el acceso a archivos de secretos."""
import json, re, sys
datos = json.load(sys.stdin)
herramienta = datos.get("tool_name", "")
entrada = datos.get("tool_input", {})
PELIGROSOS = [r"\brm\s+-[a-zA-Z]*r[a-zA-Z]*f", r"\bgit\s+push\s+.*--force",
r"\bmkfs\b", r"\bdd\s+if=", r">\s*/dev/sd"]
if herramienta == "Bash":
comando = entrada.get("command", "")
for patron in PELIGROSOS:
if re.search(patron, comando):
print(f"Bloqueado por política: '{comando}'. Propón una alternativa "
"segura o pide al usuario que lo ejecute manualmente.", file=sys.stderr)
sys.exit(2)
if herramienta in ("Read", "Edit", "Write"):
ruta = entrada.get("file_path", "")
if re.search(r"(^|/)\.env(\.|$)|/secrets/|\.pem$", ruta):
print(f"Acceso denegado a archivo sensible: {ruta}", file=sys.stderr)
sys.exit(2)
sys.exit(0)
Registrarlo
En .claude/settings.json (o de forma interactiva con /hooks):
{
"hooks": {
"PreToolUse": [
{
"matcher": "Bash|Read|Edit|Write",
"hooks": [
{ "type": "command",
"command": "python3 \"$CLAUDE_PROJECT_DIR\"/.claude/hooks/proteger.py" }
]
}
]
}
}
matcher filtra por nombre de herramienta y $CLAUDE_PROJECT_DIR apunta a la raíz del proyecto, así que funciona aunque Claude cambie de carpeta.
Pruébalo sin abrir Claude
Como el hook solo lee JSON por stdin, puedes probarlo directamente en la terminal:
echo '{"tool_name":"Bash","tool_input":{"command":"rm -rf build"}}' \
| python3 .claude/hooks/proteger.py; echo "salida=$?"
# Bloqueado por política: 'rm -rf build'. ...
# salida=2
Haz lo mismo con {"tool_name":"Read","tool_input":{"file_path":"/app/.env"}} y con un comando inocente como ls -la (este debe devolver 0).
Tres consejos
-
No es un sandbox. Un patrón puede esquivarse con un comando creativo. Combínalo con reglas
denyen los permisos y, si quieres autonomía total, trabaja dentro de un contenedor. - Mantén los hooks rápidos. Se ejecutan en cada llamada a la herramienta.
- Revisa los hooks de repos ajenos. Viajan con el proyecto y se ejecutan con tus permisos.
Si una regla de tu CLAUDE.md es crítica, conviértela en hook. Es la diferencia entre "por favor, no lo hagas" y "no puedes hacerlo".
¿Te sirvió? Este ejemplo es parte del Guía Claude Code de Cero a Experto (PDF en español): paga lo que quieras, desde 1 USD. Más recursos gratis en GitHub.
Originally published by Dev.to AI. Aggregated on AIWithGhost for educational purposes — full credit and traffic to the original publisher.