O service worker que não morre, e a VM que não se importa
O Google republicou acidentalmente na semana passada um bug do Chromium de quatro anos atrás — um service worker que continua executando JavaScript depois que o navegador fecha, em todo grande navegador Chromium, ainda sem correção. A prova de conceito já está circulando. A pergunta interessante não é como funciona. É o que “persistência” significa em um navegador cuja máquina subjacente inteira deixa de existir quando você fecha a aba.
Persistência não é uma propriedade do código. É uma propriedade do lugar em que o código tem permissão para viver. Um service worker que "nunca morre" tem um problema quando a máquina em que foi instalado é destruída toda vez que você fecha a janela.
Em 20 de maio de 2026, o issue tracker do Chromium silenciosamente removeu as restrições de acesso a um bug que estava em status restrito desde 2022. Em poucas horas, o bug — junto com uma prova de conceito funcional — estava sendo espelhado, arquivado e documentado pela web. O Google se mexeu para fechar a questão de novo. Era tarde demais; a página já estava na Wayback Machine, e o artigo da BleepingComputer foi ao ar no dia seguinte.
O bug é real, a prova de conceito funciona, e quatro anos após seu relatório inicial ele ainda não está corrigido no Chrome Dev 150 nem no Edge 148. Afeta todo navegador derivado do Chromium: Chrome, Edge, Brave, Opera, Vivaldi, Arc.
O que ele permite que uma página faça é, como frase, banal: registrar um service worker — um pequeno trecho de JavaScript que o navegador concorda em manter rodando em segundo plano — que não para de rodar quando a página fecha, quando a aba fecha, quando o navegador fecha, quando a máquina reinicia. A pesquisadora Lyra Rebane, que originalmente reportou o bug em dezembro de 2022, resumiu o pior caso à BleepingComputer: no Microsoft Edge o menu de download nem aparece, então o resultado é "RCE de JS completamente silenciosa que continua rodando mesmo depois que você fecha o navegador."
O detalhe técnico do porquê o bug existe importa menos do que a classe de ataque que ele habilita. É uma primitiva de persistência. Uma página que você visitou uma vez, possivelmente meses atrás, possivelmente um anúncio do qual você não se lembra de ter visto, tem permissão para continuar executando código no seu computador indefinidamente. Recrutamento em botnet, fraude de anúncios, cryptojacking, participação em DDoS, exfiltração lenta de dados, coleta oportunista de credenciais sempre que você faz login no mesmo site de novo — tudo isso se torna trivial quando você tem um ponto de apoio que sobrevive ao fechamento do navegador.
É um bom bug, no sentido de que é esclarecedor. É um bug ruim, no sentido de que vai ser usado.
O que um service worker realmente é
A plataforma web, há cerca de uma década, permite que páginas instalem pequenos programas em JavaScript chamados service workers. Eles rodam em segundo plano, separados de qualquer aba, e existem para que aplicações web possam fazer coisas razoáveis — cachear recursos para que um site carregue quando você está offline, entregar notificações push, sincronizar dados quando a rede volta.
Um service worker é registrado contra uma origem (digamos, a
combinação de https, o hostname example.com e a porta padrão).
Uma vez registrado, o navegador o armazena em disco e o traz de
volta na próxima vez que você visita qualquer coisa nessa origem —
ou, no caso de recursos em segundo plano como mensagens push, o
acorda periodicamente por conta própria. Do ponto de vista do
usuário, o worker é invisível. Do ponto de vista do sistema
operacional, o worker é parte do navegador, rodando com quaisquer
permissões que o processo do navegador tenha.
O bug que Rebane reportou é, em linhas gerais, que uma página hostil pode abusar de uma API de download em segundo plano para registrar um worker que o navegador então se recusa a encerrar. Outros bugs de service worker do Chromium da mesma família já chegaram antes; este é pelo menos o segundo nos últimos doze meses. O padrão — a página planta algo, o navegador armazena, o navegador traz de volta mais tarde por conta própria — é a parte que vale a pena guardar. É também o padrão que esbarra em uma parede no momento em que o navegador deixa de possuir um pedaço de disco de longa vida.
O que persistência exige
Persistência — no sentido de segurança, não no sentido da plataforma web — é a jogada que um atacante faz imediatamente após o acesso inicial. Uma página ou um binário aterrissa em sua máquina e, antes de fazer qualquer outra coisa interessante, providencia para que alguma parte de si sobreviva às etapas óbvias de limpeza: fechar a aba, sair do navegador, reiniciar, colocar o laptop para dormir no trem de volta para casa. Em um SO desktop, persistência tem um vocabulário próprio — launch agents no macOS, tarefas agendadas no Windows, unidades systemd de usuário no Linux, extensões de navegador, entradas de autostart.
Um service worker é, com efeito, um launch agent dentro do navegador. Ele é registrado, armazenado e trazido de volta à vida em um cronograma que o navegador controla. É uma das primitivas de persistência mais elegantes que a plataforma web oferece, o que é parte do motivo de continuar atraindo bugs.
Crucialmente, toda forma de persistência nessa lista tem o mesmo
requisito, declarado em três palavras: algo deve persistir. Um
launch agent persiste porque existe um sistema de arquivos no qual
o arquivo .plist do agente é escrito, e o agente é relido desse
sistema de arquivos a cada boot. Um service worker persiste porque
existe um perfil de navegador no qual o registro do worker é
escrito, e o registro é relido sempre que o navegador inicia. Tire
a camada de armazenamento e a "persistência" vai junto.
Essa frase — tire a camada de armazenamento e a persistência vai junto — é a parte da arquitetura em torno da qual a Bromure foi escrita.
Como a Bromure roda um navegador
Uma sessão da Bromure não é um processo. É uma máquina virtual. Quando você abre uma janela do navegador, o host inicia um pequeno guest Linux descartável no hypervisor do Apple Silicon. O processo do navegador dentro desse guest é onde suas abas rodam. O guest tem seu próprio sistema de arquivos, sua própria memória, seu próprio kernel. Nenhuma dessas coisas é do host.
Existem duas peças dessa arquitetura que importam para o bug do service worker em particular.
Primeiro, o perfil em disco em que um registro de service worker normalmente viveria está dentro do sistema de arquivos do guest, não no sistema de arquivos do seu Mac. Quando o guest desaparece, esse sistema de arquivos vai junto. Segundo, o próprio guest tem uma vida útil que o usuário define — por padrão, um guest é atrelado a um perfil (trabalho, pessoal, banco, um sandbox para LLM-coding) e é apagado quando o usuário escolhe, mas para abas efêmeras o guest é destruído quando a janela é fechada. Mesmo no modo de perfil, o guest é um snapshot de volume de disco que o usuário pode reverter a qualquer momento.
No caso descartável, o worker "que nunca morre" não tem de onde não-morrer. A linha em disco da qual ele teria sido relido no próximo lançamento do navegador estava dentro de uma máquina virtual que não existe mais. O JavaScript que "continuou rodando depois que o navegador fechou" só continuou rodando porque havia algo em que ele pudesse continuar rodando. Quando o fechamento do navegador também fecha a máquina subjacente, o worker para ao mesmo tempo que o navegador — exatamente o comportamento que a especificação original assumia.
No caso de perfil — digamos, você mantém um perfil de "redes sociais" de longa duração que é feito para lembrar você entre sessões — o worker sobrevive como sempre sobreviveu, com escopo na VM desse único perfil. Ele ainda não consegue ver seus Documentos, seu keychain, seus outros perfis, ou o resto do seu computador. E se em algum momento você suspeitar que esse perfil pegou algo que não deveria, pode reverter a VM inteira para seu último snapshot limpo, o que leva mais ou menos o mesmo tempo que relançar um navegador.
O ponto geral
Seria fácil ler o acima como "a Bromure por acaso derrota esse bug específico de service worker." Isso é verdade, mas não é a afirmação interessante. A afirmação interessante é mais geral, e este incidente é uma pequena evidência dela.
Zero-days de navegador continuarão aterrissando. Este é pelo menos o segundo bug de service worker do Chromium nos últimos doze meses. Os grandes de 2024 e 2025 foram type confusions em V8 e escapes de sandbox em Mojo, pagos a preços de seis dígitos, usados por fornecedores comerciais de spyware e grupos alinhados a estados. Houve comprometimentos de renderer antes de existirem service workers; haverá comprometimentos de renderer muito depois de esse bug específico ser corrigido. A economia de encontrar novos — especialmente com auditores de IA classe Mythos agora no loop dos dois lados da janela de divulgação — está se movendo contra o defensor, não em direção a ele.
A pergunta relevante, quando um zero-day dispara, não é se o navegador era impecável. Ele não era. A pergunta relevante é o que o exploit de fato alcançou.
Um escape de sandbox do Chromium — o "zero-day de navegador" canônico sobre o qual você leu a cada poucos meses na última década — é uma cadeia que pega um bug de memória em alguma peça do renderer (V8, Skia, WebP, Dawn, libxml2) e o usa para convencer o processo broker do navegador a fazer algo que o renderer não deveria ser capaz de fazer. Esses bugs são pagos a seis dígitos porque atravessam a parede que os engenheiros do Chromium passam a maior parte do tempo mantendo: o sandbox in-process.
Essa parede é uma peça notável de engenharia. Também é, genealogicamente, feita do mesmo tipo de material que o bug que a atravessou — C++, espaços de endereçamento compartilhados, primitivas de IPC como Mojo. Quando um novo bug de memória aparece no V8 ou em um dos cem parsers empacotados com o Chromium, é o tipo de coisa que pode, com trabalho suficiente, alavancar o sandbox aberto.
Uma VM é um tipo diferente de parede. O navegador rodando dentro
dela não tem um endereço de memória virtual que mapeie para o
kernel do host; o kernel do host não faz parsing da página que o
navegador está fazendo parsing; a stack de virtualização é uma
superfície minúscula comparada a um navegador, e o que ela faz
parsing (estados de registradores de vCPU, hypercalls paravirt,
descritores de buffer virtio) não são bytes adversariais arranjados
por uma página web. Bugs nessa fronteira existem. Estão em uma liga
diferente de dificuldade, são várias ordens de magnitude mais raros
e, quando aparecem, normalmente são anunciados pelo fornecedor do
hypervisor com um CVE.
O que isso muda para o usuário
No caso específico do bug de service worker exposto na semana passada, um usuário Bromure não precisa esperar por um patch. O bug existe no navegador dele também — a Bromure entrega o Chromium upstream — mas o comportamento que o bug habilita é o comportamento que a arquitetura da Bromure já desabilita. Um worker que "vive para sempre" vive apenas enquanto a VM guest na qual foi registrado, o que é, no máximo, enquanto durar o perfil que você mantém, e, no mínimo, enquanto a aba estiver aberta.
No caso mais geral de "um zero-day de navegador vai aterrissar em
algum momento no próximo mês", um usuário Bromure obtém um formato
diferente de resultado. O exploit dispara, no pior caso, contra o
conteúdo do guest: a sessão de navegação, os cookies dos sites
atualmente logados dentro daquele perfil, qualquer coisa que o
usuário tenha digitado naquele navegador. Isso é uma perda real e
não estamos tentando esconder. O que não se perde é o host: nenhuma
chave SSH, nenhum ~/.aws, nenhum keychain, nenhum Documentos,
nenhuma árvore de código-fonte, nenhum workspace de agente LLM.
Esse trade — a sessão do navegador pode ser comprometida, o host não pode, e a própria sessão é curta — é o trade que continua fazendo sentido à medida que a taxa de novos bugs de navegador aumenta.
O que não estamos alegando
Duas ressalvas, declaradas abertamente:
VMs descartáveis não corrigem o Chromium
O bug de service worker é um bug do Chromium. As pessoas certas para corrigi-lo são os engenheiros do Chromium, e eles deveriam. A arquitetura da Bromure muda o custo do bug ainda não ter sido corrigido; não é substituto para corrigi-lo. Reportamos upstream quando encontramos coisas, e lemos os mesmos advisories que todos os outros.
Um escape de VM é uma categoria real
Um bug no próprio hypervisor — o framework Virtualization da Apple, ou uma das superfícies menores em torno dele —, em princípio, permitiria que um atacante alcançasse o host. Esses bugs existem. Também são várias ordens de magnitude mais raros que bugs de memória de navegador, e a superfície é dramaticamente menor. A Bromure faz a exposição do host a um comprometimento de renderer ficar condicionada a essa classe de bug, em vez da classe que aparece a cada poucas semanas.
A manchete que você lerá no mês que vem
Haverá outro bug de service worker do Chromium. Haverá outra type
confusion do V8. Haverá outro heap overflow em libwebp. Haverá
outro zero-day que um adversário extremamente bem provido de
recursos vem usando contra pessoas extremamente específicas por um
tempo extremamente longo, que se torna público em uma manhã de
terça-feira com um advisory de patch de emergência.
A pergunta útil, na manhã de qualquer um deles, não é se você já reiniciou seu navegador. A pergunta útil é o que de fato estava dentro da caixa em que o bug aterrissou. Para a maioria dos usuários, hoje, essa caixa é a mesma caixa que guarda seus arquivos e seu keychain e seus logins salvos. Para um usuário Bromure, a caixa é uma máquina virtual guest cuja perda em pior caso é a janela que ele estava prestes a fechar de qualquer jeito.
Persistência exige algo sobre o qual persistir. Torne esse algo descartável e uma classe inteira de ataque deixa de ser uma classe de ataque.