Skip to content

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

Actualizado el

Un settings.json probado para Claude Code 2.1.319: blockWritesOutsideWorkingDirectories, denyInsteadOfPrompting, reglas deny de Read y dontAsk para ejecuciones desatendidas, más la auditoría de settings.local.json y el chequeo de la extensión con puerta trasera.

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íneaQué haceAñadido en
blockWritesOutsideWorkingDirectoriesLas herramientas Write, Edit y NotebookEdit rechazan cualquier ruta fuera de tus directorios de trabajo, en todos los modos de permiso, incluido bypassPermissions2.1.314 (30 de octubre de 2026)
denyInsteadOfPromptingUna 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 teammates2.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 archivodesde hace tiempo
Bash(curl *) / Bash(wget *)Las descargas de red desde el shell se rechazan en vez de preguntarsedesde 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.

FechaQué pasóTu comprobación única
30 octPhorion 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 novIssue 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 novApiiro 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.

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 > ~/file desde la herramienta Bash no es una llamada a Write. 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 tmp de una sesión en segundo plano y los archivos .md de 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.json tú mismo.
  • Las herramientas de worktree desaparecen. Con la clave activada, Claude Code elimina EnterWorktree y ExitWorktree o 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(...) y Edit(...). Las reglas Write(docs/**), Grep(docs/**) y Glob(...) con una ruta se aceptan pero nunca coinciden.
  • Bash(git diff *), con un espacio antes del *, coincide con cualquier comando que empiece por git diff. Bash(git diff*) coincidiría también con git 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ónUsaComportamiento
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 definidoNada másDesde 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 dontAskToda 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:

  1. 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.
  2. Borra la regla, o sustitúyela por una que no lleve el secreto: aprueba psql * y pasa la contraseña mediante PGPASSWORD desde el entorno de tu shell, no dentro del comando.
  3. Si git rastrea el archivo, ejecuta git rm --cached .claude/settings.local.json y añade .claude/settings.local.json al .gitignore del repositorio, para que la protección viaje con el repositorio en vez de depender de las exclusiones globales de cada máquina.
  4. 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 claude

En 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

IntentoblockWrites…Regla deny de ReadDeny Bash(curl *)dontAsk / -p por defectoSandbox
La herramienta Write crea ~/x.txtRechazado (probado)——Aviso → denegadoPermitido 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 BashNo cubierto——Aviso → denegadoBloqueado (docs: Operation not permitted en macOS)
curl https://… en Bash——Rechazado (probado)DenegadoBloqueado 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 aprobarloBloqueado, 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