Uma ilustração abstrata gerada sobre um fundo azul-marinho escuro: o contorno de uma janela de navegador à esquerda envia uma fita de formas pálidas semelhantes a glifos através de uma estreita tomada brilhante até um painel de terminal à direita. Uma segunda fita, mais escura, junta-se à mesma tomada por baixo e os seus glifos chegam ao terminal indistinguíveis dos primeiros. Não aparecem textos, pessoas nem logótipos de organizações. O logótipo OrcaRouter está colocado na faixa branca do rodapé, no canto inferior direito.
AI Safety Incidents

O Tencent BrowserSkill só verificava se um ID de extensão parecia real — qualquer extensão poderia fornecer ao agente páginas forjadas

Autor

Rowan Sterling

Data de publicação

Modelos mais recentes · 20Ver todos os modelos →
Benchmarks: Artificial Analysis · atualizado diariamente
Voltar para todas as publicações

Em 17 de setembro de 2026, um usuário do GitHub com o nome carfeii abriu a issue #273 no projeto de código aberto BrowserSkill da Tencent com uma alegação específica: o daemon local que conecta a extensão do navegador a um agente de IA não verificava qual extensão se conectava, apenas se a conexão se parecia com uma extensão. Três dias depois, em 20 de setembro, a VulnCheck publicou a descoberta como CVE-2026-94111, descrevendo um daemon que "aceita qualquer origem chrome-extension com 32 caracteres no intervalo a-p", classificando-a como CWE-346 (erro de validação de origem) e creditando George Chen. O aviso atribui a ela CVSS 4.0 6,9, médio, com impacto de integridade avaliado como alto e confidencialidade e disponibilidade baixas. Nada no registro sugere que a falha tenha sido usada: o arquivo não registra exploração e classifica a confiança da entrada como A, com real_harm falso. Este é um registro em o arquivo de incidentes de IA, onde cada entrada traz suas próprias fontes, gravidade, grau de confiança e sinalizador de disputa.

Uma verificação de formato que nunca verificou a identidade

O código no centro do registro é pequeno. Em crates/bsk-cli/src/daemon/ws.rs, a função origin_allowed() recebe o cabeçalho Origin do handshake WebSocket, remove a string chrome-extension://, e retorna true se o que resta tiver exatamente 32 caracteres e cada caractere estiver entre a e p. Esse é o teste completo. Ele é executado uma vez, dentro do callback de upgrade HTTP, e uma falha retorna um 403 cuja mensagem diz "Origin not in chrome-extension allow-list" — embora, conforme publicado, não houvesse lista na qual estar.

O padrão não é arbitrário. Os IDs de extensão do Manifest V3 do Chrome têm 32 caracteres retirados exatamente dessas dezesseis letras, então a verificação reconhece corretamente o formato de uma origem de extensão real. O defeito é que toda extensão tem esse formato. A verificação pergunta se o chamador parece uma extensão de navegador, nunca se é a extensão de navegador deste projeto, e o identificador com o qual ela deveria ter comparado já era público: o README do projeto tem o link da listagem na Chrome Web Store, cujo ID é hhcmgoofomhgciiibhipgmgkgnoenaoi, e o ID do Edge Add-ons ao lado. Nenhuma das duas strings aparece em lugar algum na verificação.

O daemon escuta em uma porta local fixa, 52800, declarada como DEFAULT_WS_PORT na CLI e usada sem variação no caminho de conexão local. A porta é documentada, constante e acessível por qualquer coisa que já esteja em execução no perfil do navegador do usuário. A localização é o motivo pelo qual o vetor do aviso começa em AV:L — o atacante precisa de um ponto de apoio na máquina, e PR:L registra que um de baixo privilégio é suficiente. Uma extensão instalada no mesmo perfil é exatamente esse tipo de ponto de apoio.

Quanto vale o assento, e quanto disso já está estabelecido

