Volver a todas las publicaciones
Publicado el · por Renaud Deraison

El hook nunca llegó al modelo

El 31 de agosto, Token Security repasó en The Hacker News los nuevos endpoints de la Compliance API de Anthropic para sesiones locales de Claude Code. Son una mejora real, y el artículo es preciso sobre dónde se detiene el transcript: hooks que se disparan antes de que una herramienta se ejecute, archivos que reposan en el disco, procesos lanzados fuera de una sesión, cualquier cosa servida por Bedrock o Vertex. El gusano de npm de la semana pasada vivió toda su vida en ese hueco. Bromure Agentic Coding pincha el cable entre la VM y todo lo demás, así que el registro cubre lo que ocurrió y no lo que alguien le dijo al modelo.

Un transcript solo puede contener lo que alguien le dijo al modelo. La mitad interesante de una sesión de agente es la mitad que nadie dice en voz alta.

Durante casi todo este año, una responsable de ingeniería que quisiera saber qué había estado haciendo Claude Code en un portátil tenía dos opciones: leer el scrollback de quien programaba, o comprar un wrapper de terceros. Los endpoints de conformidad de Anthropic cubrían claude.ai y Claude Desktop, y Claude Code apenas nada.

Eso cambió el 11 de agosto. Anthropic publicó tres endpoints de la Compliance API para sesiones locales: uno lista sesiones, otro recupera los metadatos de una sesión, y el tercero extrae el transcript completo desde GET /v1/compliance/apps/sessions/local/{session_id}/messages. El 31 de agosto, Token Security publicó un recorrido por lo que ofrecen en The Hacker News.

Token Security patrocinó aquel artículo, y sigue siendo uno de los relatos más cuidadosos que he leído sobre dónde se sitúa el horizonte de un registro.

Tres tipos de bloque, y qué cubren

El transcript llega en tres clases de bloque. En palabras del artículo: “Todo lo que se comunica al modelo queda registrado en tres tipos de bloque: text, tool_use y tool_result. Entre ellos cubren los prompts del usuario, los comandos bash, las lecturas y escrituras, e incluso los comandos MCP.”

Eso es mucho: cada comando de shell que el modelo eligió, cada archivo que leyó, cada herramienta MCP que llamó. Si tu pregunta es “qué decidió hacer este agente el martes”, el transcript la responde, y la responde desde los servidores de Anthropic en lugar de desde un archivo en la máquina que estás investigando.

La cláusula que hace todo el trabajo es la primera: todo lo que se comunica al modelo.

El horizonte

Cuatro cosas quedan fuera de esa frontera, y el artículo las nombra las cuatro.

Los hooks. “Los hooks son el caso más claro: se ejecutan localmente, entre la decisión del modelo y la ejecución real de la herramienta, y pueden impedir que una herramienta se ejecute o que un prompt se envíe.” Un hook es código en el disco de quien desarrolla, que se dispara en el hueco entre el momento en que el modelo elige una acción y el momento en que la acción ocurre. El modelo nunca se entera, así que el transcript nunca lo lleva.

El disco. “Ninguno de los dos puede ver lo que reposa en el disco: archivos de configuración, skills y plugins instalados y sus archivos .md (salvo que se hayan usado en una sesión), o procesos lanzados fuera de una sesión.” Una skill que instalas y nunca invocas no deja rastro, y tampoco lo deja un proceso en segundo plano que el agente arrancó hace una hora y olvidó.

Otros proveedores de modelos. “Si ejecutas Claude Code sobre un modelo que no es de Anthropic, no obtienes ninguna cobertura de la Compliance API, porque solo registra interacciones con los modelos de Anthropic. Las sesiones que corren sobre Bedrock, Foundry o Google Cloud no quedarán cubiertas.” La cobertura se detiene en los modelos de Anthropic, lo que deja fuera a Codex, Gemini CLI, Grok y lo que tu equipo instale el trimestre que viene.

Las decisiones de permiso. La aprobación por parte de quien desarrolla de un comando peligroso, o una bandera de bypass que dejó pasar un prompt entero, aterriza en OpenTelemetry en lugar de en el transcript de conformidad.

Todo esto es comportamiento correcto. Un registro anclado al modelo recoge lo que el modelo vio. La pregunta para tu próxima revisión de seguridad es si las cosas que más necesitas ver caen del lado del modelo de esa línea.

