A política de navegação padrão de um WebView está errada nas duas direções. Deixe como está e todo link — incluindo a página de pagamento do banco e o clone de phishing — abre dentro do seu próprio WebView, vazando seu Referer, seu cookie de sessão e, em CVEs reais já divulgados, seu token de autenticação direto na barra de URL. Mexa nela e você envia crashes ERR_UNKNOWN_URL_SCHEME na primeira vez que o usuário toca um número de telefone. E como não há lista branca de host por padrão, uma única chamada de bridge sem proteção vira uma tomada de conta silenciosa. Um runtime próprio do host decide, por link e por origem, o que sai e o que fica.

O padrão é «abra tudo aqui dentro»

Por padrão, um WebView carrega internamente todo destino https. Um toque na página da loja de um parceiro, um redirecionamento bancário ou até uma notícia externa é renderizado dentro da própria interface do seu app. É conveniente até virar um passivo: o site de destino agora recebe seu cabeçalho Referer, qualquer token que você acrescentou à URL e o identificador persistente que seu WebView enviou no User-Agent. Em 2026 o app Mira para Android foi divulgado (CVE-2026-66832, CVSS 6.5) exatamente por isso: anexava o token de sessão ativo do usuário à URL como parâmetro de consulta e colocava um ID de usuário estável no User-Agent do WebView, transmitindo ambos a propriedades de terceiros, logs de Referer e qualquer script no WebView. Isso é tomada de conta, não uma hipótese.

Se você conserta mal, envia crashes

A reação comum é sobrescrever shouldOverrideUrlLoading e rotear os esquemas não http para o sistema. Inverte o booleano e nada acontece; esquece tel:, mailto:, sms:, geo: ou intent:// e o usuário vê ERR_UNKNOWN_URL_SCHEME — o WebView simplesmente não consegue renderizar um esquema que não entende. No Android 7+ (API 24) a nova assinatura é obrigatória e um valor de retorno errado lança ActivityNotFoundException e crashea. O Android 11+ acrescenta regras de visibilidade de pacotes, então um deep link para outro app falha silenciosamente se não for declarado. A correção é real, mas delicada, e vive em código nativo que a equipe web não possui.

Sem lista branca, a bridge é uma porta aberta

A falha mais profunda é estrutural. A política de navegação e a bridge JS são as duas guardas entre o conteúdo web e o poder nativo, e em uma auditoria publicada (SilentGrid) ambas faltavam: a bridge devolvia o token de autenticação ativo a qualquer JavaScript da página, e o decidePolicyFor do WKWebView permitia qualquer origem https sem checagem de host. Um único deep link construído bastou para carregar uma página atacante, chamar o manipulador de tokens e exfiltrar a sessão — em um único toque, sem a vítima notar. O tabnabbing reverso agrava: uma página aberta sem rel="noopener" pode redirecionar silenciosamente seu opener para um clone de phishing, e os WebViews não aplicam o padrão protetor do navegador.

A solução do runtime

Um runtime próprio do host torna o limite de navegação explícito e obrigatório. Ele mantém uma lista branca de host: domínios próprios carregam no runtime, esquemas de app conhecidos (tel:, mailto:, market:) são roteados ao sistema com fallback seguro, e todo o resto ou abre em um navegador externo ou é bloqueado. A bridge JS é verificada por origem e com privilégios mínimos, para que uma página de uma entrada não confiável do catálogo não alcance um manipulador de credenciais. O link que deve sair, sai; o token que deve ficar, fica.

DimensãoWebViewRuntime próprio do host
Comportamento padrãoCada https abre dentro do WebViewOs próprios carregam dentro; o resto sai ou é bloqueado
Link externoReferer, token na URL, ID de UA vazados a terceirosLista branca de host governa cada origem
Esquemas personalizadosCrash ERR_UNKNOWN_URL_SCHEME se não tratadoRoteados ao sistema com fallback seguro
Bridge JSFrequentemente sem checagem de origemVerificada por origem, privilégios mínimos
Tabnabbingnoopener não é forçadoForçado em toda nova janela

O fio condutor desta série é o controle. Um WebView entrega ao seu limite de navegação um padrão inseguro nas duas direções e uma bridge que confia em quem a chama. Um runtime transforma o limite em uma política que você escreve — por link, por origem, por entrada de catálogo. Para um catálogo de páginas alheias que transacionam dentro do seu app, essa é a diferença entre uma vitrine controlada e uma porta aberta.

Fontes