«No volver a preguntar»: el archivo de Claude Code que está publicando secretos en GitHub

Apiiro encontró 2.539 secretos reales en repositorios públicos: 1.122 archivos de Claude Code con contraseñas y claves en claro. El culpable es un botón, «Sí, y no volver a preguntar», que guarda el comando con la credencial dentro.

Una comprobación simple bastó para destaparlo. La empresa de seguridad Apiiro buscó en GitHub público un archivo que Claude Code crea en casi todos los proyectos donde se usa, el .claude/settings.local.json, y de 104.185 copias descargadas, 1.122 contenían al menos un secreto real en texto plano: contraseñas de bases de datos, tokens de API, claves de nube y accesos SSH. Al ampliar la búsqueda al historial de git, al archivo de configuración que los equipos comparten a propósito, a las transcripciones de los agentes y a sus ficheros de memoria, la cifra sube a 2.539 credenciales distintas en 1.994 repositorios.

No hay exploit, ni malware, ni ingeniería social de por medio. Es el efecto acumulativo de una función de comodidad: cuando Claude Code pide permiso para ejecutar un comando y eliges «Sí, y no volver a preguntar», guarda ese comando, literal y con la contraseña dentro, en un archivo de configuración del proyecto. Ese archivo está pensado para no viajar con el repositorio, pero la protección depende de un ajuste local de git que no viaja con el proyecto, y en una parte de los casos el propio agente acaba confirmando el archivo. El resultado es una vía de fuga de credenciales que no necesita atacante: solo necesita que un desarrollador pulse «sí» una vez.

Cómo llega un secreto a un archivo de texto

El escenario no es exótico. Pídele a Claude Code que compruebe por qué falla el inicio de sesión en staging, o que ejecute una migración, y el comando que propone lleva dentro la cadena de conexión o el token, porque sin él no puede funcionar. La documentación de Anthropic describe la cadena completa: en modo manual, Claude Code pide aprobación antes de ejecutar comandos de shell, y la opción «Sí, y no volver a preguntar» guarda la regla de forma permanente, por repositorio y por comando.

La clave está en qué se guarda. La regla no es una categoría, es el propio texto del comando. Si el comando literal era una invocación de psql con la URL de conexión, o una llamada a sshpass con la contraseña de root, eso es exactamente lo que acaba escrito en el archivo, en JSON plano.

Hay un matiz importante que juega a favor. A veces el botón ofrece un patrón, del tipo npm test *, y en ese caso no hay nada que filtrar: la regla describe la forma del comando, no su contenido. El problema aparece cuando lo que se guarda es el comando literal con lo que el desarrollador escribió encima. Con comandos compuestos el detalle se multiplica: al aprobarlos con «no volver a preguntar», Claude Code guarda una regla separada por cada subcomando que necesite permiso, hasta cinco.

Y esto no es un comportamiento del modelo. La propia documentación de Anthropic insiste en que las reglas de permisos las aplica Claude Code, no el modelo, y los investigadores de Apiiro reprodujeron el fenómeno con los modelos más recientes, Opus 5.5 y Fable 5.1. Es el arnés, no la inteligencia artificial, el que escribe el archivo. Por eso mismo el problema no se arregla con un modelo más prudente.

Por qué llega a GitHub: la protección que no viaja

Claude Code intenta que el archivo se quede en el ordenador del desarrollador. La primera vez que lo escribe dentro de un repositorio git, lo añade a la lista global de exclusiones de git del usuario, no al .gitignore del proyecto. Eso cubre el caso habitual y solo ese.

No protege una carpeta que se convierte en repositorio más tarde, ni un clon en otro equipo, ni un archivo que ya estaba dentro del repositorio. Y como la exclusión es local, no viaja con el proyecto: nadie que clone el repositorio la hereda. La diferencia entre las dos opciones es que una protege una sola máquina y la otra habría viaje con el proyecto, que es lo único que protegería a quien clona el repositorio.

Lo normal es que el archivo acabe en un commit. En 539 de los 1.105 repositorios con un secreto real que los investigadores pudieron trazar, un commit con Claude como coautor modificó el archivo. Los commits citan a los modelos más recientes, desde Sonnet 4.5 hasta Opus 5.5. Y nada dentro del repositorio indica a quien lo clona que ese archivo contiene material sensible: a simple vista es un JSON más.

La escala: qué hay ahora mismo en GitHub

Los números de Apiiro dan una idea de la magnitud. De las 104.185 copias de .claude/settings.local.json presentes en 103.617 repositorios, 1.122, un 1,1 %, contienen al menos un secreto real, y entre todas acumulan 1.379 credenciales distintas: 636 claves con formato de proveedor conocido y 743 contraseñas o tokens asociados a hosts públicos.

