Cómo auditar los permisos de Claude Code en 2 minutos
· 9 min de lectura
Para auditar los permisos de Claude Code, corre un script de Python que lee la lista permissions.allow de settings.local.json (el del proyecto y el de tu usuario) y devuelve tres números: cuántas reglas Bash tienes, cuántas llevan comodín y en qué posiciones hay algo con forma de credencial. Nunca imprime el valor de la clave, solo su índice.
Claude Code guarda un archivo que casi nadie abre y que crece solo: el de permisos. Cada vez que apruebas un comando con la opción de no volver a preguntar, ese comando queda escrito ahí como regla permanente. Esta guía te da el comando que lo audita, cómo leer lo que devuelve y qué hacer con cada caso.
Tarda dos minutos y no necesitas instalar nada más que Python 3.
Dónde vive el archivo y por qué crece solo
En la carpeta .claude conviven dos archivos con nombre parecido y propósito distinto. El archivo settings.json es el compartido: lo que el equipo acuerda, y sí se sube al repositorio. El archivo settings.local.json es el tuyo: lo que tú aprobaste en tu máquina.
El segundo es el que se llena. La documentación oficial de Claude Code lo describe así: "When Claude asks permission to run a Bash command and you choose 'Yes, and don't ask again', Claude Code saves that approval as an allow rule in .claude/settings.local.json".
Dos detalles que explican el volumen:
Guarda el comando entero. No un resumen ni el nombre del programa: la línea tal cual la ejecutaste, envuelta en Bash(...). Si llevaba una credencial pegada delante, la credencial quedó escrita.
Un "sí" puede valer por cinco reglas. La documentación de permisos dice que al aprobar un comando compuesto, Claude Code guarda una regla separada por cada subcomando que necesitaba aprobación, hasta cinco.
Hay uno por proyecto, dentro de la carpeta del proyecto, y uno de usuario en tu carpeta personal. No es un archivo: son varios, y la auditoría hay que repetirla en cada proyecto.
El comando que cuenta tus reglas
Copia esta línea completa y pégala en la terminal, parado en la carpeta del proyecto que quieras revisar. Mira las dos rutas a la vez, la del proyecto y la de tu usuario, y si alguna no existe la salta sin quejarse.
python3 -c "import json,re,os;K=re.compile(r'AIza[\w-]{15,}|sk-[\w-]{20,}|\w*(KEY|TOKEN|SECRET|PASS)\w*=\S{8,}');B=[r for p in ('$HOME/.claude/settings.local.json','.claude/settings.local.json') if os.path.exists(p) for r in json.load(open(p)).get('permissions',{}).get('allow',[]) if r[:4]=='Bash'];print('BASH',len(B),'| COMODIN',sum('*' in r for r in B),'| CLAVE',[i for i,r in enumerate(B) if K.search(r)])"
Lo importante de este comando es lo que no hace: nunca imprime el valor de una credencial. Solo devuelve conteos y la posición dentro de la lista. Puedes correrlo con la pantalla compartida.
Cómo leer los tres números
La salida tiene esta forma:
BASH 284 | COMODIN 40 | CLAVE [29, 30, 37]
BASH es cuántas reglas de tipo Bash tienes guardadas en total.
COMODIN es cuántas de esas llevan un asterisco y por tanto cubren más de un comando.
CLAVE son las posiciones en las que hay algo con forma de credencial. Cuenta desde cero, primero las de tu usuario y después las del proyecto, en el mismo orden en que el comando las leyó.
Si CLAVE viene como lista vacía, no tienes nada urgente que hacer. Si trae índices, ve a la sección de rotación antes que a la de borrado: el orden importa.
Para ver las reglas una por una con las credenciales tapadas, sin abrir el archivo en el editor:
python3 -c "import json,re,os;K=re.compile(r'AIza[\w-]{6,}|sk-[\w-]{6,}|(?<==)[\w./+-]{8,}');B=[r for p in ('$HOME/.claude/settings.local.json','.claude/settings.local.json') if os.path.exists(p) for r in json.load(open(p)).get('permissions',{}).get('allow',[]) if r[:4]=='Bash'];[print(str(i).rjust(3), K.sub('<OCULTO>',r)[:96]) for i,r in enumerate(B)]"
Cada línea sale como índice más regla, con lo que parezca credencial sustituido por un marcador y recortado a 96 caracteres. Los índices coinciden con los que te devolvió el comando corto.
Las tres clases de regla peligrosa
No todas las reglas gordas son iguales. Estas son las tres que conviene mirar, de más grave a menos:
Credencial dentro de la regla. La clave quedó escrita en texto plano. Es la única de las tres que obliga a rotar algo. Ejemplo inventado: Bash(MI_CLAVE=AIzaSyEJEMPLO_NO_REAL_123 python3 subir.py).
Comodín antes del subcomando. Ejemplo: Bash(git * main). La documentación de permisos lo explica sin ambigüedad: ahí el asterisco "stands in for the subcommand, so Claude Code matches every git subcommand and every option before it. That includes -c, which makes git run a program you name". Desde la versión 2.1.246, publicada el 25 de agosto de 2026, Claude Code te avisa de estas al arrancar.
Regla demasiado amplia. Ejemplo: Bash(python3 *). Cubre familias enteras de comandos. No dispara el aviso de arranque, y la documentación de errores lo dice explícitamente: no advierte sobre "rules in which no word other than an option follows the *, such as Bash(git *)". Es una decisión tuya sobre cuánto quieres delegar, no un fallo.
La clase 2 es la que se arregla reescribiendo la regla: pon el asterisco después del subcomando, una regla por subcomando que quieras permitir. La clase 1 es la que se arregla rotando.
Borrar una regla sin romper tu flujo
Antes de tocar nada, cierra Claude Code. Si el programa reescribe el archivo encima mientras editas, pierdes el cambio.
Este comando hace un respaldo, quita todas las reglas que contengan el texto que le pases y deja el JSON válido. Cambia el texto del final por el que identifique tus reglas: el nombre de tu variable de entorno suele ser el más limpio.
python3 -c "import json,os,shutil,sys;p=os.path.expanduser('~/.claude/settings.local.json');t=sys.argv[1];shutil.copy(p,p+'.bak');d=json.load(open(p));a=d.get('permissions',{}).get('allow',[]);n=[x for x in a if t not in x];d['permissions']['allow']=n;json.dump(d,open(p,'w'),indent=2);print('BORRADAS',len(a)-len(n),'de',len(a),'| respaldo:',p+'.bak')" MI_CLAVE=
Tres avisos que salen de haberlo hecho mal:
Empieza por el texto más específico que puedas. Con algo genérico te llevas por delante reglas que usabas a diario.
El número que imprime dice cuántas se fueron. Si no cuadra, restaura el respaldo y afina el texto antes de repetir.
Cuando vuelvas a abrir Claude Code te va a preguntar otra vez por lo que borraste. Eso es exactamente lo correcto.
Rotar una credencial que ya quedó escrita
Este es el paso que la gente se salta, y es el único que de verdad sirve. Borrar la regla no desactiva la credencial: si estuvo escrita, sigue siendo válida hasta que la cambies en su origen.
Crea primero la clave nueva. Si borras la vieja antes, cortas lo que tengas corriendo.
Entra a la consola donde nació. Si empieza por AIza es de Google, y se gestiona en la sección de claves de API de Google AI Studio.
Borra allí la clave expuesta. Solo este paso la deja inservible.
Guarda la nueva fuera del comando, en un archivo de entorno (siguiente sección).
Restringe la clave nueva a la API que de verdad usas, si el proveedor lo permite.
Recién entonces limpia las reglas viejas, y borra también el archivo .bak: ese respaldo guarda la clave vieja tal cual.
Los nombres de los botones de cada consola cambian cada pocos meses. Lo que no cambia es el orden: crear, borrar la vieja, sacarla del comando, limpiar el archivo.
Cómo no volver a llenarlo
Como la regla guarda el comando entero, la solución no es acordarse de limpiar cada mes: es que la clave nunca esté escrita en el comando.
Y a partir de ahí el comando carga la clave justo antes en vez de llevarla dentro:
set -a; . ~/.config/miapp/.env; set +a
python3 subir.py
La regla que Claude Code guardará ahora es Bash(python3 subir.py), limpia. El archivo de entorno vive fuera del repositorio; si tiene que vivir dentro, va al .gitignore antes de que escribas la primera clave. Y el chmod 600 deja el archivo legible solo para tu usuario.
Si ya usabas archivos de entorno, revisa igual: basta un comando suelto con la variable pegada delante para volver a escribir la clave en los permisos.
Qué no arregla esto
La parte honesta, sin suavizar.
No es un fallo de Claude Code. Es comportamiento documentado: la regla guarda el comando entero porque así sabe qué autorizaste exactamente. Guardar menos sería peor.
La parte de git ya está cubierta. La documentación oficial dice que la primera vez que Claude Code escribe el archivo en un repositorio que no lo ignora, añade el patrón correspondiente a tu archivo de excludes global de git, "so the file stays out of your commits in every repository". El riesgo no es GitHub: es local. Respaldos, copias del proyecto, pantalla compartida, sincronización a la nube.
El detector es una heurística. Busca formas conocidas: cadenas tipo AIza, tipo sk-, y variables cuyo nombre lleva KEY, TOKEN, SECRET o PASS. Una contraseña que parezca una palabra normal se le escapa. Una lista vacía no es un certificado de limpieza.
El aviso de la 2.1.246 solo cubre la clase 2. No dice nada de la clase 1, que es la que guarda credenciales.
Los números que veas son tuyos. El 284 / 40 / 3 del ejemplo sale de una sola máquina medida el 26 de agosto de 2026. No es una estadística de nadie: es una medición.
Corre el comando en tus proyectos, apunta los tres números y repite dentro de un mes. Si un comando necesita una credencial, la credencial no va en el comando.
Preguntas frecuentes
¿Dónde está el archivo de permisos de Claude Code?
Hay más de uno. El del proyecto vive en .claude/settings.local.json dentro de la carpeta del proyecto, y el de usuario en la carpeta .claude de tu carpeta personal. El archivo settings.json que está al lado es distinto: ese es el compartido del equipo y sí se sube al repositorio. El comando de esta guía mira las dos rutas a la vez y salta la que no exista.
¿El comando de auditoría muestra mis claves en pantalla?
No. Devuelve solo tres conteos y las posiciones dentro de la lista de reglas, nunca el contenido de la credencial. La variante que lista las reglas una por una sustituye por un marcador cualquier cosa con forma de clave antes de imprimirla. Aun así, el tapado es una heurística: no copies esa salida a un chat o a un issue sin releerla línea por línea.
¿Basta con borrar la regla que contiene la clave?
No. Borrar la regla quita el texto del archivo, pero la credencial sigue siendo válida en el servicio que la emitió. Hay que rotarla: crear la nueva primero para no cortar lo que tengas corriendo, borrar la expuesta en la consola del proveedor, guardar la nueva fuera del comando y solo después limpiar las reglas. Y borra el respaldo .bak, que guarda la clave vieja.
¿Por qué Claude Code guarda el comando completo?
Porque necesita saber qué fue exactamente lo que autorizaste para volver a reconocerlo. Si guardara solo el nombre del programa, tu permiso cubriría muchas más cosas de las que aprobaste, que es justo lo contrario de lo que quieres de un sistema de permisos. Es comportamiento documentado, no un fallo: el problema aparece cuando el comando que aprobaste llevaba una credencial dentro.
¿Qué es el aviso de comodín de la versión 2.1.246?
Es un aviso al arrancar sobre reglas allow de Bash cuyo asterisco va antes del subcomando, como Bash(git * main). Claude Code no cambia nada de cómo funciona la regla: solo la nombra para que la puedas estrechar. Existe porque ese asterisco también cubre opciones metidas antes del subcomando, incluida -c, con la que git puede acabar ejecutando un programa que el comando nombre.