A frase de impacto do aviso é ampla: um atacante que registra uma extensão maliciosa como cliente de navegador pode "interceptar e manipular conteúdo de página, DOM e capturas de tela retornadas ao agente de IA". Essa é a descrição que o CVE, o registro de aviso do GitHub GHSA-x6f2-qp26-2xrh e o arquivo trazem, e explica a classificação de integridade alta no CVSS. É também um resumo escrito pela parte que atribuiu o CVE, não um relato de um ataque demonstrado.

Um relato mais cuidadoso chegou na mesma semana, na própria thread de issues. Em 20 de setembro, um comentarista que publicou como bb6894 relatou ter reverificado o código em relação à main no commit fa953dc6 (18 de setembro) e rastreou o que um cliente externo consegue realmente alcançar após o handshake. A maioria dos métodos de ferramentas de entrada, segundo o comentário, retorna UnknownMethod para um par WebSocket; as solicitações de auditoria ficam restritas aos próprios registros do cliente; e ações entre sessões, como window_closed, user_interrupt e update_interaction, são protegidas contra sessões pertencentes a outro navegador. O que sobrevive a esse filtro é mais restrito e, para um agente, possivelmente pior do que a interceptação: uma extensão externa pode ocupar uma vaga de navegador sob um instance_id escolhido pelo atacante e, se for o único navegador registrado — em uma inicialização a frio ou após reinício do daemon —, recebe as solicitações de ferramentas do agente e responde a elas com conteúdo de página, DOM ou capturas de tela fabricados. O comentarista chama isso de "falsificação de resultados em direção ao agente" e "um canal para a percepção do agente". Um segundo efeito é uma negação de serviço: com um navegador genuíno também on-line e sem seletor, a inicialização da sessão pode falhar com MultipleBrowsersOnline.

Nenhuma das duas versões foi demonstrada publicamente. O repórter descreveu uma prova de conceito como "disponível mediante solicitação"; ninguém publicou uma, e não há registro de exploração. Portanto, a diferença entre as duas versões é uma diferença entre duas leituras do mesmo arquivo-fonte, não entre uma teoria e um exploit funcional — e o texto reporta ambas em vez de escolher. O que se pode afirmar sem ressalvas é que nunca houve uma segunda verificação de identidade após o handshake, de modo que tudo o que o gate admitiu permaneceu admitido durante toda a duração da conexão.

O código já sabia

O documento mais desconfortável neste registro não é o aviso. É o comentário diretamente acima da função, incluído na versão marcada à qual o aviso se refere. Ele marca a lacuna como TODO(M10/M12), nomeia a lista de permissões que pretende criar — um conjunto DaemonConfig::allowed_extension_ids populado a partir de um arquivo de configuração ou fluxo de emparelhamento — e então declara o motivo de ter sido adiado: "lacuna aceitável de defesa em profundidade por enquanto, porque o emparelhamento acontece pelo popup, mas uma extensão carregada lateralmente na mesma máquina atualmente passa pela verificação."

Lido sem rodeios, os mantenedores sabiam qual chamador o gate deixava passar, registraram que sabiam disso e lançaram. Isso não torna a exposição maior do que o aviso diz; torna-a menos surpreendente e desloca a pergunta de “como isso passou pela revisão” para “o que teria tornado a correção adiada urgente”. A suposição de projeto mitigadora é real e também documentada: a conexão local é opt-in. Segundo o README, o usuário instala a extensão, abre o popup dela e habilita a conexão local, e o daemon precisa estar em execução para que qualquer parte disso seja alcançável. Um perfil com a extensão instalada, mas com o toggle desligado, ou sem nenhum daemon bsk em execução, não é um alvo de escuta.

Cinco semanas depois, o portão continua inalterado.