El reparto es revelador. Más de un tercio abren una base de datos directamente: 379 contraseñas de base de datos y 122 claves service-role de Supabase. Otras 103 son tokens de cuenta de Supabase, que controlan todos los proyectos de la cuenta, no uno. Después vienen 43 credenciales SSH para servidores públicos, algunas con la contraseña de root escrita en una invocación de sshpass, 42 tokens de GitHub, GitLab o npm, 23 claves de AWS y 6 claves privadas.

Hay 63 organizaciones con un secreto real en una lista pública, y los casos más claros son de empresas del sector, no de atacantes: un proveedor de bases de datos con su propia clave de administración en la nube junto al host del clúster, una empresa B2B con la contraseña de producción de Azure Postgres, una agencia de software con el token de producción de Square, un proyecto de código abierto con más de 1.000 estrellas y una clave con formato de OpenAI, un token de npm, la contraseña de propietario de una aplicación de historias clínicas y la contraseña del rol postgres de una empresa de pagos. Son startups, agencias, empresas de servicios de TI y algunos proveedores consolidados, expuestos a través de sus repositorios de demos y talleres.

El tiempo de exposición tampoco ayuda. A fecha 2 de noviembre, todos los archivos con un secreto seguían en la rama por defecto de su repositorio. En la mediana de los repositorios afectados, el archivo se había comprometido 217 días antes; en 118 repositorios, más de un año. Los forks amplifican: 211 bifurcaciones de 74 repositorios arrastran los mismos secretos.

Borrar el archivo tampoco sirve. Hay 734 secretos más en 509 repositorios que solo existen en versiones antiguas, normalmente porque alguien eliminó el fichero. La credencial no se rota porque desaparezca del árbol de trabajo: sigue en el historial, en los forks y en los clones.

Y no todo se queda en GitHub. De 31.053 Hugging Face Spaces, 126 contienen el archivo, y en uno hay reglas con una clave de servicio de base de datos y una contraseña de usuario. En GitLab.com, 2 de las 69 copias públicas revisadas contienen un secreto real, uno de ellos un token de acceso.

Lo que los escáneres de secretos no ven

Apiiro ejecutó gitleaks y TruffleHog sobre las 104.185 copias, sin el paso de verificación que las herramientas aplican en modo normal. gitleaks marcó 564 de los 1.122 archivos con secreto y TruffleHog marcó 643. Los dos juntos dejan pasar 215: uno de cada cinco.

La causa es conocida y sigue abierta: los escáneres buscan formatos de clave, un prefijo de proveedor o una cadena aleatoria larga. Una contraseña escrita tras PGPASSWORD=, sshpass -p o mysql -p, o una cabecera Authorization: Bearer dentro de una llamada a curl, es una palabra corriente en una línea de comandos corriente. Para el escáner no hay clave, hay texto.

El patrón se repite en todos los sitios donde un agente registra lo que ejecutó. Un escáner afinado a formatos de clave falla igual en las transcripciones, en los ficheros de memoria y en las notas de traspaso. La investigación confirma el patrón con datos: de 76.011 copias del .claude/settings.json compartido, 298 tenían un secreto; de 2.523 transcripciones de agentes de .specstory, aider o exports de Cursor, 120 archivos en 87 repositorios; de 1.200 ficheros de memoria tipo MEMORY.md, nueve repositorios; de 2.095 notas de traspaso o despliegue, 33 archivos, y en un tercio de ellos la nota la escribió el propio agente; y de 173 ficheros de autoaprobación de VS Code Copilot, nueve repositorios.

Los equipos que comparten el .claude/settings.json a propósito son los que más riesgo acumulan, porque sus secretos suelen estar justo en las reglas de permisos, que son comandos literales. Y hay un patrón claro en el tamaño: uno de cada diez archivos de más de 8 KB contiene un secreto real, frente a menos de uno de cada 500 por debajo de 500 bytes. Cuanto más tiempo lleva un desarrollador pulsando «no volver a preguntar», más grande es el fichero y más probable es que contenga algo.

El ordenador del desarrollador también es el objetivo

La mayoría de esos archivos nunca llegan a GitHub, y eso tampoco los hace seguros: son JSON plano en la carpeta del proyecto, en cada portátil donde alguien pulsó «no volver a preguntar». El malware ya recorre máquinas de desarrollo en busca de credenciales. El gusano Shai-Hulud, que en septiembre de 2025 infectó cientos de paquetes de npm, descargaba TruffleHog y lo ejecutaba sobre los ficheros de las víctimas: ese escáner marca 643 de los 1.122 archivos con secreto. Tres semanas antes, unos paquetes npm maliciosos ordenaron al propio Claude Code y a Gemini CLI del desarrollador que buscaran secretos por su disco.

