Dentro de un WebView, el transporte seguro de la página depende de lo que configure la app contenedora, y los valores por defecto son hostiles. Android bloquea el contenido mixto desde Pie pero no incluye anclaje de certificados, y onReceivedSslError cancela en silencio; innumerables apps "arreglan" la pantalla en blanco con handler.proceed() y confían en cualquier certificado, lo que Google Play rechaza y un atacante de intermediario aprovecha. iOS WKWebView se apoya en App Transport Security pero no expone una API limpia de anclaje por petición. Un runtime propio del host impone una política única: exigir HTTPS, anclar certificados, fallar cerrado y mostrar un error real y explicable en lugar de una pantalla muerta.
Android: contenido mixto bloqueado, pero los certificados son tu problema
Desde Android 9 (Pie, API 28) WebView bloquea el contenido mixto por defecto: una página HTTPS no puede cargar scripts, imágenes o iframes por HTTP. Antes, lo permitía. Para relajar la regla llamas a setMixedContentMode, pero cualquier modo más permisivo (COMPATIBILITY o ALWAYS_ALLOW) debilita la garantía y se desaconseja en producción.
La trampa mayor son los certificados. Cuando el WebView encuentra un certificado no confiable, expirado o con nombre incorrecto, se dispara WebViewClient.onReceivedSslError y la acción por defecto es cancelar la carga: una pantalla en blanco silenciosa. El "arreglo" que los desarrolladores copian de Stack Overflow es handler.proceed(), que acepta cualquier certificado y desactiva toda la validación. Google Play rechaza activamente las apps que ignoran los errores SSL así, y regala a un atacante de red una posición de hombre-en-el-medio. WebView tampoco incluye anclaje de certificados: fijar una clave pública conocida requiere construir a mano una capa de red, y casi nadie lo hace.
iOS: ATS bloquea el texto plano, pero la ruta de certificados es tosca
WKWebView se beneficia de App Transport Security, que bloquea el HTTP en texto plano por defecto y solo permite excepciones mediante entradas explícitas en Info.plist. Pero el manejo de certificados es tosco: los errores aparecen como un reto de autenticación genérico (didReceiveAuthenticationChallenge con un espacio de protección server-trust), sin API de anclaje por petición y sin un flujo integrado para "mostrar al usuario qué salió mal". El atajo habitual del desarrollador es aceptar el server trust incondicionalmente: válido en un dispositivo de prueba, riesgo de rechazo y agujero de seguridad en producción. Las versiones antiguas de iOS ni siquiera tenían un gancho de error SSL en WKWebView, así que un certificado malo simplemente fallaba.
Qué pierdes sin una política de transporte unificada
- El contenido mixto falla en silencio. Scripts e imágenes bloqueados por un valor por defecto silencioso dejan una página rota sin razón visible.
- Confianza ciega en certificados. proceed() / confianza incondicional convierte cualquier Wi-Fi en un posible punto de intercepción.
- Sin anclaje. Una CA comprometida o mal emitida puede falsificar un certificado "válido" para tu dominio y el WebView lo acepta.
- Fallos silenciosos. La ruta de error de certificado por defecto es una pantalla en blanco, no un error que el usuario o la app puedan manejar.
- Sin consistencia. Android e iOS divergen en valores por defecto, APIs y modos de fallo, así que la misma página se comporta distinto por plataforma.
La solución del runtime
Un runtime propio del host impone una política de transporte única bajo la página. Exige HTTPS y HSTS, ancla la clave pública del servidor para que una CA maliciosa no pueda suplantarlo, falla cerrado ante cualquier error de validación de certificado y, en lugar de una pantalla en blanco, muestra un error real y explicable con una anulación segura y claramente etiquetada. La página obtiene un contrato de transporte seguro único y predecible en todas las plataformas, y el host puede demostrar que la conexión estuvo realmente protegida.
| Dimensión | WebView | Runtime propio del host |
|---|---|---|
| Contenido mixto | Bloqueado por defecto desde Pie; relajar lo debilita | Exigido, sin degradación silenciosa |
| Error de cert. por defecto | Cancelación silenciosa / pantalla en blanco | Fallo cerrado, error explicable |
| Anclaje de cert. | Ninguno integrado, hay que hacerlo a mano | Exigido por origen |
| Ruta de cert. en iOS | Reto server-trust tosco | Política unificada, misma superficie de API |
| Exposición MITM | Alta si se usa proceed() | Cerrada por anclaje + validación estricta |
| Multiplataforma | Valores y APIs divergentes | Un contrato en todas partes |
Un navegador gana confianza haciendo que el camino seguro sea el predeterminado y el inseguro sea visible. Un WebView de fábrica convierte el camino inseguro en el atajo tentador y el fallo en una pantalla en blanco. Un runtime restaura el contrato: HTTPS exigido, certificados anclados, errores explicados y sin degradación silenciosa que el usuario no aceptó.
Fuentes
- Android Developers — WebSettings.setMixedContentMode: contenido mixto bloqueado por defecto desde API 28, modos permisivos desaconsejados — el valor por defecto silencioso.
- Android Developers — WebViewClient.onReceivedSslError: cancela por defecto; Google Play rechaza handler.proceed() — la trampa de la pantalla en blanco y su "arreglo" peligroso.
- Apple Developer — App Transport Security: HTTP en texto plano bloqueado por defecto — la base en iOS.
- Apple Developer — WKNavigationDelegate webView(_:didReceive:completionHandler:): reto server-trust, sin anclaje por petición — la ruta de certificados tosca en iOS.
- web.dev — Web on Android: WebView delega el comportamiento sensible de seguridad a la app contenedora — por qué la seguridad de transporte está del lado de la app, no del web.