Dentro de um WebView, o transporte seguro da página é o que a app hospedeira configurar, e os padrões são hostis. O Android bloqueia conteúdo misto desde o Pie, mas não traz fixação de certificado, e o onReceivedSslError cancela em silêncio; inúmeras apps "consertam" a tela branca com handler.proceed() e confiam em qualquer certificado, o que o Google Play rejeita e um atacante intermediário aproveita. O iOS WKWebView conta com o App Transport Security, mas não expõe uma API limpa de fixação por requisição. Um runtime próprio do host impõe uma política única: exigir HTTPS, fixar certificados, falhar fechado e mostrar um erro real e explicável em vez de uma tela morta.
Android: conteúdo misto bloqueado, mas os certificados são o seu problema
Desde o Android 9 (Pie, API 28), o WebView bloqueia conteúdo misto por padrão: uma página HTTPS não pode puxar scripts, imagens ou iframes via HTTP. Antes, permitia. Para relaxar a regra você chama setMixedContentMode, mas qualquer modo mais permissivo (COMPATIBILITY ou ALWAYS_ALLOW) enfraquece a garantia e é desencorajado em produção.
A armadilha maior são os certificados. Quando o WebView encontra um certificado não confiável, expirado ou com nome errado, dispara WebViewClient.onReceivedSslError e a ação padrão é cancelar o carregamento: uma tela branca silenciosa. O "conserto" que os desenvolvedores copiam do Stack Overflow é handler.proceed(), que aceita qualquer certificado e desativa toda a validação. O Google Play rejeita ativamente apps que ignoram erros SSL assim, e entrega a um atacante de rede uma posição de homem-no-meio. O WebView também não traz fixação de certificado embutida: fixar uma chave pública conhecida exige montar à mão uma camada de rede, e quase ninguém faz.
iOS: o ATS bloqueia texto puro, mas o caminho do certificado é grosseiro
O WKWebView se beneficia do App Transport Security, que bloqueia HTTP em texto puro por padrão e só permite exceções via entradas explícitas no Info.plist. Mas o tratamento de certificados é grosseiro: os erros aparecem como um desafio de autenticação genérico (didReceiveAuthenticationChallenge com um espaço de proteção server-trust), sem API de fixação por requisição e sem um fluxo integrado para "mostrar ao usuário o que deu errado". O atalho comum do desenvolvedor é aceitar o server trust incondicionalmente: aceitável num dispositivo de teste, risco de rejeição e buraco de segurança em produção. Versões antigas do iOS nem tinham gancho de erro SSL no WKWebView, então um certificado ruim simplesmente falhava.
O que se perde sem uma política de transporte unificada
- O conteúdo misto falha em silêncio. Scripts e imagens bloqueados por um padrão silencioso deixam a página quebrada sem motivo visível.
- Confiança cega em certificados. proceed() / confiança incondicional transforma qualquer Wi-Fi num possível ponto de interceptação.
- Sem fixação. Uma CA comprometida ou mal emitida pode forjar um certificado "válido" para o seu domínio e o WebView aceita.
- Falhas silenciosas. O caminho de erro de certificado padrão é uma tela branca, não um erro que o usuário ou a app possam tratar.
- Sem consistência. Android e iOS divergem em padrões, APIs e modos de falha, então a mesma página se comporta diferente por plataforma.
A solução do runtime
Um runtime próprio do host impõe uma política de transporte única abaixo da página. Exige HTTPS e HSTS, fixa a chave pública do servidor para que uma CA maliciosa não possa se passar por ele, falha fechado diante de qualquer erro de validação de certificado e, em vez de uma tela branca, mostra um erro real e explicável com uma substituição segura e claramente rotulada. A página obtém um contrato de transporte seguro único e previsível em todas as plataformas, e o host pode provar que a conexão esteve realmente protegida.
| Dimensão | WebView | Runtime próprio do host |
|---|---|---|
| Conteúdo misto | Bloqueado por padrão desde o Pie; relaxar enfraquece | Exigido, sem degradação silenciosa |
| Erro de cert. padrão | Cancelamento silencioso / tela branca | Falha fechada, erro explicável |
| Fixação de cert. | Nenhuma embutida, precisa fazer à mão | Exigida por origem |
| Caminho de cert. no iOS | Desafio server-trust grosseiro | Política unificada, mesma superfície de API |
| Exposição MITM | Alta se usar proceed() | Fechada por fixação + validação estrita |
| Multiplataforma | Padrões e APIs divergentes | Um contrato em todo lugar |
Um navegador conquista confiança tornando o caminho seguro o padrão e o inseguro visível. Um WebView de fábrica transforma o caminho inseguro no atalho tentador e a falha numa tela branca. Um runtime restaura o contrato: HTTPS exigido, certificados fixados, erros explicados e sem degradação silenciosa que o usuário nunca aceitou.
Fontes
- Android Developers — WebSettings.setMixedContentMode: conteúdo misto bloqueado por padrão desde API 28, modos permissivos desencorajados — o padrão silencioso.
- Android Developers — WebViewClient.onReceivedSslError: cancela por padrão; Google Play rejeita handler.proceed() — a armadilha da tela branca e seu "conserto" perigoso.
- Apple Developer — App Transport Security: HTTP em texto puro bloqueado por padrão — a base no iOS.
- Apple Developer — WKNavigationDelegate webView(_:didReceive:completionHandler:): desafio server-trust, sem fixação por requisição — o caminho de certificados grosseiro no iOS.
- web.dev — Web on Android: WebView delega o comportamento sensível de segurança à app hospedeira — por que a segurança de transporte está do lado da app, não do web.