CAPÍTULO 57 · PARTE V

Navegadores, DOM, orígenes y modelo de seguridad web

Reconstruye la autoridad del navegador a partir de documentos, DOM, orígenes y tipos de operación; separa SOP, Fetch/CORS, cookies, mensajería, sandbox y CSP de la autorización del servidor.

Nivel N2 · Estado published

Abrir una página parece una operación única. En realidad, el navegador recibe bytes, interpreta HTML, construye un Document, mantiene un árbol DOM mutable, ejecuta código, solicita recursos, conserva estado y media entre autoridades que no tienen por qué confiar entre sí. Su modelo de seguridad no consiste en una muralla llamada «same-origin policy». Es un conjunto de decisiones distintas sobre qué puede leerse, enviarse, incrustarse, ejecutarse, almacenarse o comunicarse.

El capítulo 56 separó cliente, servidor, intermediario, autenticación y autorización. Aquí el navegador ocupa el rol de cliente en algunos saltos, pero también aplica políticas locales entre documentos y scripts. Es crucial no mezclar ambos niveles: que el navegador permita enviar un request no autoriza el efecto en el servidor; que oculte una respuesta al script no demuestra que el servidor no la procesó. Para razonar con rigor hay que nombrar el objeto, el origen, la operación, las credenciales y el punto que decide.

Seis entidades que no deben confundirse

El HTML recibido es una secuencia de bytes interpretada bajo una codificación y un parser. El Document es un objeto de la plataforma asociado a una navegación. El DOM es el modelo de nodos, relaciones y eventos que el código puede consultar y modificar. La serialización original y el DOM vivo pueden diferir: el parser corrige estructuras, los scripts añaden o eliminan nodos y el navegador mantiene estados que no aparecen como texto HTML.

Un Window expone el entorno global de un documento. Un contexto de navegación contiene una secuencia de documentos a través de navegaciones y puede ser superior, un iframe o una ventana auxiliar. Un origen es una identidad de seguridad calculada según reglas de la plataforma. Por último, un proceso es una decisión de implementación del navegador. No debe inferirse que una pestaña equivale a un proceso, que cada origen siempre obtiene uno o que compartir proceso implica compartir autoridad.

Esta separación explica una observación aparentemente extraña. Un documento puede navegar en el mismo marco hacia otro origen: el contexto continúa, pero cambia el documento activo y su autoridad. Dos documentos same-origin pueden acceder a objetos permitidos aunque el navegador los ejecute en procesos distintos. Dos documentos cross-origin pueden estar visualmente uno dentro de otro y, aun así, no disponer de acceso DOM general.

El árbol DOM es una representación activa, no una garantía de confianza. Un nodo creado por código tiene la misma capacidad de afectar la página que uno derivado del HTML inicial cuando se inserta en el mismo contexto. Por eso «vino del servidor» y «está en el DOM» no prueban que un valor sea seguro para un sink. Texto, atributos, URL, CSS y JavaScript tienen gramáticas diferentes; la codificación correcta depende del contexto de interpretación.

Origen: esquema, host y puerto

En el caso habitual, el origen es una tupla formada por esquema, host y puerto. Las rutas, parámetros y fragmentos no forman parte de esa comparación. https://app.example/a y https://app.example/b?x=1 son same-origin. En cambio, cualquiera de estos cambios produce otro origen:

Desliza horizontalmente para consultar todas las columnas.

A B Relación
https://app.example https://app.example:443 mismo origen si el puerto se normaliza al predeterminado
https://app.example http://app.example distinto esquema, distinto origen
https://app.example https://api.example distinto host, distinto origen
https://app.example:443 https://app.example:8443 distinto puerto, distinto origen
https://app.example/a https://app.example/b mismo origen

Un site responde a otra pregunta. La plataforma lo deriva alrededor del esquema y el dominio registrable, y algunas políticas —en particular SameSite para cookies— se expresan con esa relación. Dos subdominios pueden ser same-site y cross-origin a la vez. https://shop.example.com y https://admin.example.com comparten site en el caso ordinario, pero no host y, por tanto, no origen. Tratar site y origin como sinónimos conduce a errores de cookies, mensajes y acceso DOM.

También existen orígenes opacos. No son una tupla que pueda reconstruirse a partir de la serialización null; son valores internos cuya igualdad sólo tiene sentido respecto del mismo valor. Algunos documentos sandboxed y ciertas URL pueden obtenerlos. Ver Origin: null no identifica un principal universal llamado «null» ni justifica añadirlo sin contexto a una allowlist.