la frontera del hipervisor — proxy de host y conmutadorcada byte que la VM envía o recibe, de cualquier proceso, bajo cualquier agentelo que llegó al modelotext · tool_use · tool_resultprompts del usuariocomandos bash que eligiólecturas y escrituras que pidióllamadas a herramientas MCPentre las dos líneashooks, disparándose antesde que la herramienta corrascripts de instalación y node-gyp,hijos de un solo comandotarballs que llegan denpm, PyPI, Cargo, Mavenlas peticiones salientesde un servidor MCPprocesos iniciados fuerade toda sesiónsesiones en Bedrock,Foundry o VertexAmbos registros son exactos. Responden a preguntas distintas, porque están anclados a fronteras distintas.
Dos fronteras alrededor de la misma sesión de agente. La interior es el modelo: todo lo que se le comunica está en el transcript de la Compliance API. La exterior es el hipervisor: todo lo que la VM envía o recibe cruza el proxy y el conmutador virtual de Bromure. La región interesante es el espacio entre ambas, donde viven los hooks, los procesos hijos creados durante la instalación, los tarballs de paquetes, el tráfico propio de los servidores MCP y los backends que no son de Anthropic.

El gusano de la semana pasada vivía en ese hueco

Hace cuatro días, un generador de código de npm con más de 150 000 descargas semanales empezó a distribuir un ladrón de credenciales. Nosotros describimos la cadena de publicación el sábado: un desconocido comentó en una pull request, el workflow de publicación del proyecto leyó el comentario sin comprobar quién lo había escrito, y salieron diez versiones firmadas.

Vuelve a leer ese malware con el transcript de conformidad en la otra mano. Traza dónde aparecería cada paso.

El agente ejecuta npm install. Eso es un bloque tool_use, y el transcript lo tiene. Todo lo posterior es un proceso hijo.

La primera oleada no declaraba ningún script de instalación. Su carga estaba en binding.gyp, la descripción de compilación que lee node-gyp, dentro de un campo conditions que node-gyp evalúa con Python. El modelo nunca tuvo un turno sobre eso y ninguna llamada a herramienta lo nombra: una expresión de Python en un archivo de compilación, ejecutándose como nieta del único comando que el transcript registró como perfectamente ordinario.

El barrido que vino después recorrió más de 150 patrones glob por el directorio personal en busca de claves SSH, archivos .env, credenciales de nube y tokens de registro. Nadie le preguntó nada de eso al modelo.

Después, la persistencia. En cada repositorio que podía alcanzar, el gusano escribía un .claude/settings.json con un hook SessionStart que ejecuta setup.mjs cada vez que alguien abre el proyecto en Claude Code. Un hook, en el disco, disparándose antes de que nadie consulte al modelo: dos de los cuatro puntos ciegos en un solo archivo. Quien atacó eligiendo ese sitio casi con certeza no había leído jamás el changelog de conformidad de ningún proveedor.

Toda la ventana hostil duró tres horas y once minutos, y ninguna parte de ella llegó nunca a un modelo.

Dónde pone Bromure la escucha

Bromure Agentic Coding ejecuta cada espacio de trabajo como su propia máquina virtual sobre Apple Silicon, y todo lo que sale de esa VM pasa por un proxy y un conmutador virtual que viven del lado Mac del hipervisor. El conmutador desvía los flujos de los puertos 80 y 443 de la VM hacia el proxy sin ninguna variable de entorno que configurar y sin nada que el invitado pueda desactivar, así que el registro no depende de que el agente coopere, ni de que el agente sepa que el proxy está ahí.

En el nivel de traza Solo actividad y superiores, cada petición obtiene un registro de metadatos: marca de tiempo, host, puerto, método, ruta, código de estado, latencia, bytes de petición medidos antes de cualquier intercambio de credenciales, bytes de respuesta, qué credenciales sustituyó el proxy a la salida, y un aviso si la petición llevaba un token bearer que Bromure no emitió. Ese registro existe tanto si la petición vino del agente, como de un script postinstall, de un servidor MCP haciendo lo suyo, o de un proceso que alguien arrancó hace tres horas y olvidó.

La ventana Línea de tiempo de seguridad (Window → Security Timeline…) está al lado y responde a otra pregunta. El Inspector de trazas te dice lo que el agente envió; la línea de tiempo te dice lo que decidieron los motores de Bromure. Una sola tabla cronológica, lo más reciente primero, con colores: verde para permitido, rojo para bloqueado, azul para informativo — Intermediación de credenciales, Cortafuegos, Cadena de suministro, Guardarraíles, Inyección de prompt, Credencial usada, TLS ascendente. Mantiene 5000 filas en memoria. La copia duradera son las trazas de sesión cifradas en ~/Library/Application Support/BromureAC/traces/, selladas con AES-GCM bajo la misma clave del Llavero que tus secretos del espacio de trabajo.

La misma instalación, en un espacio de trabajo Bromure, se lee así.

