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ão | WebView | Runtime próprio do host |
|---|---|---|
| Comportamento padrão | Cada https abre dentro do WebView | Os próprios carregam dentro; o resto sai ou é bloqueado |
| Link externo | Referer, token na URL, ID de UA vazados a terceiros | Lista branca de host governa cada origem |
| Esquemas personalizados | Crash ERR_UNKNOWN_URL_SCHEME se não tratado | Roteados ao sistema com fallback seguro |
| Bridge JS | Frequentemente sem checagem de origem | Verificada por origem, privilégios mínimos |
| Tabnabbing | noopener não é forçado | Forç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
- CVE-2026-66832 — o app Mira para Android vaza o token de sessão na URL e o ID persistente no UA do WebView a terceiros (CVSS 6.5) — divulgado em 2026: material de autenticação e identificador de rastreamento transmitidos além do limite do app.
- Roubo de tokens de autenticação pela bridge WebView — uma bridge JS sem checagem de origem somada a uma política de navegação sem lista branca encadeia uma tomada de conta silenciosa — auditoria SilentGrid: um deep link, um toque, token exfiltrado.
- WebView do Android e App Links: o comportamento padrão carrega tudo internamente — App Links, tel:, mailto:, intent:// presos dentro do WebView a menos que interceptados.
- ERR_UNKNOWN_URL_SCHEME: o WebView renderiza só http/https/file/data/about; qualquer outro esquema deve ser um Intent — a correção canônica e por que o crash aparece só em login, pagamento ou compartilhamento.
- Tabnabbing reverso em WebViews móveis — a referência opener é abusável sem noopener e os WebViews podem não aplicar o padrão do navegador — como uma página aberta redireciona silenciosamente seu opener para phishing.