El service worker que no quiere morir, y la VM a la que no le importa
Google republicó accidentalmente la semana pasada un bug de Chromium de hace cuatro años — un service worker que sigue ejecutando JavaScript después de cerrar el navegador, en todos los navegadores Chromium principales, todavía sin parchar. La prueba de concepto ya está circulando. La pregunta interesante no es cómo funciona. Es qué significa la persistencia en un navegador cuya máquina subyacente entera deja de existir cuando cierras la pestaña.
La persistencia no es una propiedad del código. Es una propiedad del lugar al que se le permite vivir al código. Un service worker que «nunca muere» tiene un problema cuando la máquina en la que fue instalado se destruye cada vez que cierras la ventana.
El 20 de mayo de 2026, el rastreador de incidencias de Chromium retiró silenciosamente las restricciones de acceso sobre un bug que llevaba en estado restringido desde 2022. En cuestión de horas, el bug — junto con una prueba de concepto funcional — estaba siendo replicado, archivado y comentado por toda la web. Google intentó volver a cerrar la incidencia. Era demasiado tarde; la página ya estaba en la Wayback Machine, y el artículo de BleepingComputer salió al día siguiente.
El bug es real, la prueba de concepto funciona y, cuatro años después de su informe inicial, sigue sin estar parchado en Chrome Dev 150 ni en Edge 148. Afecta a todos los navegadores derivados de Chromium: Chrome, Edge, Brave, Opera, Vivaldi, Arc.
Lo que permite hacer a una página es, como frase, mundano: registrar un service worker — un pequeño fragmento de JavaScript que el navegador acepta mantener en ejecución en segundo plano — que no deja de ejecutarse cuando la página se cierra, cuando la pestaña se cierra, cuando el navegador se cierra, cuando la máquina se reinicia. La investigadora Lyra Rebane, que reportó originalmente el bug en diciembre de 2022, le resumió a BleepingComputer el peor caso: en Microsoft Edge el menú de descargas ni siquiera aparece, con lo que el resultado es «un RCE de JS completamente silencioso que sigue ejecutándose incluso después de que cierres el navegador».
El detalle técnico de por qué existe el bug importa menos que la clase de ataque que habilita. Es una primitiva de persistencia. Una página que visitaste una vez, posiblemente hace meses, posiblemente un anuncio que no recuerdas haber visto, tiene permiso para seguir ejecutando código en tu computadora indefinidamente. Enrolamiento en una botnet, fraude publicitario, criptojacking, participación en DDoS, exfiltración lenta de datos, recolección oportunista de credenciales cada vez que vuelves a iniciar sesión en el mismo sitio — todo eso se vuelve trivial una vez que tienes un punto de apoyo que sobrevive al cierre del navegador.
Es un buen bug, en el sentido de que es revelador. Es un mal bug, en el sentido de que se va a usar.
Qué es realmente un service worker
La plataforma web lleva, desde hace alrededor de una década, permitiendo a las páginas instalar pequeños programas de JavaScript llamados service workers. Se ejecutan en segundo plano, separados de cualquier pestaña, y existen para que las aplicaciones web puedan hacer cosas razonables — cachear recursos para que un sitio cargue cuando estás sin conexión, entregar notificaciones push, sincronizar datos cuando la red vuelve.
Un service worker se registra contra un origen (digamos, la
combinación de https, el hostname example.com y el puerto por
defecto). Una vez registrado, el navegador lo guarda en disco y lo
vuelve a traer la próxima vez que visitas cualquier cosa de ese
origen — o, en el caso de funciones de segundo plano como los
mensajes push, lo despierta periódicamente por su cuenta. Desde el
punto de vista del usuario, el worker es invisible. Desde el punto
de vista del sistema operativo, el worker es parte del navegador,
ejecutándose con los permisos que tenga el proceso del navegador.
El bug que reportó Rebane es, a grandes rasgos, que una página hostil puede abusar de una API de descargas en segundo plano para registrar un worker que el navegador después se niega a terminar. Otros bugs de service worker de Chromium de la misma familia ya se han enviado antes; este es al menos el segundo en los últimos doce meses. El patrón — la página planta algo, el navegador lo guarda, el navegador lo trae de vuelta más tarde por su cuenta — es la parte que merece la pena retener. Es también el patrón que se estrella contra un muro en el momento en que el navegador deja de ser dueño de un trozo de disco de larga duración.
Qué requiere la persistencia
La persistencia — en el sentido de seguridad, no en el sentido de plataforma web — es el movimiento que hace un atacante inmediatamente después del acceso inicial. Una página o un binario aterriza en tu máquina y, antes de hacer cualquier otra cosa interesante, se las arregla para que alguna parte de sí mismo sobreviva a los pasos de limpieza evidentes: cerrar la pestaña, salir del navegador, reiniciar, dejar el portátil suspendido en el tren de vuelta a casa. En un SO de escritorio, la persistencia tiene su propio vocabulario — launch agents en macOS, tareas programadas en Windows, unidades de usuario de systemd en Linux, extensiones del navegador, entradas de autoarranque.
Un service worker es, en efecto, un launch agent dentro del navegador. Se registra, se almacena y se trae de vuelta a la vida según un calendario que controla el navegador. Es una de las primitivas de persistencia más elegantes que ofrece la plataforma web, lo cual es parte de por qué sigue atrayendo bugs.
Crucialmente, cada forma de persistencia de esa lista tiene el
mismo requisito, expresado en tres palabras: algo debe persistir.
Un launch agent persiste porque hay un sistema de archivos en el
que se escribe el archivo .plist del agente, y el agente se
vuelve a leer desde ese sistema de archivos en cada arranque. Un
service worker persiste porque hay un perfil del navegador en el
que se escribe el registro del worker, y el registro se vuelve a
leer cada vez que el navegador arranca. Quita la capa de
almacenamiento y la «persistencia» se va con ella.
Esa frase — quita la capa de almacenamiento y la persistencia se va con ella — es la parte de la arquitectura alrededor de la cual se construyó Bromure.
Cómo ejecuta un navegador Bromure
Una sesión de Bromure no es un proceso. Es una máquina virtual. Cuando abres una ventana del navegador, el host lanza un pequeño invitado Linux desechable sobre el hipervisor de Apple Silicon. El proceso del navegador dentro de ese invitado es donde se ejecutan tus pestañas. El invitado tiene su propio sistema de archivos, su propia memoria, su propio kernel. Ninguna de esas cosas es del host.
Hay dos piezas de esa arquitectura que importan en particular para el bug del service worker.
Primero, el perfil en disco en el que normalmente viviría el registro de un service worker está dentro del sistema de archivos del invitado, no en el sistema de archivos de tu Mac. Cuando el invitado desaparece, ese sistema de archivos se va con él. Segundo, el invitado en sí tiene una vida que fija el usuario — por defecto, un invitado está ligado a un perfil (trabajo, personal, banca, un sandbox para codificar con un LLM) y se borra cuando el usuario lo elige, pero para pestañas efímeras el invitado se destruye cuando se cierra la ventana. Incluso en modo perfil, el invitado es una instantánea de volumen de disco que el usuario puede revertir en cualquier momento.
En el caso desechable, el worker que «nunca muere» no tiene desde dónde no-morir. La fila en disco desde la que se habría vuelto a leer en el siguiente arranque del navegador estaba dentro de una máquina virtual que ya no existe. El JavaScript que «siguió ejecutándose después de cerrar el navegador» solo siguió ejecutándose porque había algo sobre lo cual seguir ejecutándose. Cuando el cierre del navegador también cierra la máquina subyacente, el worker se detiene al mismo tiempo que el navegador — exactamente el comportamiento que asumía la especificación original.
En el caso del perfil — digamos, mantienes un perfil de larga duración para «redes sociales» que está pensado para recordarte entre sesiones — el worker sobrevive como siempre lo ha hecho, acotado a la VM de ese único perfil. Sigue sin poder ver tus Documentos, tu llavero, tus otros perfiles ni el resto de tu computadora. Y si alguna vez sospechas que ese perfil ha recogido algo que no debería, puedes revertir toda la VM a su última instantánea limpia, lo que tarda más o menos lo mismo que volver a lanzar un navegador.
El argumento general
Sería fácil leer lo anterior como «Bromure resulta que derrota este bug específico de service worker». Eso es cierto, pero no es la afirmación interesante. La afirmación interesante es más general, y este incidente es una pequeña pieza de evidencia a su favor.
Los días cero del navegador seguirán cayendo. Este es al menos el segundo bug de service worker de Chromium en los últimos doce meses. Los grandes de 2024 y 2025 fueron confusiones de tipo en V8 y escapes de sandbox vía Mojo, pagados a precios de seis cifras, usados por proveedores de spyware comercial y grupos alineados con estados. Hubo compromisos de renderer antes de que existieran los service workers; habrá compromisos de renderer mucho después de que este bug en particular se parche. La economía de encontrar nuevos — especialmente con auditores de IA de clase Mythos ahora en el bucle a ambos lados de la ventana de divulgación — se mueve contra el defensor, no a su favor.
La pregunta relevante, cuando se dispara un día cero, no es ¿era el navegador impecable? No lo era. La pregunta relevante es ¿a qué llegó realmente el exploit?
Un escape del sandbox de Chromium — el «día cero del navegador» canónico sobre el que has leído cada pocos meses durante la última década — es una cadena que toma un bug de memoria en alguna pieza del renderer (V8, Skia, WebP, Dawn, libxml2) y la usa para convencer al proceso broker del navegador de hacer algo que el renderer no debía poder hacer. Esos bugs se pagan a seis cifras porque cruzan el muro que los ingenieros de Chromium dedican la mayor parte de su tiempo a mantener: el sandbox in-process.
Ese muro es una pieza de ingeniería extraordinaria. También está, genealógicamente, hecho del mismo tipo de material que el bug que lo cruzó — C++, espacios de direcciones compartidos, primitivas IPC como Mojo. Cuando aparece un nuevo bug de memoria en V8 o en uno de los cien parsers que vienen con Chromium, es el tipo de cosa que, con suficiente trabajo, puede hacer palanca y abrir el sandbox.
Una VM es un tipo distinto de muro. El navegador que se ejecuta
dentro no tiene una dirección de memoria virtual que mapee al
kernel del host; el kernel del host no parsea la página que el
navegador está parseando; la pila de virtualización es una
superficie diminuta comparada con un navegador, y lo que parsea
(estados de registro de vCPU, hypercalls paravirt, descriptores
de buffer virtio) no son bytes adversariales arreglados por una
página web. Hay bugs en esa frontera. Están en una liga distinta
de dificultad, son varios órdenes de magnitud más raros y, cuando
aparecen, los suele anunciar el fabricante del hipervisor con un
CVE.
Qué cambia esto para el usuario
En el caso específico del bug del service worker expuesto la semana pasada, un usuario de Bromure no necesita esperar a un parche. El bug también existe en su navegador — Bromure envía Chromium upstream — pero el comportamiento que el bug habilita es el comportamiento que la arquitectura de Bromure ya deshabilita. Un worker que «vive para siempre» vive solo mientras viva la VM invitada en la que se registró, que es como mucho mientras dure el perfil que mantengas, y como mínimo mientras la pestaña esté abierta.
En el caso más general de «un día cero del navegador caerá en
algún momento del próximo mes», un usuario de Bromure obtiene una
forma de resultado distinta. El exploit se dispara, en el peor
caso, contra el contenido del invitado: la sesión del navegador,
las cookies de los sitios en los que hay sesión iniciada dentro de
ese perfil, cualquier cosa que el usuario haya tecleado en ese
navegador. Eso es una pérdida real y no intentamos ocultarla. Lo
que no se pierde es el host: ni claves SSH, ni ~/.aws, ni
llavero, ni Documentos, ni árbol de código fuente, ni espacio de
trabajo de agente LLM.
Ese intercambio — la sesión del navegador puede verse comprometida, el host no, y la propia sesión es corta — es el intercambio que sigue teniendo sentido a medida que el ritmo de nuevos bugs del navegador sube.
Lo que no estamos afirmando
Dos matices, dichos abiertamente:
Las VMs desechables no parchan Chromium
El bug del service worker es un bug de Chromium. Las personas adecuadas para arreglarlo son los ingenieros de Chromium, y deberían hacerlo. La arquitectura de Bromure cambia el coste de que el bug aún no esté arreglado; no es un sustituto de arreglarlo. Reportamos upstream cuando encontramos cosas, y leemos los mismos avisos que todos los demás.
Un escape de VM es una categoría real
Un bug en el propio hipervisor — el framework de Virtualization de Apple, o una de las superficies más pequeñas a su alrededor — permitiría, en principio, que un atacante alcance el host. Esos bugs existen. También son varios órdenes de magnitud más raros que los bugs de memoria del navegador, y la superficie es drásticamente menor. Bromure hace que la exposición del host a un compromiso del renderer dependa de esa clase de bug, en lugar de la clase que se envía cada pocas semanas.
El titular que leerás el mes que viene
Habrá otro bug de service worker de Chromium. Habrá otra confusión
de tipo en V8. Habrá otro desbordamiento de heap en libwebp.
Habrá otro día cero que un adversario extremadamente bien dotado
de recursos ha estado usando contra gente extremadamente
específica durante un tiempo extremadamente largo, que se vuelve
público un martes por la mañana con un aviso de parche de
emergencia.
La pregunta útil, en la mañana de cualquiera de esos, no es si ya has reiniciado tu navegador. La pregunta útil es qué había realmente dentro de la caja en la que aterrizó el bug. Para la mayoría de los usuarios, hoy, esa caja es la misma caja que contiene sus archivos y su llavero y sus inicios de sesión guardados. Para un usuario de Bromure, la caja es una máquina virtual invitada cuya peor pérdida posible es la ventana que de todas formas estaba a punto de cerrar.
La persistencia requiere algo sobre lo que persistir. Haz que ese algo sea desechable, y toda una clase de ataque deja de ser una clase de ataque.