document.domain permitió históricamente que documentos de subdominios redujeran su dominio efectivo para accederse. La especificación advierte que debilita la política y está en retirada. Una arquitectura nueva debe usar mensajería explícita con contrato, no rebajar la identidad de seguridad de documentos completos.

Figura 57-01 · ¿Qué código y documentos comparten autoridad dentro de una página?

Vista adaptada. Toca el diagrama para ampliarlo.

Mapa de autoridad: documento A e iframe A conectados por DOM; iframe B separado por frontera cross-origin; CDN entrega un script que entra al contexto del documento A, no a un sandbox propio.

Same-origin policy: una familia de restricciones

«La SOP bloquea lo cross-origin» es demasiado impreciso para predecir comportamiento. La plataforma distingue al menos cuatro clases de operación:

  1. Lectura o manipulación. Acceder al DOM, propiedades sensibles de una ventana o cuerpo de una respuesta suele estar restringido entre orígenes.
  2. Envío. Formularios, imágenes, navegaciones y algunos requests pueden alcanzar otro origen bajo reglas específicas. Que el código no pueda leer la respuesta no implica que el request no salió.
  3. Incrustación. Imágenes, hojas de estilo, scripts o marcos pueden cargarse cross-origin con políticas que dependen del tipo de recurso, respuesta y documento.
  4. Navegación y comunicación. Una página puede iniciar ciertas navegaciones o enviar mensajes explícitos; eso no le concede acceso DOM general al destino.

La pregunta defendible no es «¿lo permite SOP?», sino «¿qué operación intenta qué actor sobre qué objeto y qué algoritmo la gobierna?». Una imagen cross-origin puede mostrarse sin que el script obtenga sus píxeles libremente. Un formulario puede provocar un POST cross-site aunque el emisor no lea la respuesta. Un iframe puede renderizar un documento y seguir aislado del DOM del padre. Un script externo, en cambio, no se ejecuta como un visitante aislado: si el documento lo admite, el código actúa en el contexto del documento.

Esta última propiedad cambia el análisis de supply chain. Cargar https://cdn.example/lib.js no concede a la librería únicamente la autoridad de cdn.example. El script puede operar sobre el DOM y las API disponibles al documento que lo incorporó. Por tanto, la decisión de carga es una decisión de confianza sobre código. CSP puede restringir fuentes, Subresource Integrity puede fijar bytes en casos compatibles y un pipeline de dependencias puede controlar procedencia; ninguno debe representarse como un sandbox automático del script aceptado.

Fetch: request, modo, credenciales y respuesta

Fetch modela un request con URL, método, headers, destino, modo, modo de credenciales, origen y otras propiedades. El navegador deriva parte de ese estado de la API y del contexto. Para analizar un caso no basta mirar la llamada fetch(url): hay que observar el origen del cliente, la URL efectiva tras redirecciones, mode, credentials, método, headers y política de respuesta.

En modo same-origin, un intento cross-origin falla. En modo cors, el navegador aplica el protocolo CORS para decidir si la respuesta puede exponerse al solicitante. En modo no-cors, la plataforma limita métodos y headers disponibles al autor y produce una respuesta opaca para script; no es una vía para «saltarse CORS» y leer el contenido. Una respuesta opaca oculta estado, cabeceras y cuerpo al código aunque pueda representar una transferencia de red real.

Las credenciales incluyen cookies y otros mecanismos gestionados por el user agent según el contexto. El modo de credenciales influye en si se envían y si se respetan respuestas Set-Cookie. Pero el servidor no debe deducir autorización sólo de que llegó una cookie. Debe validar sesión, propósito, estado, acción y recurso. Tampoco debe confiar en que una política de navegador sea aplicable a clientes no navegador.

Qué hace CORS y qué no hace

CORS es un protocolo de headers mediante el cual una respuesta indica si puede compartirse cross-origin. Cuando el request supera el perfil que HTML ya puede emitir de ciertas formas, el navegador realiza un preflight OPTIONS con el método y los nombres de headers previstos. Si la respuesta al preflight satisface las comprobaciones, puede emitir el request real.

Hay cuatro límites esenciales:

El header Access-Control-Allow-Origin no es una lista de usuarios autorizados. Es una condición de exposición en el navegador. Si el endpoint devuelve datos privados a cualquier request autenticado por una cookie ambiental, debe además verificar quién actúa y si la operación fue intencional. Si refleja Origin indiscriminadamente y permite credenciales, expande qué documentos pueden leer respuestas; si usa *, las reglas de credenciales imponen restricciones distintas. El análisis debe usar la combinación real, no una regla memorizada fuera de contexto.

