Reglas de permisos: allow, ask y deny
Aquí decides, con precisión, qué puede hacer Claude Code sin preguntarte. Son reglas que aplica el cliente, no el modelo: ni tu prompt ni tu CLAUDE.md pueden saltárselas. El objetivo de la lección es que termines con un bloque permissions versionado que elimine las preguntas inútiles y cierre lo peligroso.
Las tres listas
Dentro de permissions:
- allow — Claude usa la herramienta sin pedir aprobación.
- ask — pide confirmación cada vez.
- deny — la bloquea, sin más.
{
"permissions": {
"allow": ["Bash(pnpm *)", "Bash(git commit *)"],
"ask": ["Bash(git push *)"],
"deny": ["Bash(git push --force*)", "Read(./.env)", "Read(./secrets/**)"]
}
}
Gestiónalas también en vivo con /permissions, que además te dice de qué fichero sale cada regla.
El orden importa: deny → ask → allow
Se evalúan en ese orden y gana la primera regla que casa. Conclusión práctica: un deny siempre tiene prioridad, venga del nivel que venga. No puedes "des-denegar" desde un nivel inferior.
En el ejemplo de arriba,
git push origin maincae enask(pregunta), perogit push --force origin maincae antes endeny(bloqueado). El force-push gana al push normal porque deny se evalúa primero.
Sintaxis de reglas
El formato es Herramienta o Herramienta(especificador):
Bash(sin paréntesis) casa todos los usos de Bash. Como regladeny, además saca la herramienta del contexto: Claude ni la ve.Bash(pnpm *)casa solo lo que empieza porpnpm.- Otros:
Read(./.env),WebFetch(domain:example.com),mcp__servidor__*,Agent(Explore).
La trampa del espacio
En reglas Bash, el espacio antes del * marca un límite de palabra:
| Regla | Casa | No casa |
|---|---|---|
Bash(ls *) | ls -la | lsof |
Bash(ls*) | ls -la, lsof | — |
Además, Claude Code entiende los operadores de shell (&&, ||, ;, |…): una regla Bash(safe-cmd *) no autoriza safe-cmd && rm -rf .. Cada subcomando debe casar por su cuenta. Es una protección, no un bug.
Read y Edit usan patrones tipo gitignore
| Patrón | Significa |
|---|---|
Read(.env) | cualquier .env en este directorio o por debajo |
Edit(/src/**/*.ts) | TS bajo la raíz del proyecto (el / no es la raíz del disco) |
Read(~/.ssh/**) | bajo tu home |
Read(//tmp/x) | ruta absoluta del disco (doble //) |
Ojo a esa distinción:
/srces relativo al proyecto; para una ruta absoluta de verdad necesitas//.
Una trampa de fondo: no restrinjas argumentos con patrones
Intentar acotar argumentos con patrones Bash es frágil. Bash(curl https://github.com/ *) parece restringir curl a GitHub, pero no cubre curl -L, otra URL, variables, espacios extra… Para filtrar red de verdad:
- Deniega
curl/wgety usa el toolWebFetchconWebFetch(domain:...)para los dominios permitidos. - O un hook
PreToolUseque valide el comando (lo verás en el curso de hooks deterministas).
Una base sensata para empezar
{
"permissions": {
"allow": ["Bash(pnpm *)", "Bash(git status)", "Bash(git diff *)", "Bash(git commit *)"],
"deny": ["Bash(git push --force*)", "Bash(rm -rf *)", "Read(./.env)", "Read(./secrets/**)"]
}
}
Permite lo cotidiano (tests, diffs, commits), bloquea lo destructivo y la lectura de secretos. Commitéalo y todo el equipo lo hereda.
Qué llevas hasta aquí
allow/ask/deny, evaluados deny → ask → allow (deny manda).- Sintaxis
Herramienta(especificador), con la trampa del espacio en Bash y patrones gitignore en Read/Edit. - No se acotan argumentos con patrones: para red,
deny+WebFetch(domain:...)o un hook.
En la siguiente lección, la otra mitad del control: los permission modes, que ponen la línea base de cuánto te interrumpe Claude.
Fuente oficial: Configure permissions
¿Quieres llevar la cuenta de las lecciones que has terminado?
Entrar para guardar progreso