An HTML timeline card titled 'From a filed issue to an unmerged patch', subtitled 'Nine dated entries in the BrowserSkill daemon origin-check record', listing: 16 September 2026 BrowserSkill extension 0.3.0 tagged; 17 September the bsk CLI 0.3.0 release, and issue #273 filed the same day at 15:14 UTC; 20 September CVE-2026-94111 published by VulnCheck and listed by NVD and GitHub, with a narrower impact analysis posted in the thread; 21 September CISA's SSVC assessment records exploitation none and automatable no; 23 September CLI 0.3.1 released with the same origin check; 28 September pull request #363 opened from a fork to pin the store extension IDs; 30 September CLI 0.3.2, same check; 15 October CLI 0.3.3, same check; 27 October issue still open and the patch still unmerged. A footer cites the GitHub issue, the VulnCheck advisory, NVD and the project's release tags, with the OrcaRouter logo in a white strip at the bottom right.

O intervalo afetado do registro é "até 0.3.0", e esse é o intervalo atribuído pela CNA. Não é o fim da história no repositório. A versão 0.3.0 da CLI foi marcada com tag em 17 de setembro às 02:22 UTC; a issue foi registrada no mesmo dia às 15:14 UTC, cerca de treze horas depois. Três outras versões da CLI foram lançadas desde então — 0.3.1 em 23 de setembro, 0.3.2 em 30 de setembro, e 0.3.3 em 15 de outubro — e a função origin_allowed() em cada uma dessas tags é a mesma verificação descrita acima, com o TODO ainda acima dela. O branch main também a contém em 27 de outubro.

Existe uma correção, mas apenas como proposta. Em 28 de setembro, um colaborador que publica como dangzitouabriu o pull request #363 a partir de um fork pessoal, intitulado "fix(daemon): pin WS origin gate to BrowserSkill's own extension ids". Ele substitui o teste de formato por uma comparação com os dois IDs da loja e adiciona BSK_ALLOWED_EXTENSION_IDS como um caminho de admissão explícito para builds carregados lateralmente e de desenvolvimento, cujos IDs não empacotados são derivados do caminho de instalação. O corpo do pull request afirma que ele fecha tanto o caminho de falsificação quanto a negação de serviço do MultipleBrowsersOnline. Em 27 de outubro, ele ainda está aberto e não foi mesclado, e a issue permanece aberta sem nenhuma declaração do fornecedor anexada a ela.

A consequência prática é que não há nenhuma versão para a qual atualizar que contenha a correção. Quem aconselha "atualizar para além da 0.3.0" está aconselhando um passo que ainda não existe: as versões posteriores são posteriores, não corrigidas. O intervalo do registro deve ser lido como o intervalo que o CNA avaliou em 20 de setembro, e não como uma afirmação de que a 0.3.1 e as seguintes estão limpas.

O que está estabelecido e o que não está

• Estabelecido: o código-fonte publicado na tag cli-v0.3.0 contém uma verificação de origem que aceita qualquer origem chrome-extension de 32 caracteres de a–p. O relator, o CNA e o próprio arquivo concordam, e o código é público.

• Estabelecido: o gate era executado uma vez, na atualização do WebSocket, sem verificação de identidade posterior, em uma porta local fixa 52800.

• Estabelecido: o próprio comentário no código-fonte do projeto reconhecia que uma extensão instalada por sideload passaria, e adiou a lista de permissões.

• Estabelecido: CVSS 4.0 6.9 e CVSS 3.1 6.6 são ambas pontuações da VulnCheck, registradas pelo NVD como secundárias. O status do próprio NVD para a entrada é Deferred, o que significa que ele publicou os dados da CNA sem adicionar uma análise de autoria do NVD.

• Estabelecido: A avaliação SSVC da CISA, datada de 21 de setembro, registra a exploração como nenhuma, a automatização como não e o impacto técnico como parcial. O EPSS da FIRST coloca a probabilidade de exploração nos próximos trinta dias em 0,00135, o 2,9º percentil, em 26 de outubro.

• Não estabelecido: qualquer uso no mundo real. Nenhuma exploração, nenhuma prova de conceito publicada, nenhuma contagem de instalações afetadas. As métricas do repositório do projeto não são uma contagem de instalações, e ninguém publicou uma.

• Não estabelecido: qual conta de impacto se aplica. O "interceptar e manipular" da CNA e a "falsificação de resultados mais negação de serviço" da thread descrevem tetos diferentes, e nenhum foi demonstrado fora da leitura de código.