Figura 57-02 · ¿Qué decide el navegador y qué debe decidir siempre el servidor en un fetch cross-origin?

Vista adaptada. Toca el diagrama para ampliarlo.

Flujo A a B con preflight opcional, request real, autorización del servidor y bifurcación final del navegador entre respuesta expuesta y respuesta bloqueada para el script.

La secuencia completa

Supongamos que un script en https://app.example solicita https://api.example/profile:

  1. El navegador determina que los orígenes son distintos aunque puedan ser same-site.
  2. Construye el request con su modo y política de credenciales.
  3. Si método y headers lo requieren, envía un preflight sin credenciales de la solicitud real.
  4. El servidor decide cómo responder al preflight según su política CORS. Esa respuesta no concede acceso al recurso como principal.
  5. El request real llega al servidor si el flujo continúa. El servidor autentica y autoriza la acción.
  6. El navegador comprueba los headers CORS de la respuesta y decide si el script puede observarla.

El orden muestra por qué 200 en el servidor y «CORS error» en consola pueden coexistir. El servidor pudo completar la operación y el navegador negar la exposición. También explica por qué una herramienta de línea de comandos no «sufre CORS»: la política pertenece al user agent que aplica Fetch, no es un control de acceso universal del endpoint.

Cookies: credenciales ambientales con varios alcances

Una cookie se almacena con metadatos y el navegador decide cuándo adjuntarla. Sus atributos resuelven dimensiones distintas:

Las cookies son ambient authority cuando el navegador las adjunta por el destino y contexto sin que el script tenga que presentar explícitamente una credencial por operación. Esa comodidad crea la necesidad de controles contra requests inducidos. SameSite reduce clases de CSRF, pero no sustituye tokens vinculados a sesión/intención cuando el modelo los requiere, verificación de Origin o Fetch Metadata en perfiles apropiados, reautenticación para acciones sensibles y autorización server-side.

RFC 6265 documenta además una diferencia importante respecto de origen: las cookies de un host se comparten entre puertos. Por tanto, «puerto diferente» separa orígenes, pero no crea necesariamente un almacén de cookies independiente. Diseñar aislamiento entre aplicaciones sólo mediante puertos contradice el modelo real.

Mensajería cross-origin como protocolo explícito

window.postMessage() permite comunicar documentos que no comparten origen. No abre el DOM del receptor; entrega un evento con datos serializados, origen y referencia al emisor bajo reglas de la API. La seguridad depende del protocolo que construya la aplicación.

El emisor debe usar un targetOrigin exacto cuando conoce al receptor. * hace posible entregar información a un documento distinto si la ventana navega o si la referencia apunta a un actor inesperado. El receptor debe verificar event.origin contra un conjunto exacto y normalizado. Esa comprobación no basta por sí sola: cuando la arquitectura conoce la ventana esperada, también debe comprobar event.source.

Después hay que validar event.data como entrada no confiable: tipo de mensaje, versión, campos permitidos, límites de tamaño y semántica. Por último, el receptor autoriza la acción en su propio contexto. Que un mensaje provenga de un origen admitido no demuestra que cada página, usuario o estado de ese origen pueda ordenar deleteAccount, leer un token o cambiar el tenant.

Figura 57-03 · ¿Qué comprobaciones convierten postMessage en un canal explícito y defendible?

Vista adaptada. Toca el diagrama para ampliarlo.

Canal postMessage con targetOrigin exacto y una secuencia de cuatro comprobaciones del receptor; todas deben aprobar para ejecutar una acción, cualquier no termina en rechazo.

Un contrato robusto puede usar mensajes como {version: 1, type: "select-account", accountId: "…"} y una tabla cerrada de handlers. Evita evaluar strings como código, concatenar HTML o despachar dinámicamente cualquier nombre de método. Las respuestas incluyen un identificador de correlación, pero éste no es una credencial. Si el receptor navega o se reemplaza, el emisor debe volver a establecer el canal y no asumir que la referencia conserva identidad de aplicación.

Iframes, sandbox y ventanas auxiliares

Un iframe crea un contexto de navegación anidado. La diferencia de origen limita acceso, pero el documento embebido todavía puede ejecutar código, hacer red, mostrar interfaces o iniciar acciones permitidas. El atributo sandbox añade un conjunto de restricciones y sólo devuelve capacidades mediante tokens explícitos. Debe diseñarse desde la ausencia de privilegios, no agregarse como decoración a una integración que ya presupone poder total.

