Bastaba con estar conectado
Un desarrollador que estaba liberando espacio en disco encontró 700 MB en un directorio oculto y tiró del hilo: ZCode, de Z.ai, estaba metiendo en un tar todo su árbol de trabajo, 42.411 archivos con un 86,6 % de .git, lo cifraba con una clave que solo tiene Z.ai y lo publicaba directo en un bucket de Alibaba Cloud. Ningún atacante, ningún CVE, ningún malware. El disparador era tener la sesión iniciada. El cliente cifraba el paquete, así que ningún escáner podía leerlo, y los únicos datos que quedaban eran el destino y el tamaño. Esos son dos de los campos que Bromure Agentic Coding apunta para cada petición que hace un workspace, en un cable que la herramienta dentro de la VM no elige.
Su herramienta de programación no necesita estar comprometida para enviar su código fuente a algún sitio. Le basta con un ajuste por defecto. Confiar en el proveedor no es la cuestión. Pregúntese si se enteraría.
Está liberando espacio en un disco lleno. Un directorio oculto de su carpeta
personal ocupa más de 700 megabytes, y no recuerda haber instalado nada de ese
tamaño. Dentro, en una carpeta llamada checkpoints, hay un único archivo de
313 MB con extensión .enc y, al lado, un registro de metadatos que dice
failureCount: 564.
Quinientos sesenta y cuatro intentos de algo que usted nunca pidió.
Setecientos megabytes y un hilo del que tirar
Ahí empezó el desarrollador chino conocido como ferstar, el 18 de septiembre, y el informe de ingeniería inversa de lo que encontró merece leerse entero. El directorio pertenecía a ZCode, el cliente de escritorio de programación con IA de Z.ai, la empresa detrás de los modelos GLM. CSO Online e InfoWorld recogen los dos su resumen del comportamiento palabra por palabra:
“Siempre que tiene la sesión iniciada, ZCode empaqueta en silencio todo su workspace — historial .git completo, caché de assets LFS, reflogs y configuraciones globales de la aplicación —, lo cifra y lo sube directamente a Aliyun OSS.”
El disparador está en la primera oración. No una pregunta que usted hizo, no la función de indexación que activó: un token de sesión válido, que el proceso de captura espera, y nada más. Una sola sesión registró 62 eventos de captura en sus logs. Cuando borró el paquete, el cliente construyó otro en menos de media hora e incrementó el contador de fallos.
Qué había en la caja
El snapshot que desmontó contenía 42.411 archivos, 345 MB antes de cifrar y 313 MB después. De qué se componían esos 345 MB:
.git/lfs/: 196,1 MB, 56,8 %.git/objects/: 102,2 MB, 29,6 %.git/logs/: 0,6 MB, 0,2 %- todo lo demás, es decir, código y documentación de verdad: 46,2 MB, 13,4 %
El 86,6 % de la subida era .git. El paquete llevaba cada objeto del
historial del repositorio, cada binario grande que la caché LFS había descargado y
el reflog, que enumera cada rama en la que ha estado y cada commit que hizo y
luego deshizo. Quien lee su árbol de trabajo averigua qué está entregando; quien
lee su .git averigua qué intentó, qué revirtió y quién lo commiteó.
Los dos interruptores gobernaban su lado
ZCode venía con dos ajustes que un desarrollador cuidadoso habría encontrado y apagado: uno para la optimización de la experiencia, otro para la indexación de snapshots del repositorio. Los dos hacían algo real, y ninguno hacía lo que su nombre sugiere.
El primero gobernaba si Z.ai estaba autorizada a usar los datos. El segundo gobernaba si el servidor indexaba el snapshot una vez llegaba. El empaquetado y la subida corrían de todos modos, porque el sidecar de captura arranca en cuanto el proveedor de tokens le entrega un JWT válido, sin ninguna preferencia de usuario en ningún punto del camino. Apague los dos interruptores y el cliente seguía metiendo su repositorio en un tar, seguía cifrándolo, seguía publicándolo en el bucket y prometía discreción sobre lo que pasaba después.
El fallo de un solo proveedor, pero la forma se repite en todas partes. Un control que vive dentro del programa que restringe es una petición. Corre sobre el mismo código que tendría que estar mal para que usted lo necesitara.
Nada en el cable podía leerlo
El cliente cifraba cada paquete con AES-256-CTR antes de que saliera de la máquina
y envolvía la clave simétrica con una clave pública RSA-OAEP-SHA256 que le
entregaba zcode.z.ai. La clave privada correspondiente nunca baja. ferstar no
podía abrir su propio paquete; la única parte que podía era la que tenía el
bucket.
Eso descarta toda defensa suya que funcione leyendo bytes. Un escáner de secretos no encuentra ninguna clave de AWS en ese tarball, y la inspección profunda de paquetes ve una sesión TLS hacia un endpoint de almacenamiento que transporta texto cifrado envuelto en texto cifrado.
Sobreviven dos datos: adónde fue y cuánto pesaba. Un POST de 313 MB a un host de almacenamiento de objetos en una región donde su equipo no despliega destaca, siempre que alguien apunte dónde se conectan sus herramientas y cuánto envían. Nadie lo hacía, y por eso encontrar esto exigió arqueología de espacio en disco y una semana de ingeniería inversa en vez de un vistazo a un log.
Dónde está la salida
Bromure Agentic Coding pone al agente y a su cadena de herramientas dentro de una VM Ubuntu en su Mac, y le da a esa VM exactamente una manera de alcanzar internet: un proxy man-in-the-middle que corre en el host, fuera del guest, donde ningún proceso de la VM puede reconfigurarlo.
Esto no pide nada al software. El conmutador de red virtual desvía de forma
transparente los flujos de los puertos 80 y 443 de la VM hacia el proxy del
host, sin ninguna variable de entorno necesaria y sin nada que el guest pueda
desactivar, activo por defecto en todo workspace. Una herramienta que ignora
HTTPS_PROXY, abre su propio socket y habla directamente con un endpoint de
almacenamiento pasa igualmente por ahí, porque la interceptación vive en el
conmutador al que el guest está enchufado y no en el guest.
Entonces le pasan cuatro cosas a un POST de 313 MB que nadie pidió.
Cada petición deja registro
Cada petición que pasa por el proxy deja un registro de traza: marca de tiempo, host, puerto, método, ruta, estado, latencia y bytes de petición medidos antes de cualquier canje de credencial. No necesita descifrar un paquete para advertir una subida del tamaño de un repositorio hacia un host donde nunca ha desplegado: el tamaño y el destino son columnas. Ordene por una, filtre por la otra.
Nunca sale del Mac
El cortafuegos de egreso por workspace es una tabla de reglas ordenada
(acción, protocolo, host o CIDR, puertos, métodos HTTP) que termina en
default allow o default deny. Ejecute un workspace en modo lista blanca y
la subida no tiene adónde ir. Dos sitios aplican las reglas: el conmutador
compara la IP de destino y el nombre de host escuchado por DNS, el proxy
compara de nuevo el nombre de servidor TLS, y sus cambios llegan a las
sesiones en marcha al instante.
Cada veredicto es una fila
Cada decisión del cortafuegos aterriza en la Security Timeline, así que una herramienta que sigue reintentando, 564 veces por ejemplo, aparece como una denegación repetida contra un host con nombre y no como un disco que se llena despacio. El patrón que costó una semana reconstruir es justo lo que esa ventana está hecha para mostrar.
Las configs que empaqueta son señuelos
“Configuraciones globales de la aplicación” es la expresión que más debería
preocuparle de la frase de ferstar. Dentro de un workspace Bromure,
~/.git-credentials, ~/.docker/config.json, ~/.kube/config y
~/.aws/config existen y funcionan, y cada valor que contienen es un falso
que preserva la estructura. Ningún archivo, variable de entorno ni proceso de
la VM guarda jamás una clave de API, un token OAuth, un secreto de AWS o una
clave privada SSH de verdad.
El alcance hace el resto. Un workspace es un proyecto, o una frontera de
credenciales; dos workspaces nunca comparten una VM, y la carpeta personal del
guest es su propio archivo de imagen. Lo que una herramienta empaqueta dentro de
un workspace es ese workspace, lo que deja fuera los otros cuarenta repositorios
de su Mac, el ~/.aws/config de su host y sus claves SSH. Las claves no llegan a
ningún guest: la firma ocurre en el host por un puente vsock, y los bytes privados
se quedan ahí.
Qué hizo Z.ai después
Z.ai se disculpó el día en que ferstar publicó. La empresa desactivó el flujo de subida en ZCode v3.14.0, que según InfoWorld salió el 19 de septiembre listando una corrección para un problema anómalo de subida de repositorios, y llamó a la firma de seguridad NSFOCUS para auditar el resultado. NSFOCUS confirmó que “todos los objetos de datos del bucket zcode-prod de Alibaba Cloud OSS, así como el propio bucket, han sido eliminados”, y Z.ai dice que tras la auditoría “no se identificó ninguna ruta funcional capaz de disparar la generación de snapshots locales del repositorio ni de transmitir archivos locales al exterior”, y que el código “nunca se ha usado para entrenar modelos”. Después abrieron el código: cliente, backend, UI compartida, Agent CLI y runtime, con Apache-2.0, en GitHub. Eso supera la norma del sector, y significa que la próxima persona que quiera comprobarlo no necesitará un desensamblador.
Cris Thomas, de Semgrep, citado en la cobertura de CSO, nombró la categoría: “Esto no es realmente un problema del modelo de IA, es un problema anticuado de arquitectura de seguridad.” Quiere que los proveedores revelen qué sale de la máquina, adónde va y cuánto tiempo se conserva, “con los permisos mínimos activados por defecto, no los máximos”. Ningún cliente puede imponer eso desde fuera.
El ajuste por defecto es el modelo de amenaza
Aquí nadie atacó a nadie. Nadie robó una credencial, envenenó un paquete ni convenció a un modelo de nada. Un producto hizo lo que sus autores lo construyeron para hacer, y lo que lo construyeron para hacer fue enviar su repositorio a un sitio donde usted no podía leerlo. El incidente es un ajuste por defecto, y los ajustes por defecto los eligen personas cuyos intereses corren al lado de los suyos.
Ese es el argumento para poner la frontera fuera de la herramienta. No tiene que creer que su asistente de programación es hostil; solo tiene que notar que no puede auditar lo que no ve, y que el panel de ajustes del proveedor es un sitio raro para guardar el interruptor. Ejecute el agente y su cadena de herramientas donde cada conexión que abren cruce un cable que es suyo, donde el registro de ese cruce se escriba en su Mac, y donde la regla que detiene una subida de 313 MB sea una línea que usted escribió y no una casilla que honró otra persona.
Entonces “¿qué está enviando mi herramienta de programación?” es una ventana que abre un martes por la tarde, no un proyecto de investigación.