• Não estabelecido: que a falha termina na 0.3.0. A string de versão é a avaliação da CNA; o código não foi alterado nas três versões lançadas depois dela, e nenhuma declaração do fornecedor as isenta.

• Contraditado, não resolvido: um agregador, o DevGuard, exibe um sinal de "foram detectadas tentativas de exploração" para este CVE sem nomear uma fonte, IOC ou provedor de telemetria. Isso contrasta com a avaliação registrada da CISA de ausência de exploração e com a ausência de qualquer relatório público; o arquivo também não registra exploração. Trate o sinalizador do agregador como não comprovado, e não como um segundo ponto de dados.

An HTML card titled 'Established, and not', split into two columns. The left column, headed 'Established', lists: an origin gate that accepts any 32-character a-p chrome-extension origin, in the released cli-v0.3.0 source; checked once at the WebSocket upgrade on local port 52800; the project's own comment conceded sideloaded extensions pass; CVSS 4.0 6.9 and 3.1 6.6 are VulnCheck's scores, NVD status Deferred; CISA SSVC records exploitation none and automatable no; EPSS 0.00135 as of 26 October. The right column, headed 'Not established', lists: any real-world use or published proof of concept; which impact account holds, interception or result forgery; that the flaw stops at 0.3.0, since 0.3.1 to 0.3.3 carry the same check; an installation count; an unsourced aggregator claim of exploitation attempts. A footer cites VulnCheck, NVD, CISA, FIRST and the GitHub issue, with the OrcaRouter logo in a white strip at the bottom right.

Por que o transporte é o alvo interessante

A maioria das orientações sobre injeção de prompt traça a mesma fronteira: tratar o texto de página recuperado como não confiável e tratar o mecanismo que o entrega como confiável. Presume-se que uma ferramenta que lê uma página web está lendo a página; a questão é o que a página diz. Este registro inverte isso. O que o agente recebe como conteúdo da página, DOM e capturas de tela é o que o cliente na outra ponta de um socket local decidir enviar, e o único controle nesse socket fazia uma pergunta que qualquer candidato conseguia responder corretamente. Se o cliente for externo, “verificar o que a página diz” não tem mais nada contra o que verificar.

É também por isso que a gravidade fica onde fica, e não mais alta. O ataque é local, pressupõe uma extensão já instalada no perfil ou outro código local, e exige que o daemon esteja em execução com a conexão local habilitada. Essas condições são comuns para os usuários a que a ferramenta se destina e incomuns para todos os demais. A gravidade média do arquivo e a ausência de danos registrada são consistentes com esse formato: um verdadeiro alargamento da confiança exatamente na fronteira que o design deveria policiar, sem evidência de que alguém a tenha atravessado.

O conselho prático e restrito que se segue não é uma mitigação que alguém tenha publicado, mas apenas o limite que o código deixa de pé. Como a admissão é decidida uma única vez no handshake em uma porta publicada, a superfície alcançável é definida por quais extensões estão instaladas no perfil que executa o daemon e por se esse daemon e a conexão local da extensão estão ativados no momento. Até que uma lista de permissões de IDs de extensão seja disponibilizada, esses dois fatos são todo o controle.

A generated abstract illustration on a dark navy field: two identical hexagonal tokens of the same size and colour approach a narrow blue aperture from opposite sides; both pass through, and beyond it their outputs merge into a single stream of pale glyph shapes entering a terminal panel. A faint dotted outline of a browser window sits above. No text, people or organisation logos appear. The OrcaRouter logo is set in the white footer strip at the bottom right.

A CVE-2026-94111 envelhecerá de uma de duas maneiras. Ou o pull request é mesclado e o registro se torna uma entrada curta sobre uma lacuna conhecida que levou um tempo para ser corrigida, ou a mesma verificação de forma ainda estará na árvore daqui a um ano e a entrada será citada como um exemplo inicial da classe à qual pertence: não uma página que carrega uma instrução hostil, mas o pipe que decide qual página o agente está lendo.