Las combinaciones importan. Conceder simultáneamente allow-scripts y allow-same-origin a contenido same-origin puede permitir que el contenido recupere suficiente autoridad para neutralizar el sandbox en ciertos escenarios. allow-forms, allow-popups, navegación superior y descargas son capacidades distintas. La lista correcta depende del comportamiento mínimo requerido y debe probarse con contenido hostil, no sólo con el caso feliz.

Una ventana abierta puede conservar relación con opener. Esa relación ha sido fuente de navegación o interferencia inesperada. noopener y políticas como Cross-Origin-Opener-Policy permiten separar grupos o relaciones según el caso, pero no deben presentarse como un permiso general. Al igual que con procesos, COOP y COEP controlan propiedades concretas de aislamiento; no autentican usuarios ni corrigen sinks DOM inseguros.

El control de quién puede embeber una página pertenece al documento embebido: CSP frame-ancestors expresa orígenes permitidos para ancestros. Esto es diferente de frame-src, que controla qué puede incorporar el documento actual. Confundir dirección de la relación deja una política visualmente plausible pero técnicamente inútil.

CSP: reducir capacidad tras una inyección

Content Security Policy permite restringir fuentes y clases de ejecución, conexiones, marcos, objetos y otras capacidades. Una política basada en nonces o hashes puede dificultar que markup inyectado ejecute scripts arbitrarios. object-src 'none', base-uri 'none' y directivas específicas cierran rutas distintas. El modo Report-Only ayuda a observar impacto antes de aplicar, aunque los reportes pueden contener datos sensibles y no prueban ausencia de violaciones.

CSP es defensa en profundidad. Si la aplicación inserta datos no confiables en innerHTML, construye URL peligrosas o entrega un nonce a markup manipulable, debe corregir la causa. Añadir dominios amplios, unsafe-inline o esquemas genéricos puede conservar compatibilidad a costa de vaciar el control. Tampoco basta admitir un CDN: si allí se publica código comprometido, la política autorizó su ejecución.

La evaluación empieza por enumerar sinks y recursos realmente necesarios. Después se formula una política mínima, se prueba en una superficie representativa, se atienden rutas de carga dinámica y se despliega con telemetría. Una CSP estricta que rompe una ruta crítica y se retira sin alternativa no es un control operativo. Una política permisiva que «pasa el scanner» tampoco.

Contextos seguros y HTTPS

Un contexto seguro permite que ciertas API potentes estén disponibles sólo cuando la cadena de entrega se considera potencialmente confiable. Un documento HTTPS embebido por un ancestro inseguro puede no cumplir la condición; workers y service workers tienen reglas asociadas a su propietario y contexto. El concepto captura propiedades de transporte y ancestros, no una calificación absoluta de la aplicación.

HTTPS protege confidencialidad e integridad del canal y autentica el endpoint conforme a TLS. No demuestra que el JavaScript sea correcto, que una extensión no intervenga, que el usuario pretendiera la acción ni que el servidor autorice el recurso. El icono de candado no es una auditoría. Del mismo modo, una API disponible sólo en secure contexts puede usarse inseguramente por código autorizado en un documento vulnerable.

Caso trabajado: selector de cuenta embebido

https://portal.example incorpora un selector servido por https://accounts.example dentro de un iframe. Ambos son same-site en el caso ordinario, pero cross-origin. El padre no debe leer el DOM interno; el hijo no debe recibir autoridad sobre el DOM del padre. Se comunican mediante postMessage.

El portal crea el iframe con una URL fija, una CSP que limita frame-src y un sandbox con las capacidades mínimas que el selector necesita. Conserva iframe.contentWindow como fuente esperada. Cuando el hijo está listo, envía {version: 1, type: "ready", nonce} al origen exacto del portal. El padre acepta sólo si event.origin es https://accounts.example, event.source coincide con el iframe y el esquema es válido.

Para seleccionar una cuenta, el hijo envía únicamente un identificador opaco. El portal no confía en atributos como owner=true enviados por el iframe; solicita al servidor el recurso y éste autoriza la cuenta para la sesión del portal. El mensaje coordina interfaces, no transfiere autorización. Si el selector fue navegado por error a otro origen, la verificación de origen y source rechaza el mensaje.

El servidor de cuentas configura CORS sólo para los endpoints que el portal debe leer. Aun con CORS correcto, valida sesión, audiencia y acceso a la cuenta. Una operación de cambio sensible exige intención adicional; no depende de que haya preflight. Las cookies usan Secure, HttpOnly y una política SameSite coherente con la integración, pero el servidor mantiene protección CSRF acorde a los requests que realmente admite.