Línea de tiempomotorqué decidió20:31:02Cadena de sum.npm [email protected] — bloqueadola versión tiene 9 minutos, el umbral son 2 días20:31:02Cadena de sum.npm [email protected] — reescritoscripts retirados, hash de metadatos actualizado20:31:07Cortafuegosobjects.githubusercontent.com:443 tcp — bloqueadosin regla, el tráfico no coincidente es Denegar20:31:09Interm. de cred.brm_a1b2… → paste.example — bloqueadointento de exfiltración, VM en pausa20:31:09Guardarraílesgit-receive-pack github.com — bloqueadoGitHub es de Solo lectura en este espacio20:33:41Inyección de promptrules FLAG source=/repo/AGENTS.md
Lo que una Línea de tiempo de seguridad de Bromure registra durante la instalación que el transcript vio como un único bloque tool_use. Cada fila viene de un motor del lado del host vigilando el cable, así que nada de ello depende de que la carga pase por el modelo, y nada de ello puede editarse desde código dentro de la VM.

Ni una sola de esas filas exige que la carga haya pasado por un modelo, y ninguna de ellas puede editarse desde código dentro de la VM, porque los motores que las escriben corren en el Mac y el invitado no tiene ruta hasta ellos.

Las mismas filas, sea cual sea el agente que ejecutes

Un equipo se estandariza en Claude Code. Luego alguien trae Codex para un proyecto, el grupo de plataforma enruta un espacio de trabajo por Bedrock por residencia de datos, y una investigadora se pone a ejecutar Grok. Según el propio relato del artículo, tres de esos cuatro no producen nada en la Compliance API.

Un proxy que lee nombres de servidor TLS ve los cuatro igual, y los mismos eventos llm.request, tool.use, command.run, file.read y file.write salen de cada uno de ellos, extraídos del tráfico en lugar de concedidos por un proveedor. En un Mac inscrito en un espacio de trabajo de bromure.io se transmiten a la organización por TLS mutuo, junto a egress.firewall para cada veredicto del cortafuegos, credential.exfiltration cuando una credencial señuelo sale hacia un host para el que nunca fue emitida, y supply_chain.fetch por cada paquete que el espacio de trabajo descargó. Este último se dispara incluso con todas las capas de aplicación de cadena de suministro apagadas, porque Bromure separa observar de bloquear. El flujo no lleva prompts en crudo. Responde a lo que el agente hizo, no a lo que quien programa pidió.

Claude Code · Anthropicen la Compliance APIClaude Code · Bedrockno cubiertoCodex · OpenAIno cubiertoGrok · xAIno cubiertoproxy de host + switchlee nombres de servidor TLS,no ID de cuentaun solo flujo de eventosllm.request · tool.usecommand.run · file.readegress.firewallsupply_chain.fetchcredential.exfiltration
Cuatro espacios de trabajo sobre cuatro backends distintos. Un registro anclado a los modelos de Anthropic cubre el primero. Un registro anclado al cable los cubre los cuatro, porque se extrae del tráfico en lugar de concederlo el proveedor que resulte estar sirviendo los tokens.

La línea con la que termina el artículo

La frase más afilada del texto deja atrás la cobertura: “los registros de actividad por sí solos no pueden decirte si el acceso de un agente es legítimo.”

Eso vale para cualquier registro, y por eso los motores de Bromure escriben la línea de tiempo ellos mismos en lugar de encargarle el trabajo a un observador aparte. Cada fila roja de esa ventana recoge una decisión que ya ocurrió. Una fila de cadena de suministro significa un paquete que el agente nunca recibió. Una fila de cortafuegos significa una conexión que nunca se abrió. La fila de Guardarraíles es un push que el host rechazó con un 403, que el agente leyó como un fallo de API corriente, y la fila de intermediación de credenciales es un señuelo que nunca llegó a ser un secreto real, porque el real nunca estuvo en la VM.

Aún tienes que decidir la política. Pero respondes a “¿fue legítimo este acceso?” mientras configuras el espacio de trabajo, no semanas después leyendo filas.

Dónde poner tu escucha

Si ejecutas agentes de código a cualquier escala, activa la Compliance API. Hace cuatro semanas esa superficie casi no tenía visibilidad, y una semana de transcripts te contará cosas de tu propio equipo que no sabías.

Luego haz la segunda pregunta: ¿qué haces con una máquina cuyo transcript parece limpio? En un espacio de trabajo Bromure la respuesta ya está en pantalla. Abre Window → Trace Inspector para los hosts, Window → Security Timeline para los veredictos, o ejecuta bromure-cli trace hostnames y lee cada dominio que ese espacio de trabajo ha contactado desde que arrancó.

El agente no tiene que mencionar nada para que esté ahí. Instala Bromure Agentic Coding, abre la línea de tiempo y mira una instalación.