La regla de permiso se quedó con la contraseña
El 6 de noviembre, Apiiro informó de que el botón “Yes, and don't ask again” de Claude Code guarda el comando aprobado, secretos incluidos, en .claude/settings.local.json, y de que 1.122 repositorios públicos de GitHub habían hecho commit de ese archivo con contraseñas, tokens y claves de aspecto válido. Dos escáneres de secretos populares pasaron por alto 215. En Bromure Agentic Coding, cada secreto que el agente puede escribir es un sustituto, así que una regla guardada contiene un valor que no funciona en ningún sitio, y cada push te espera en el Mac.
Haces clic en “Yes, and don't ask again” para que una pregunta no vuelva a salir. Claude Code guarda el comando entero para reconocerlo la próxima vez, contraseña incluida, en un archivo dentro de tu proyecto.
El 6 de noviembre, Apiiro publicó una investigación sobre agentes de código que filtran secretos a través de “don't ask again”. The Hacker News lo cubrió a la mañana siguiente.
El mecanismo es corriente. Cuando Claude Code pide ejecutar un comando de
shell y eliges Yes, and don't ask again, escribe una regla de permiso en
.claude/settings.local.json, dentro del proyecto. A veces la regla es un
patrón como npm test *. A veces es el comando exacto, y cuando ese comando
llevaba una contraseña después de PGPASSWORD=, sshpass -p o mysql -p,
la contraseña queda en un archivo JSON junto a tu código.
Apiiro buscó ese archivo en GitHub público y encontró:
- 104.185 copias en 103.617 repositorios;
- 1.122 copias con al menos un secreto, 1.379 secretos distintos en total;
- 379 contraseñas de bases de datos, 43 accesos SSH a servidores públicos, 42 tokens de GitHub, GitLab y npm, y 23 claves de AWS;
- secretos de 63 organizaciones, entre ellos una clave de nube con nivel de administrador de un proveedor de bases de datos y la contraseña de propietario de una base de historiales médicos.
Todos esos archivos seguían en su rama por defecto cuando Apiiro lo
comprobó. En la mitad de los repositorios llevaban ahí al menos 217 días.
Commits más antiguos contenían 734 secretos más en 509 repositorios, y 211
forks de 74 repositorios llevaban copias. Sumando el .claude/settings.json
compartido, el historial y los registros de chat de agentes guardados,
Apiiro contó 2.539 secretos en 1.994 repositorios.
Por qué se coló por todas partes
Claude Code sí intenta mantener el archivo fuera de git. La primera vez que
escribe settings.local.json, lo añade a tus exclusiones globales de git.
Esa regla vive en un solo ordenador. El portátil de un compañero, un runner
de CI o un clon recién hecho no la tienen, y no hace nada por un archivo que
ya está en un commit. Claude Code nunca añade el archivo al .gitignore
propio del proyecto. De los 16 agentes de código que comparó Apiiro, Claude
Code es el único confirmado que guarda por defecto el comando aprobado
completo en un archivo del proyecto.
Los escáneres de secretos no lo hicieron mejor. Apiiro pasó gitleaks y
TruffleHog por cada copia. Gitleaks marcó 564 de los 1.122 archivos,
TruffleHog 643, y 215 se escaparon de ambos. La mayoría de esos 215 eran
contraseñas escritas dentro de comandos. Los escáneres buscan formatos de
clave conocidos como AKIA… o ghp_…, y sshpass -p hunter2 no encaja con
ninguno.
Los usuarios avisaron del problema hace más de un año. Una incidencia de agosto de 2025, anthropics/claude-code#5544, decía que Claude Code guardaba en este archivo, palabra por palabra, comandos aprobados con claves de API. Un bot la cerró como “not planned” tras 60 días sin actividad. Otros cuatro informes relacionados corrieron la misma suerte con el mismo bot, el último el día antes del artículo de The Hacker News.
El secreto estaba en el comando porque estaba en manos del agente
Cada uno de esos 1.379 secretos era un valor que el agente podía escribir. Un
desarrollador lo había pegado en el chat, o lo había dejado en un archivo
.env, o el agente lo leyó de ~/.aws/credentials o ~/.pgpass y lo metió
en línea en el comando que quería ejecutar. La regla de permiso anotó lo que
el agente ya tenía.
El consejo de Apiiro para el futuro apunta en la misma dirección: mantener
los secretos fuera de los comandos y hacer referencia a una variable de un
gestor de secretos, para que la regla guardada solo contenga $DB_URL. Eso
ayuda si cada desarrollador y cada agente lo cumple siempre. Un agente que
intenta que funcione un comando que falla expandirá la variable. Bromure toma
el otro camino y mantiene el valor real fuera del entorno del agente por
completo.
El mismo clic, en Bromure Agentic Coding
Bromure Agentic Coding ejecuta Claude Code, Codex, Grok, Kimi y omp en máquinas virtuales Ubuntu en tu Mac. El agente recibe las credenciales a través de lo que llamamos la frontera del cable: dentro de la VM, cada secreto es un sustituto, y el valor real está cifrado en el Mac, con su clave maestra en el llavero de macOS. Un proxy en el Mac cambia el sustituto por el valor real en el cable, y solo en una petición al host al que pertenece esa credencial.
Así que cuando el agente pide ejecutar un comando con un token dentro y eliges Yes, and don't ask again, Claude Code guarda el comando igual que antes. El token que contiene es el sustituto.
Cada secreto que el agente puede escribir es un sustituto
Los sustitutos conservan la forma que esperan las herramientas, así que en el proyecto no cambia nada:
| Lo que Apiiro encontró filtrado | Lo que tiene el agente en una máquina Bromure |
|---|---|
| Tokens de GitHub y GitLab | ghp_ + 36 caracteres, glpat- + 20, en GH_TOKEN, GITLAB_TOKEN y ~/.git-credentials |
| Tokens de npm | un valor brm_… en NPM_TOKEN, de una entrada Other API key limitada a registry.npmjs.org |
| Claves de AWS | el ID de clave de acceso real con una clave secreta falsa; el Mac vuelve a firmar cada petición con la real |
| Contraseñas de bases de datos | un valor brm-db-… en la variable que elijas, para ClickHouse, la Mongo Data API y Elasticsearch |
| Accesos SSH | ninguna contraseña: firmas hechas con claves que se quedan en el Mac |
Toma un comando que un agente podría ejecutar contra un endpoint de ClickHouse:
curl -u default:brm-db-7Qk2…Xw9 \
'https://analytics.example.com:8443/?query=SELECT+count()+FROM+events'
Apruébalo para siempre y la regla de permiso contendrá brm-db-7Qk2…Xw9. El
proxy cambia el sustituto en las peticiones a analytics.example.com, en la
cabecera Basic-auth y en el cuerpo de la petición, porque los secretos de
bases de datos suelen viajar dentro de SQL y JSON. En cualquier otro sitio,
la cadena no sirve de nada.
Con AWS, el secreto nunca llega a la VM. El ~/.aws/config del agente apunta
a un ayudante que entrega el ID de clave de acceso real con un secreto falso
de 40 caracteres. La AWS CLI, boto3 y terraform firman con el falso, y el Mac
quita esa firma y vuelve a firmar con el secreto real antes de que salga la
petición. La clave secreta real nunca existe en la VM, así que ningún comando
que escriba el agente puede contenerla.
SSH cubre los 43 accesos a servidores del recuento de Apiiro. El
SSH_AUTH_SOCK de una máquina Bromure es un puente hacia el Mac: la VM puede
pedir firmas, y los bytes de la clave privada nunca cruzan. Las claves
protegidas por una YubiKey, Secretive o 1Password se pueden ofrecer una a
una, con la petición de toque en tu Mac. El agente nunca necesita
sshpass -p, así que no hay contraseña que una regla de permiso pueda
guardar.
Un sustituto en GitHub no abre nada
Supongamos que el archivo sí llega a un repositorio público, a un fork y a
una docena de commits antiguos. Quien lo extraiga obtiene ghp_ y 36
caracteres que GitHub nunca ha emitido. Enviado desde su portátil, falla la
autenticación. Solo el proxy de tu Mac sabe a qué token real corresponde, y
ese proxy solo da servicio a la VM de tu máquina.
Si un sustituto sale alguna vez hacia el host equivocado desde dentro de la máquina, el proxy lo trata como un intento de exfiltración. Bloquea la petición con un HTTP 451, pausa la VM y abre una alerta titulada This environment may have been compromised que nombra la credencial, el host al que pertenece y el host al que se envió. Así cada sustituto es también un cable trampa, no solo un marcador de posición.
La rotación se hace en un solo sitio
La primera instrucción de Apiiro es rotar cada contraseña, token y clave que
el archivo haya contenido, antes de limpiar nada. Con secretos reales
repartidos entre archivos .env, el historial del shell y la configuración
del agente, eso es una búsqueda del tesoro. En Bromure, los valores reales
están en un solo sitio: el panel Credentials de la máquina, en el Mac.
Pega ahí el nuevo token y el sustituto en la VM sigue siendo el mismo, así
que las reglas de permiso, los scripts y las configuraciones dentro de la
máquina siguen funcionando.
Una Other API key también puede ser una referencia de 1Password como
op://Engineering/npm/token. Bromure la resuelve en el Mac y la vuelve a
resolver cada 2 minutos, así que una clave rotada en 1Password surte efecto
en un par de minutos, sin reiniciar y sin nada que editar en la VM.
Llevar los secretos a ese panel es un solo paso. Import env file… lee un
.env o un ~/.bashrc existente, muestra las variables con los valores
ocultos y las convierte en credenciales. La carpeta del proyecto ya no
necesita el archivo .env del que el agente leía las contraseñas.
El push te espera
En 539 de los repositorios que rastreó Apiiro, un commit que acreditaba a Claude como coautor modificó el archivo. Los agentes hacen commit y push como parte de terminar una tarea.
En las máquinas Bromure nuevas, cada servicio de forja empieza en Prompt
before write en el panel Guardrails. Las lecturas pasan. Un git push
por HTTPS se detiene en un diálogo en el Mac que muestra la operación, y nada
sale hasta que respondes. La política se aplica en el proxy, así que nada
dentro de la VM puede desactivarla.
Un revisor que lee la línea
Los 215 secretos que ningún escáner detectó eran contraseñas dentro de
comandos. Un modelo que lee PGPASSWORD=… psql -h db.example.com en un
archivo JSON con commit sabe lo que tiene delante.
Una code review de Bromure vigila un repositorio de GitHub con un agente en una de tus máquinas, en Every new commit, Every new pull request o con un calendario semanal, y Bromure elimina los hallazgos duplicados entre análisis. Para el paso de Apiiro “busca en tu organización, historial incluido”, Run Baseline Now lanza una revisión puntual de todo el repositorio. GitHub se consulta desde el Mac, y el agente de revisión trabaja con los mismos sustitutos que cualquier otro.
La transcripción también guarda el sustituto
El recuento de Apiiro incluía registros de chat de agentes guardados. Los
archivos de sesión del propio Claude Code en la VM registran lo que vio el
agente, y en una máquina Bromure eso es el sustituto. Las trazas de Bromure en
el Mac guardan los cambios de token como vistas previas cortas del tipo
sk-a…f9q3, y los bytes del secreto real nunca entran en un registro de
traza. Cuando aun así aparece en el tráfico un secreto de aspecto real,
porque alguien lo pegó a mano en la VM, la traza lo marca como posible fuga,
y bromure-cli trace leaks las lista todas. Activa Only allow credentials
Bromure injects en Guardrails, y una petición que lleve una credencial que
Bromure no puso ahí queda bloqueada y dispara la alerta de compromiso.
Sustitutos en cada comando
Los tokens, los secretos de bases de datos y las claves de AWS son sustitutos en la VM. Una regla de permiso guarda un valor que solo funciona a través de tu Mac.
Sin contraseña para SSH
La VM pide firmas al Mac. La clave privada nunca cruza, y sshpass -p
nunca hace falta.
Los push esperan en el Mac
Prompt before write es el valor por defecto en máquinas nuevas. Un push espera tu respuesta, y lo impone el proxy.
Rotar una vez
Los valores reales viven en un solo panel, o en 1Password. Cámbialos ahí y cada sustituto sigue funcionando.
Qué hacer esta semana
De la lista de Apiiro, para todo equipo que use agentes de código:
- Busca
.claude/settings.local.jsony.claude/settings.jsonen tu organización de GitHub, historial incluido. - Rota cada secreto que esos archivos hayan contenido y después limpia.
- Ejecuta
git rm --cached .claude/settings.local.jsony añade el archivo al.gitignoredel proyecto y a las plantillas de tu organización. - Trata
.claude/,.cursor/,.gemini/y.specstory/como archivos.enven el análisis pre-commit y de CI, y asegúrate de que el escáner detecta contraseñas dentro de comandos.
Después, saca los secretos reales del alcance del agente. En una máquina Bromure, el próximo “don't ask again” guarda un sustituto, tanto si el desarrollador se acordó ese día de las reglas de Apiiro como si no.
Para el patrón más amplio de agentes que copian secretos en el historial y los registros, mira La transcripción se quedó con el token. Para ver cómo funciona el cambio en detalle, lee el capítulo Credentials del manual.