Si una librería del selector procede de CDN, el equipo la trata como código con la autoridad del selector. Fija versión e integridad cuando el modo de carga lo permite, limita CSP y conserva un proceso de actualización. No dibuja el CDN como un tercer iframe: esa topología ocultaría que el código ejecuta dentro del documento de accounts.example.

Método de diagnóstico

Ante un comportamiento o incidente web, reconstruye esta tabla antes de proponer una cabecera:

  1. Actor y objeto. ¿Qué documento, worker, script o servidor inicia la acción? ¿Qué DOM, URL, ventana, cookie o recurso afecta?
  2. Origen y site. Calcula esquema, host y puerto de cada actor; calcula site por separado sólo donde la política lo usa.
  3. Operación. Clasifica lectura, envío, incrustación, ejecución, navegación, almacenamiento o mensajería.
  4. Fetch efectivo. Registra modo, credenciales, método, headers, redirecciones y destino final.
  5. Punto de aplicación. Nombra qué decide el navegador, qué decide el servidor y qué decide cada documento.
  6. Evidencia. Separa request enviado, efecto server-side, response recibida y response expuesta al script.
  7. Autoridad. Comprueba autenticación, acción, recurso, tenant, estado e intención; no importes autoridad desde un header CORS.

Este método evita arreglos por ensayo. Si el request nunca sale por modo same-origin, cambiar autorización del servidor no corrige el cliente. Si el servidor procesa y la respuesta se oculta por CORS, repetir automáticamente puede duplicar el efecto. Si postMessage llega desde una ventana navegada, añadir un parser sin comprobar origen conserva el canal adversarial.

Errores recurrentes y su corrección

«Mismo dominio» es una frase insuficiente. Escribe las URL, normaliza puertos y decide si la regla pregunta por origin, site, host de cookie o dominio registrable. «CORS bloqueó el request» debe dividirse en preflight rechazado, request no enviado, request enviado con respuesta no compartida u otro error de red. «La cookie no es visible a JavaScript, por tanto XSS no puede actuar» ignora que el script puede inducir requests autenticados desde el mismo documento.

«El iframe está aislado porque tiene otro origen» omite navegación, mensajes, formularios y capacidades otorgadas. «El sandbox lo hace seguro» omite tokens y relación same-origin. «Sólo cargamos scripts desde nuestro CDN» omite que ese código recibe autoridad del documento. «HTTPS significa sitio confiable» confunde canal con conducta y autorización.

La corrección general es abandonar etiquetas globales y describir relaciones verificables. Un control es fuerte sólo para la propiedad que realmente aplica. Las capas se complementan: encoding contextual evita crear una interpretación ejecutable; CSP limita ejecución; SOP separa ciertas lecturas; CORS gobierna exposición de respuestas; SameSite condiciona cookies; la autorización del servidor protege recursos. Ninguna capa resume a las demás.

Transferencia al capítulo 58

El navegador entrega al servidor un request con semántica de red y contexto de usuario, pero no define el contrato del recurso. El capítulo 58 estudiará cómo una API representa recursos, métodos, precondiciones, idempotencia y errores. La frontera debe conservarse: CORS decide si un script puede observar una respuesta cross-origin; la API decide qué significa GET, POST, PUT o DELETE y si el principal puede ejecutarlos.

Esta distinción también protege los retries. Si una respuesta queda oculta por un fallo CORS, el cliente puede percibir error aunque el servidor haya aplicado el cambio. Repetir sólo es seguro si el contrato de la operación lo permite. Origen y Fetch explican la observación del navegador; idempotencia y estado explican la repetición del efecto.

Síntesis

El navegador no es una caja única. HTML, Document, DOM, Window, contexto de navegación, origen y proceso son conceptos distintos. Un origen de tupla usa esquema, host y puerto; site responde a otra relación. La política same-origin restringe operaciones concretas, no todo tráfico cross-origin. CORS gobierna el uso de respuestas por el solicitante, no la autorización del endpoint. Las cookies aportan credenciales ambientales con alcances que no coinciden exactamente con origen.

postMessage convierte la comunicación cross-origin en un protocolo que debe validar destino, origen, fuente, esquema y autorización. Sandbox y CSP reducen capacidades específicas; HTTPS y secure contexts aportan propiedades de entrega. Un script externo aceptado ejecuta dentro de la autoridad del documento. El análisis riguroso siempre separa request emitido, efecto aplicado, response recibida y response expuesta.

Fuentes primarias