Endurecer los permisos de Claude Code: bloquear escrituras fuera del repo, denegar en vez de preguntar y auditar settings.local.json
Actualizado el

La versión corta: pon esto en .claude/settings.json en la raíz del repositorio, haz commit y reinicia Claude Code. Necesita Claude Code 2.1.319 o posterior (claude --version y luego claude update).
{
"permissions": {
"blockWritesOutsideWorkingDirectories": true,
"denyInsteadOfPrompting": true,
"deny": [
"Read(./.env)",
"Read(./.env.*)",
"Read(./secrets/**)",
"Bash(curl *)",
"Bash(wget *)"
]
}
}Qué te aporta cada línea:
| Línea | Qué hace | Añadido en |
|---|---|---|
blockWritesOutsideWorkingDirectories | Las herramientas Write, Edit y NotebookEdit rechazan cualquier ruta fuera de tus directorios de trabajo, en todos los modos de permiso, incluido bypassPermissions | 2.1.314 (30 de octubre de 2026) |
denyInsteadOfPrompting | Una llamada de herramienta que se quedaría esperando un aviso de permiso se deniega y el turno continúa, en sesiones de terminal, subagentes y teammates | 2.1.319 (6 de noviembre de 2026) |
Reglas deny Read(...) | .env y secrets/ siguen siendo ilegibles para las herramientas de archivos y para comandos de shell como cat, head y sed que nombren el archivo | desde hace tiempo |
Bash(curl *) / Bash(wget *) | Las descargas de red desde el shell se rechazan en vez de preguntarse | desde hace tiempo |
Probé el bloqueo de escrituras, las reglas deny y el comportamiento en ejecuciones desatendidas en macOS con Claude Code 2.1.319 el 8 de noviembre de 2026, como ejecuciones de claude -p --output-format json con el modo de permiso fijado en cada prueba. Los mensajes exactos de rechazo se citan más abajo. El changelog describe denyInsteadOfPrompting para sesiones de terminal, subagentes y teammates, algo que una ejecución con -p ya deniega, así que su descripción procede del changelog (opens in a new tab), no de una prueba. El resto de esta guía explica por qué estos ajustes importan justo ahora, qué cubre y qué no cubre cada uno, y tres comprobaciones que deberías ejecutar una vez.
Por qué ahora: tres incidentes en nueve días
Claude Code publicó siete versiones entre el 26 de octubre y el 6 de noviembre de 2026, y en esa misma ventana aparecieron tres historias de seguridad. Cada una se traduce en una comprobación concreta.
| Fecha | Qué pasó | Tu comprobación única |
|---|---|---|
| 30 oct | Phorion encontró una compilación con puerta trasera de la extensión oficial Claude Code for VS Code en el Cursor Marketplace, versiones 2.1.312 y 2.1.313. Añade una clave SSH del atacante a ~/.ssh/authorized_keys e instala persistencia (aviso (opens in a new tab), HN, 1.011 puntos (opens in a new tab)) | Lista la versión de tu extensión y haz grep en authorized_keys; ver el paso 5 más abajo |
| 5 nov | Issue de GitHub #107757 (opens in a new tab): Claude Code ponía la dirección de correo de la cuenta en la cabecera User-Agent de las peticiones web. Casi 1.000 reacciones, todavía abierto el 8 de noviembre (HN, 1.597 puntos (opens in a new tab)) | Actualiza a la 2.1.317 o posterior, que pregunta antes de que una herramienta envíe tu correo; ver el paso 6 más abajo |
| 7 nov | Apiiro encontró 1.122 archivos públicos en GitHub en los que una aprobación «Yes, and don't ask again» había guardado un comando de shell con una contraseña o un token en .claude/settings.local.json (informe (opens in a new tab), HN, más de 500 puntos (opens in a new tab)) | Ejecuta el script de auditoría del paso 4 más abajo |
Ninguno de estos problemas se arregla con un modo de permiso. Hacen falta un archivo de ajustes, una auditoría y una comprobación de versión.
- Endurecer los permisos de Claude Code: bloquear escrituras fuera del repo, denegar en vez de preguntar y auditar settings.local.json
- Cómo convertir Python a EXE: Runcell, PyInstaller, Nuitka y cx_Freeze
- Actualizar a pandas 3.1: Pandas4Warning de inplace y read_csv más rápido
- Cómo actualizar a OpenCode 2.0: migra tu configuración, plugins y sesiones de v1
- Cómo instalar y actualizar a Python 3.15 con uv: importaciones diferidas (lazy imports) y compatibilidad de paquetes
- Tutorial de Claude Code Mods: cómo crear, probar e instalar tu primer mod, con 15 ejemplos de la comunidad
- Cómo usar GPT Image 2.5: Flare vs. Sunburst y precios de la API
- Cómo usar DeepSeek Harness: instalación, configuración y tu primer agente
- Runcell Science: alternativa open source a Claude Science para investigación
- Cómo evitar que Mac se duerma: mantener Codex, Claude Code y agentes de IA corriendo
- OpenClaw vs ZeroClaw vs Pi Agent vs Nanobot: ¿Qué stack de agentes de IA deberías elegir en 2026?
- Cómo Claude Code analiza notebooks de Jupyter en Data Science: lo que sí hace y lo que no
- Claude Code routines: qué son y por qué importan los cron jobs de agentes de IA
- Claude Code Desktop Bypass Permissions: cómo activarlo
- Cómo Construir Dos Agentes en Python con el Protocolo A2A de Google - Tutorial Paso a Paso
- Las 10 principales bibliotecas de visualización de datos en Python que crecen en 2025
Paso 1: bloquear las escrituras de archivos fuera del repositorio
permissions.blockWritesOutsideWorkingDirectories hace que las tres herramientas de edición de archivos rechacen cualquier ruta que no esté dentro de un directorio de trabajo. Los directorios de trabajo son la carpeta en la que arrancaste Claude Code más todo lo que añadiste con /add-dir, --add-dir o permissions.additionalDirectories en tus ajustes de usuario o gestionados. Una entrada additionalDirectories en el .claude/settings.json del propio repositorio no levanta el bloqueo. El bloqueo se mantiene en todos los modos de permiso, incluido bypassPermissions, y un true en cualquier archivo de ajustes gana a un false en otro, así que un repositorio puede activarlo para todo el que lo clone.
Puse la clave en un proyecto de prueba y le pedí a Claude Code, en modo acceptEdits, que creara un archivo en mi directorio home con la herramienta Write. La llamada fue rechazada con este mensaje:
/Users/me/kanaries-block-test.txt is outside /path/to/proj; the
permissions.blockWritesOutsideWorkingDirectories setting blocks writes
outside the working directories. Ask the user to add the directory with
/add-dir, or to remove that setting.El archivo no se creó, y el resultado JSON listó la llamada en permission_denials con tool_name: "Write".
Cuatro límites que debes conocer antes de confiar en él:
- No limita comandos de shell, herramientas MCP ni programas de hooks.
echo x > ~/filedesde la herramienta Bash no es una llamada aWrite. Para acotar las escrituras del shell, activa el sandbox (ver la sección opcional al final). - Algunos archivos propios de Claude Code siguen siendo escribibles: los archivos de plan, el scratchpad de la sesión, el directorio
tmpde una sesión en segundo plano y los archivos.mdde auto-memoria del proyecto. Es así por diseño. - Claude ya no puede editar ningún archivo de ajustes de Claude Code, ni siquiera dentro del repositorio, porque un archivo de ajustes podría levantar el bloqueo. Edita
.claude/settings.jsontú mismo. - Las herramientas de worktree desaparecen. Con la clave activada, Claude Code elimina
EnterWorktreeyExitWorktreeo rechaza las llamadas a ellas. Si los subagentes necesitan worktrees, añade.claude/worktrees, en la raíz del repositorio, como directorio de trabajo.
Su hermano, permissions.blockReadsOutsideWorkingDirectories (2.1.257 o posterior), hace lo mismo para Read, Grep, Glob y las llamadas LSP. Actívalo cuando el repositorio no deba ver el resto de tu disco.
Paso 2: mantener los secretos ilegibles con reglas deny
Una regla deny Read(path) bloquea las herramientas de archivos integradas y, según la documentación, los comandos de archivos que Claude Code reconoce en Bash (cat, head, tail, sed, tee) más las redirecciones de shell que nombren el archivo. También bloquea Edit y Write sobre la misma ruta, así que Claude tampoco puede sobrescribir tu .env.
Probado: con "deny": ["Read(./.env)"], pedirle a Claude que leyera .env con la herramienta Read devolvió:
File is in a directory that is denied by your permission settings.La regla llega también al shell. En una ejecución en acceptEdits pedí cat README.md y después cat .env: el primero imprimió el archivo, el segundo fue rechazado con Permission to use Bash with command cat .env has been denied. Una ejecución aparte de claude -p en modo por defecto rechazó curl -sI https://example.com por la regla Bash(curl *). Cada denegación quedó registrada en permission_denials, y cada ejecución terminó en vez de quedarse colgada.
Tres trampas de sintaxis de reglas sacadas de la referencia de permisos (opens in a new tab):
- Usa
Read(...)yEdit(...). Las reglasWrite(docs/**),Grep(docs/**)yGlob(...)con una ruta se aceptan pero nunca coinciden. Bash(git diff *), con un espacio antes del*, coincide con cualquier comando que empiece porgit diff.Bash(git diff*)coincidiría también congit diff-index.- Una regla deny no atrapa un comando que lee un archivo sin nombrarlo, como
grep -r pattern .ejecutado desde la carpeta que contiene el secreto. Para eso está el sandbox.
Un archivo .claudeignore no tiene ningún efecto. Pasa sus entradas a reglas deny de Read.
Paso 3: no quedarse nunca colgado en un aviso de permiso
Ahora hay cuatro formas de hacer que una ejecución desatendida de Claude Code deniegue en vez de esperar. Elige según dónde ocurra la ejecución.
| Situación | Usa | Comportamiento |
|---|---|---|
| Sesión interactiva de terminal que dejas sola, incluidos sus subagentes y teammates | "denyInsteadOfPrompting": true en los ajustes (2.1.319) | Una llamada que se quedaría esperando un aviso se deniega y el turno continúa (redacción del changelog; no pude probarlo en modo -p porque -p ya deniega) |
Ejecución de claude -p o del Agent SDK sin modo de permiso definido | Nada más | Desde la 2.1.302, -p arranca en modo Manual y deniega cualquier llamada que necesite aprobación. Probado arriba |
| Trabajo de CI cerrado a cal y canto | --permission-mode dontAsk | Toda llamada que preguntaría se deniega (un mod que hayas instalado todavía puede aprobar algunas llamadas). Los comandos de solo lectura, las lecturas de archivos dentro de los directorios de trabajo y tus reglas allow siguen ejecutándose |
Una ejecución con un host de permisos, como una app del SDK con canUseTool o --permission-prompt-tool | --permission-prompts none (2.1.259 o posterior) | La ejecución no espera al host; a Claude se le dice que nadie puede aprobar y que no lo reintente |
Probado con --permission-mode dontAsk --permission-prompts none: python3 -c 'print(2+2)' fue denegado con un mensaje que empieza por Permission to use Bash has been denied because Claude Code is running in don't ask mode, mientras que git log --oneline -1 se ejecutó porque está en la lista integrada de comandos de solo lectura. El JSON de resultado llevaba una entrada en permission_denials.
Cosas que ningún modo ni regla allow puede aprobar, ni siquiera bypassPermissions: las reglas ask explícitas, la herramienta AskUserQuestion, las herramientas MCP marcadas con requiresUserInteraction y los comandos rm sobre rutas críticas reconocidas, como rm -rf ~. dontAsk las deniega todas de plano, y además deniega las lecturas desde rutas de red incluso cuando coincide una regla allow.
Si un repositorio nunca debería ejecutarse en modo bypass, añade "disableBypassPermissionsMode": "disable" bajo permissions. Claude Code rechaza entonces --dangerously-skip-permissions e ignora cualquier definición de subagente que pida bypass.
Paso 4: auditar settings.local.json en busca de reglas allow filtradas
Este es el mecanismo detrás del informe de Apiiro. Cuando Claude pide ejecutar un comando de shell y eliges Yes, and don't ask again, Claude Code guarda una regla allow con ese prefijo de comando en .claude/settings.local.json, en la raíz del repositorio. Si el comando era psql postgres://app:Sup3rSecret@db/prod -c ..., la contraseña está ahora en la regla.
Claude Code añade **/.claude/settings.local.json a tu archivo global de exclusiones de git la primera vez que escribe el archivo en un repositorio que aún no lo ignora. En mi Mac ese archivo es ~/.config/git/ignore, y el patrón está en la línea 1. Por eso git add .claude/settings.local.json falla aquí con "ignored by one of your .gitignore files". Pero la protección es por máquina, no por repositorio. Un archivo que creaste a mano antes de que Claude Code escribiera nunca en él, un repositorio copiado a otra máquina como directorio en vez de clonado, o un sandbox sin tus exclusiones globales: todos hacen commit del archivo como de cualquier otro. El autor de Apiiro dijo en HN que en 539 de 1.105 repositorios rastreados un commit con coautoría de Claude había modificado el archivo.
Ejecuta esto desde la raíz del repositorio. Encuentra todos los .claude/settings*.json, informa de si git los rastrea y marca las reglas allow que llevan credenciales o aprueban familias enteras de herramientas.
cat > /tmp/claude-settings-audit.sh <<'EOF'
#!/usr/bin/env bash
set -u
root="${1:-.}"
found=0
while IFS= read -r -d '' f; do
found=1
echo "== $f"
if git -C "$(dirname "$f")" ls-files --error-unmatch "$(basename "$f")" >/dev/null 2>&1; then
echo " TRACKED BY GIT <- settings.local.json should never be"
fi
grep -nE '"(Bash|PowerShell)\([^"]*(password|passwd|token|secret|api[_-]?key|Authorization|Bearer |PGPASSWORD|AWS_SECRET|--password|://[^"/]*:[^"@]*@|[A-Z_]{3,}=[^[:space:]"]{8,})' "$f" \
| sed 's/^/ SECRET-LIKE: /'
grep -nE '"(Bash|PowerShell)(\(\*\))?"|"Bash\((sudo|rm|curl|wget|ssh|scp|git push|npm publish|docker)[^"]*\*\)"' "$f" \
| sed 's/^/ BROAD: /'
done < <(find "$root" -path '*/node_modules' -prune -o -type f \
\( -name 'settings.local.json' -o -name 'settings.json' \) -path '*/.claude/*' -print0 2>/dev/null)
[ "$found" = 0 ] && echo "No .claude/settings*.json under $root"
EOF
bash /tmp/claude-settings-audit.sh .Sobre un archivo sintético con seis reglas marcó las tres que incrustaban una contraseña de base de datos, un token bearer y una asignación DATABASE_URL=..., además de Bash(*), Bash(rm -rf *) y, por segunda vez bajo BROAD, la regla curl. Cuando el archivo estaba commiteado en un repositorio de prueba, también imprimió la línea TRACKED BY GIT. Es un grep, así que cuenta con tener que leer los resultados tú mismo.
Qué hacer con un hallazgo:
- Rota la credencial primero. Si el archivo llegó a subirse alguna vez, trata el secreto como público. Reescribir el historial no basta una vez que un fork o un scraper lo tiene.
- Borra la regla, o sustitúyela por una que no lleve el secreto: aprueba
psql *y pasa la contraseña mediantePGPASSWORDdesde el entorno de tu shell, no dentro del comando. - Si git rastrea el archivo, ejecuta
git rm --cached .claude/settings.local.jsony añade.claude/settings.local.jsonal.gitignoredel repositorio, para que la protección viaje con el repositorio en vez de depender de las exclusiones globales de cada máquina. - Revisa también
.claude/settings.json. Está pensado para hacerle commit, así que una regla allow con un secreto ahí es una filtración por diseño, no por accidente.
Por la misma razón, prefiere reglas deny y ask en el archivo que se sube al repositorio y mantén las reglas allow personales en local. Las reglas deny y ask se aplican de inmediato; las reglas allow de un repositorio se aplican solo después de que cada compañero de equipo confíe en la carpeta.
Paso 5: revisar la extensión del IDE y las claves SSH
Las compilaciones con puerta trasera fueron las versiones 2.1.312 y 2.1.313 de la extensión Claude Code for VS Code distribuida a través del Cursor Marketplace, reportadas por Phorion el 30 de octubre. Cursor las retiró. Según Phorion, la carga útil se ejecuta en cuanto se carga el módulo de la extensión, añade una clave Ed25519 con el comentario svc-telemetry a ~/.ssh/authorized_keys, intenta activar Remote Login y envía balizas con información del host. En macOS, el paso de activar SSH falla para un usuario sin privilegios de administrador por un bug del implante, pero la clave y la persistencia se dejan igualmente.
Lista las versiones instaladas:
cursor --list-extensions --show-versions | grep -i claude
code --list-extensions --show-versions | grep -i claude
ls ~/.cursor/extensions ~/.vscode/extensions 2>/dev/null | grep -i claudeEn mi máquina esto devolvió anthropic.claude-code@2.1.302, que está fuera del rango afectado. Después comprueba el indicador que publicó Phorion:
grep -n "svc-telemetry" ~/.ssh/authorized_keys 2>/dev/null || echo "no match"Phorion señala que la cadena del comentario la controla el atacante, así que un grep limpio no es una prueba. Si ejecutaste la 2.1.312 o la 2.1.313 desde el Cursor Marketplace, elimina de authorized_keys todas las claves que no reconozcas, revisa los elementos de inicio de sesión y los launch agents, y trata la máquina como comprometida hasta que un equipo de respuesta a incidentes la haya revisado. Instala la extensión desde el VS Code Marketplace o deja que la CLI claude la instale mediante /ide; desde la 2.1.318, Claude Code ya no instala ni actualiza la extensión por su cuenta desde un terminal integrado en el IDE.
Paso 6: la corrección del correo en el User-Agent
El issue #107757 informa de que Claude Code enviaba la dirección de correo de la cuenta en la cabecera User-Agent de las peticiones web realizadas durante búsquedas y descargas, de modo que sitios y rastreadores podían recolectarla. La entrada del changelog de la 2.1.317, con fecha del 3 de noviembre, dice: "Added a check that asks before a web or shell tool call sends your account email address to the network" (se añadió una comprobación que pregunta antes de que una llamada de herramienta web o de shell envíe a la red la dirección de correo de tu cuenta). Quien lo reportó lo hizo desde la 2.1.318, y el issue seguía abierto con casi 1.000 reacciones el 8 de noviembre, así que síguelo para conocer la resolución final.
Qué puedes hacer hoy: actualiza (claude update) y, si ejecutas sin supervisión, ten en cuenta que con denyInsteadOfPrompting o dontAsk esa nueva comprobación de «pregunta antes» se convierte en una denegación, que es justo el comportamiento que quieres.
Qué detiene cada guardia: probado frente a documentado
| Intento | blockWrites… | Regla deny de Read | Deny Bash(curl *) | dontAsk / -p por defecto | Sandbox |
|---|---|---|---|---|---|
La herramienta Write crea ~/x.txt | Rechazado (probado) | — | — | Aviso → denegado | Permitido por el sandbox; las herramientas de archivos quedan fuera de él |
La herramienta Read lee .env | — | Rechazado (probado) | — | Permitido, las lecturas no necesitan aprobación | — |
cat .env en Bash | — | Rechazado (probado) | — | Denegado | — |
echo x > ~/x.txt en Bash | No cubierto | — | — | Aviso → denegado | Bloqueado (docs: Operation not permitted en macOS) |
curl https://… en Bash | — | — | Rechazado (probado) | Denegado | Bloqueado salvo que el host esté permitido |
python3 -c … en Bash | — | — | — | Denegado (probado) | Se ejecuta dentro del sandbox |
git log, ls y otros comandos de solo lectura | — | — | — | Se ejecuta (probado) | Se ejecuta |
rm -rf ~ | — | — | — | Denegado, ninguna regla puede aprobarlo | Bloqueado, el home está fuera del conjunto escribible (docs) |
«Probado» significa que lo ejecuté en la 2.1.319 el 8 de noviembre de 2026. Todo lo demás procede de la documentación oficial enlazada en esta guía.
Opcional: meter también el shell en un sandbox
Los ajustes anteriores acotan las herramientas de archivos propias de Claude. Los comandos de shell los acota el sandbox de Bash (opens in a new tab), que está desactivado por defecto. Usa Seatbelt de macOS y, en Linux o WSL2, necesita bubblewrap y socat. Actívalo para un proyecto con /sandbox dentro de una sesión, o para todos los proyectos añadiendo esto a ~/.claude/settings.json:
{
"sandbox": {
"enabled": true
}
}Para confirmar que funciona, pídele a Claude que ejecute touch ~/sandbox-probe. Dentro del sandbox falla con Operation not permitted en macOS o Read-only file system en Linux. Si Claude se ofrece a reintentar el comando fuera del sandbox, di que no.
Una configuración combinada cubre el hueco de la matriz anterior: herramientas de archivos acotadas por blockWritesOutsideWorkingDirectories, shell acotado por el sandbox, secretos acotados por reglas deny de Read y ejecuciones desatendidas acotadas por denyInsteadOfPrompting. El modo bypass entonces solo acelera los avisos que quedan. Si aun así necesitas bypass para una tarea contenida, la guía de bypass permissions cubre el interruptor y sus mensajes de error.
Preguntas frecuentes
Guías relacionadas
- Claude Code Desktop Bypass Permissions: cómo activarlo
- Tutorial de Claude Code Mods: cómo crear, probar e instalar tu primer mod
- Claude Code routines: qué son y por qué importan los cron jobs de agentes de IA
- Parallel Code Agents: worktrees, sandboxes y patrones de herramientas de escritorio
- Cómo actualizar a OpenCode 2.0: migra tu configuración, plugins y sesiones de v1