La política de navegación por defecto de un WebView está mal en ambas direcciones. Si la dejas, cada enlace —incluida la página de pago del banco y el clon de phishing— se abre dentro de tu propio WebView, filtrando tu Referer, tu cookie de sesión y, en CVE reales ya divulgados, tu token de autenticación directamente en la barra de URL. Si la tocas, envías crashes ERR_UNKNOWN_URL_SCHEME la primera vez que un usuario toca un número de teléfono. Y como por defecto no hay lista blanca de host, una sola llamada de puente sin protección se convierte en una toma de cuenta silenciosa. Un runtime propio del host decide, por enlace y por origen, qué sale y qué se queda.

El comportamiento por defecto es «ábreme todo aquí»

Por defecto, un WebView carga internamente cada destino https. Un toque en la página de la tienda de un socio, una redirección bancaria o incluso una noticia externa se renderiza dentro de la propia interfaz de tu app. Es conveniente hasta que se vuelve un pasivo: el sitio de destino ahora recibe tu cabecera Referer, cualquier token que hayas añadido a la URL y el identificador persistente que tu WebView envió en su User-Agent. En 2026 se divulgó la app Mira para Android (CVE-2026-66832, CVSS 6.5) precisamente por esto: añadía el token de sesión en vivo del usuario a la URL como parámetro de consulta y ponía un ID de usuario estable en el User-Agent del WebView, transmitiendo ambos a propiedades de terceros, registros de Referer y cualquier script en el WebView. Eso es toma de cuenta, no una hipótesis.

Si lo arreglas mal, envías crashes

La reacción habitual es sobreescribir shouldOverrideUrlLoading y enrutar los esquemas no http al sistema. Si inviertes el booleano no pasa nada; si omites tel:, mailto:, sms:, geo: o intent://, el usuario ve ERR_UNKNOWN_URL_SCHEME — el WebView simplemente no puede renderizar un esquema que no entiende. En Android 7+ (API 24) la nueva firma es obligatoria y un valor de retorno incorrecto lanza ActivityNotFoundException y crashea. Android 11+ añade reglas de visibilidad de paquetes, así que un deep link a otra app falla en silencio salvo que se declare. La solución es real pero delicada, y vive en código nativo que el equipo web no posee.

Sin lista blanca, el puente es una puerta abierta

El fallo más profundo es estructural. La política de navegación y el puente JS son las dos guardas entre el contenido web y el poder nativo, y en una auditoría publicada (SilentGrid) ambas faltaban: el puente devolvía el token de autenticación en vivo a cualquier JavaScript de la página, y el decidePolicyFor del WKWebView permitía cualquier origen https sin comprobación de host. Un solo deep link construido bastó para cargar una página atacante, llamar al manejador de tokens y exfiltrar la sesión — en un solo toque, sin que la víctima viera nada. El tabnabbing inverso lo agrava: una página abierta sin rel="noopener" puede redirigir en silencio tu opener a un clon de phishing, y los WebView no aplican el valor por defecto del navegador.

La solución del runtime

Un runtime propio del host hace que el límite de navegación sea explícito y obligatorio. Mantiene una lista blanca de host: los dominios propios se cargan en el runtime, los esquemas de app conocidos (tel:, mailto:, market:) se enrutan al sistema con respuestas seguras, y todo lo demás se abre en un navegador externo o se bloquea. El puente JS se verifica por origen y con mínimos privilegios, para que una página de un catálogo que no se confía no alcance un manejador de credenciales. El enlace que debe salir, sale; el token que debe quedarse, se queda.

DimensiónWebViewRuntime propio del host
Comportamiento por defectoCada https se abre dentro del WebViewLos propios cargan dentro; el resto sale o se bloquea
Enlace externoReferer, token en URL, ID de UA filtrados a tercerosLista blanca de host gobierna cada origen
Esquemas personalizadosCrash ERR_UNKNOWN_URL_SCHEME si no se manejaEnrutados al sistema con respuesta segura
Puente JSA menudo sin verificación de origenVerificado por origen, mínimos privilegios
Tabnabbingnoopener no se fuerzaForzado en cada nueva ventana

El hilo conductor de esta serie es el control. Un WebView entrega a tu límite de navegación un valor por defecto inseguro en ambas direcciones y un puente que confía en quien lo llame. Un runtime convierte el límite en una política que tú escribes — por enlace, por origen, por entrada de catálogo. Para un catálogo de páginas ajenas que transan dentro de tu app, esa es la diferencia entre un escaparate controlado y una puerta abierta.

Fuentes