Hay además un efecto perenne: un secreto aprobado una vez se queda en el archivo hasta que alguien borra la regla, mucho después de que ese comando dejara de ser necesario. Y el archivo puede salir sin pasar por git, dentro de un paquete npm publicado, en una imagen de Docker construida con un COPY, en un zip o en una carpeta sincronizada.

Lo que se sabía, y qué respondió Anthropic

El mecanismo no es nuevo. Los usuarios de Claude Code lo vienen reportando en GitHub desde agosto de 2025, en los tickets 5544, 11982, 13106 y 91270. Ninguno recibió respuesta de Anthropic; tres los cerró automáticamente un bot por inactividad o por duplicado, y el más reciente se cerró un día antes de la publicación de la investigación. Apiiro notificó a cada organización afectada los días 2 y 3 de noviembre, y una de ellas confirmó que su clave había estado viva.

La documentación de Anthropic sí describe el comportamiento, porque es intencionado: la regla se guarda en el .claude/settings.local.json en la raíz del repositorio. Lo que no dice es que esa regla pueda contener la credencial en claro. Para quien usa la herramienta a diario, la diferencia entre leer la documentación y leer el fichero es la diferencia entre saber que existe un problema y tener la contraseña delante.

Dos límites del trabajo conviene tener presentes. No se usó ni se probó ninguna credencial y no se contactó con ningún host. Y «secreto real» significa que el valor encaja con un formato de proveedor conocido o que apunta a un host público, no que se haya comprobado que siga vigente.

Qué hacer en tu equipo

Para los desarrolladores:

  • Si el archivo está versionado, deja de seguirlo con git rm --cached .claude/settings.local.json y añádelo al .gitignore del propio repositorio.
  • Rota todas las credenciales que el archivo haya contenido alguna vez. Borrar el archivo no limpia el historial, los forks ni los clones.
  • Abre el archivo en tu máquina, aunque nunca se haya subido, y borra cada regla que contenga un secreto.
  • Deja de escribir secretos en comandos: referencia variables del gestor de secretos, del tipo $DB_URL, para que la regla guarde el nombre y no el valor.
  • Aprueba patrones, como Bash(npm run:*), en lugar de comandos literales.
  • Busca en tu organización los settings.local.json y settings.json, y revisa el historial de cada repositorio, incluso donde el archivo ya no está.

Para los equipos de seguridad:

  • Trata las carpetas de agente (.claude/, .cursor/, .gemini/, .specstory/) y los ficheros de memoria de los agentes como si fueran un .env: pueden contener secretos, tanto en portátiles como en repositorios.
  • Añádelos al escaneo de secretos en el pre-commit y en la integración continua, incluido el historial, y comprueba que las reglas detecten contraseñas dentro de comandos (PGPASSWORD=, sshpass -p, mysql -p, cabeceras bearer), no solo formatos de clave.
  • Añade el .claude/settings.local.json a las plantillas de .gitignore de la organización.
  • Prioriza las credenciales que apuntan a un servidor público: son las que dan acceso.

La lección de fondo: la confianza se guarda en un archivo

El fallo no está en el modelo ni en el atacante. Está en el diseño de una función que convierte una decisión puntual, «puedes ejecutar esto otra vez», en un artefacto durable, versionable y portable que además contiene la carga útil que se quería proteger. Una regla de permisos debería recordar la forma del comando, no su contenido: Bash(psql *) es una política; Bash(psql postgres://usuario:clave@servidor/base) es una credencial guardada en un fichero con extensión .json.

El mismo razonamiento aplica al resto de ficheros que escriben los agentes. El agente registra lo que ejecutó, y lo que ejecutó lleva dentro el secreto. Si la transcripción, la nota de traspaso o el fichero de memoria terminan en un repositorio, el secreto termina en el repositorio. Y no hace falta que un humano lo suba: basta con que el agente lo escriba y alguien ejecute git add -A.

Un dato de contexto viene de otro estudio, publicado por The Register el 6 de noviembre a partir de un hackathon de la AI Alignment Foundation con AE Studio: en 181 ensayos con nueve agentes de programación, a los que se dio un token privado con la instrucción de no compartirlo, el agente lo publicó en el 15 % de los casos y ejecutó un script desconocido en el 24 %, frente al 2 % y el 0 % de un foro normal. Cuando el equipo añade presión, el porcentaje de scripts ejecutados sube al 34 %. La lección que extraen los investigadores se reduce a una frase: lo que no quieres en GitHub tampoco debería estar en el contexto de un agente.

Entre ambas historias hay un mismo patrón. Al agente le importa completar la tarea, y el camino más corto para completarla pasa por obedecer. Cuando el atajo consiste en dejar de proteger el secreto, el agente lo deja de proteger. El diseño de las herramientas tiene que asumir eso, porque el modelo no va a corregirlo solo.

Fuentes