Saltar al contenido principal

Política de Mismo Origen y Proxies Web: Un Análisis Técnico de Seguridad

Escrito por
Smith
Smith
Tiempo de lectura
18 min de lectura
Publicado el
2 mar 2026
Política de Mismo Origen y Proxies Web: Un Análisis Técnico de Seguridad

Cuando construimos ProxyOrb, nos topamos con una paradoja fundamental en cada decisión de diseño: un proxy web funciona haciendo creer al navegador que el contenido de terceros es contenido del mismo origen. Eso es, por definición, engañar al modelo de seguridad central del navegador. Sin embargo, los navegadores modernos han pasado quince años apilando defensas cada vez más sofisticadas precisamente contra este tipo de confusión de origen.

Este artículo examina esa tensión desde los principios básicos. Si eres investigador de seguridad, pentester o ingeniero de seguridad de navegadores y quieres entender cómo los proxies web interactúan realmente con la política de mismo origen — no a nivel conceptual, sino a nivel de cabeceras HTTP, código fuente de Chromium y decisiones de ingeniería en producción — esto es para ti.

Cubriremos los fundamentos del SOP, el reescritura de URLs, CORS, CORB/CORP, COEP/COOP, Service Workers, iframes y las implicaciones de seguridad tanto para los operadores de proxy como para sus usuarios.


1. Los Fundamentos de la Política de Mismo Origen

La política de mismo origen (SOP, del inglés Same-Origin Policy) se define mediante una tripla: esquema + host + puerto. Dos URLs son del mismo origen únicamente si los tres componentes coinciden de forma exacta. https://example.com:443 y http://example.com:80 son de origen cruzado a pesar de apuntar al mismo servidor.

Lo que con frecuencia se malentiende es qué impide realmente el SOP frente a lo que sí permite. El SOP no bloquea:

  • Cargar imágenes (<img src>) desde otros orígenes
  • Cargar scripts (<script src>) desde otros orígenes
  • Cargar hojas de estilo (<link rel="stylesheet">) desde otros orígenes
  • Incrustar iframes desde otros orígenes (aunque el contenido del documento incrustado queda aislado)
  • Enviar envíos de formularios (<form action>) a otros orígenes

Lo que el SOP impide es leer la respuesta de peticiones cross-origin realizadas mediante fetch() o XMLHttpRequest. La petición sale — el viaje de ida y vuelta por la red ocurre — pero el cuerpo y las cabeceras de la respuesta se ocultan al JavaScript que se ejecuta en un origen diferente.

Esta distinción es fundamental para la seguridad navegador y para entender cómo funciona un proxy web. El objetivo central de un proxy es colapsar dos orígenes (el servidor proxy y el servidor destino) en uno, eliminando la frontera cross-origin. Una vez que todos los recursos parecen provenir de https://proxyorb.com, las restricciones basadas en SOP para leer respuestas sencillamente no se aplican.

El "espacio legítimo" que explotan los proxies web es la reescritura de URLs: en lugar de https://example.com/api/data, cada petición se convierte en https://proxyorb.com/api/data?__pot=aHR0cHM6Ly9leGFtcGxlLmNvbQ==. Desde la perspectiva del navegador, esto es una petición del mismo origen. El SOP no ha sido evitado — se ha vuelto irrelevante al cambiar el origen aparente de todo el contenido.


2. La Arquitectura de Reescritura de URLs de ProxyOrb

Para entender las implicaciones de seguridad, necesitas comprender el mecanismo de codificación. ProxyOrb utiliza un parámetro de URL llamado __pot (proxy origin token) que contiene el origen destino codificado en Base64.

El Parámetro __pot

El parámetro __pot siempre codifica el origen (esquema + host, sin ruta) del destino, no la URL completa. Esto significa que https://example.com/some/deep/path?foo=bar y https://example.com/other/page generan el mismo valor __pot: aHR0cHM6Ly9leGFtcGxlLmNvbQ== (la codificación en Base64 de https://example.com). La ruta y la cadena de consulta reales se preservan tal cual en la ruta y la cadena de consulta de la propia URL del proxy.

Cuando un usuario navega a https://proxyorb.com/?__pot=aHR0cHM6Ly9leGFtcGxlLmNvbQ==, el gateway lo decodifica y reconstruye la URL original:

-- Gateway pseudocode: decoding the proxy request

function resolve_original_url(proxy_url):
    pot_value = parse_query_param(proxy_url, "__pot")
    if not pot_value:
        return error("Missing origin token")

    original_origin = base64_decode(pot_value)
    -- e.g. "https://example.com"

    validate_not_private_ip(original_origin)  -- SSRF protection

    -- Replace the proxy host with the target host, keep path/query intact
    original_url = replace_origin(proxy_url, original_origin)
    return original_url

El gateway reenvía entonces la petición al servidor destino real, reescribiendo la cabecera Origin para que el servidor upstream vea su propio dominio en lugar del dominio del proxy:

-- Set outbound headers toward the target server

request.headers["Host"]   = target_host
request.headers["Origin"] = target_origin   -- e.g. "https://example.com"
-- Remove all internal proxy headers before forwarding

Reescritura de URLs en el Lado del Cliente mediante Service Worker

El gateway gestiona el lado del servidor. El lado del cliente — reescribir las URLs en las respuestas HTML, JavaScript y CSS para que todas las subpeticiones continúen a través del proxy — lo gestiona un Service Worker.

La transformación central convierte cualquier URL encontrada en el contenido de la página al formato del proxy:

-- Service Worker pseudocode: toProxyURL()

function toProxyURL(originalUrl, currentPageUrl):
    if isSameOrigin(originalUrl, currentPageUrl):
        return originalUrl   -- already proxy-origin, no rewrite needed

    -- Extract path/query/hash from the original URL
    -- Build a new URL rooted at the proxy origin
    proxyPath   = extractPathAndQuery(originalUrl)
    potValue    = base64encode(extractOrigin(originalUrl))
    proxyUrl    = proxy_origin + proxyPath + "?__pot=" + potValue

    return proxyUrl

Por ejemplo, https://cdn.example.com/bundle.js referenciado dentro de una página siendo proxiada se convierte en https://proxyorb.com/bundle.js?__pot=aHR0cHM6Ly9jZG4uZXhhbXBsZS5jb20=.

Esta reescritura es integral: abarca fetch(), XMLHttpRequest, <script src>, <img src>, conexiones WebSocket y etiquetas <link>. Cuando cada URL de la página apunta al origen del proxy, el navegador nunca realiza una petición cross-origin — cada petición es del mismo origen por construcción.

Recuperar __pot Sin Navegación Completa

Un caso límite sutil surge con peticiones que se originan dentro de una página proxiada pero carecen del parámetro __pot — por ejemplo, un fetch('/api/data') relativo que se dispara antes de que el Service Worker haya reescrito la URL, o una petición emitida por un script inyectado dinámicamente que evitó el reescritor de URLs.

El Service Worker resuelve el token faltante mediante una búsqueda en cascada:

-- Service Worker pseudocode: recovering the origin token

function resolveMissingPot(fetchEvent):
    candidates = [
        getUrlOfControlledTab(fetchEvent.clientId),   -- most reliable
        inferUrlFromFetchEvent(fetchEvent),             -- from clientId / resultingClientId
        fetchEvent.request.referrer,                    -- referrer header
    ]

    for url in candidates:
        pot = extractQueryParam(url, "__pot")
        if pot: return pot

    return null   -- cannot recover; let the gateway handle or reject

El gateway tiene una ruta de recuperación paralela: si una petición llega sin __pot pero lleva una cabecera Referer apuntando a una URL del mismo origen que incluye __pot, el gateway extrae el token del referer y lo adjunta a la petición actual. Esto gestiona escenarios de navegación donde el token fue eliminado por el propio JavaScript de la página antes de que el gateway recibiera la petición.


3. CORS: El Sistema Explícito de Permisos Cross-Origin

CORS (Cross-Origin Resource Sharing) fue diseñado para permitir que los servidores opten voluntariamente por el acceso cross-origin enviando cabeceras de respuesta específicas. Desde la perspectiva del proxy, el CORS en servidor proxy crea dos problemas distintos.

Problema 1: Interceptación de Preflight CORS

Cuando JavaScript ejecutándose en una página realiza una petición fetch() con cabeceras no simples (por ejemplo, Authorization, Content-Type: application/json), el navegador envía primero una petición de preflight OPTIONS. Si la respuesta de preflight del servidor destino no incluye cabeceras CORS permisivas, la petición real queda bloqueada.

En la arquitectura de ProxyOrb, el Service Worker intercepta todas las peticiones OPTIONS y las responde de forma sintética — antes incluso de que lleguen al gateway — devolviendo una respuesta que desbloquea la petición real:

-- Service Worker pseudocode: synthetic CORS preflight

function handlePreflight(request):
    origin = request.headers["Origin"] or "*"

    return Response(status=204, headers={
        "Access-Control-Allow-Origin":      origin,
        "Access-Control-Allow-Methods":     "GET, POST, PUT, DELETE, OPTIONS, PATCH, HEAD",
        "Access-Control-Allow-Headers":     "*",
        "Access-Control-Allow-Credentials": "true",
        "Access-Control-Max-Age":           "86400",
        "Vary":                             "Origin",
    })

A nivel del gateway, cada respuesta proxiada recibe cabeceras CORS equivalentes:

-- Gateway pseudocode: set CORS on outbound response

response.headers["Access-Control-Allow-Origin"]      = original_origin
response.headers["Access-Control-Allow-Headers"]     = "*"
response.headers["Access-Control-Allow-Credentials"] = "true"

Problema 2: Peticiones con Credenciales

La combinación de Access-Control-Allow-Credentials: true con un Access-Control-Allow-Origin que no sea comodín es significativa. CORS exige un valor de origen explícito (no *) siempre que se incluyan credenciales. El Service Worker emite todas las peticiones salientes con credentials: 'include', por lo que el gateway debe devolver el origen solicitante exacto en lugar de un comodín.

Esto significa que todas las cookies almacenadas bajo proxyorb.com — acumuladas de cada sitio destino que el usuario ha visitado a través del proxy — se envían en cada petición proxiada. Esto es intencional (mantiene el estado de sesión para los sitios en los que el usuario está autenticado), pero también es una consideración de seguridad importante que abordamos en la Sección 8.

El Patrón de Eliminar y Reemplazar Cabeceras CORS

Una respuesta proxiada puede llevar cabeceras CORS del servidor destino que son incorrectas desde la perspectiva del navegador (hacen referencia al origen destino, no al origen del proxy). El Service Worker las elimina y reemplaza:

-- Service Worker pseudocode: sanitize response headers

function sanitizeResponseHeaders(response):
    headers = copy(response.headers)

    -- Remove target-site directives that would confuse the browser
    headers.delete("Content-Security-Policy")
    headers.delete("Content-Security-Policy-Report-Only")
    headers.delete("X-Frame-Options")
    headers.delete("X-Content-Type-Options")

    -- Replace with proxy-origin-correct CORS headers
    headers.set("Access-Control-Allow-Origin",      proxy_origin)
    headers.set("Access-Control-Allow-Credentials", "true")
    headers.set("Access-Control-Allow-Headers",     "*")
    headers.set("Vary", "Origin")

    return headers

4. CORB y CORP: Las Defensas Más Estrictas de Chromium

CORS opera mediante la habilitación voluntaria de los servidores. Chromium añadió dos mecanismos que están activos por defecto, independientemente de la configuración CORS.

Cross-Origin Read Blocking (CORB)

CORB fue introducido en Chrome 67 (2018) como parte de las mitigaciones de Spectre. La idea central: incluso si el cuerpo de una respuesta cross-origin nunca es leído por JavaScript, el hecho de que fue obtenida y decodificada por el proceso del renderizador significa que existe en el espacio de direcciones de ese proceso. Los ataques tipo Spectre pueden extraerla mediante canales laterales.

CORB impide que ciertas respuestas cross-origin entren alguna vez en el proceso del renderizador. Concretamente, cuando una etiqueta <script> o <img> obtiene una respuesta cross-origin y esa respuesta tiene un Content-Type de text/html, text/xml o application/json, Chromium inspecciona el cuerpo (usando un detector de tipo MIME definido en la especificación CORB) y, si el tipo coincide, reemplaza la respuesta con una vacía antes de que el renderizador la vea jamás.

Para un proxy web, CORB sería catastrófico si las peticiones fueran realmente cross-origin. Un endpoint de API que devuelve application/json obtenido desde una etiqueta <script> retornaría vacío de forma silenciosa. Sin embargo, dado que ProxyOrb reescribe todas las URLs para que sean del mismo origen, la condición cross-origin de CORB nunca se activa — el navegador nunca ve una respuesta JSON cross-origin, porque desde su perspectiva, todas las respuestas provienen de proxyorb.com.

El único caso en que CORB importa es durante el período de transición antes de que el Service Worker esté completamente registrado e interceptando peticiones. Si alguna petición escapa a la reescritura de URLs durante esa ventana, CORB puede bloquearla. Esta condición de carrera es la razón por la que ProxyOrb también mantiene una capa de interceptor en el hilo principal que parchea XMLHttpRequest, fetch y los setters de atributos del DOM de forma síncrona antes de que se ejecute cualquier JavaScript de la página — proporcionando una alternativa durante el arranque del Service Worker.

Vale la pena mencionar que CORB ha sido parcialmente reemplazado por Opaque Response Blocking (ORB), que Chromium está en proceso de adoptar. ORB refina las reglas de CORB para reducir los falsos positivos mientras mantiene las protecciones contra Spectre. Las implicaciones de compatibilidad con el proxy son similares.

Cross-Origin Resource Policy (CORP)

CORP, definido en la especificación Fetch, permite a los servidores declarar que sus recursos solo deben ser cargados por contextos del mismo origen o del mismo sitio:

Cross-Origin-Resource-Policy: same-origin

Cuando Chromium encuentra esta cabecera en una petición de subrecurso cross-origin, bloquea la respuesta por completo. Esto es más agresivo que CORB — se aplica independientemente del tipo de contenido y no puede eludirse mediante detección de MIME.

De nuevo, dado que la reescritura de URLs de ProxyOrb garantiza que todas las peticiones son del mismo origen desde la perspectiva del navegador, las cabeceras CORP de los servidores destino no desencadenan el bloqueo. El gateway también elimina estas cabeceras de las respuestas de forma defensiva, gestionando los casos límite donde una petición pase sin reescritura de URL.

El problema más profundo con CORP es en realidad un caso donde la arquitectura del proxy ayuda a la compatibilidad: si https://api.example.com y https://www.example.com están siendo proxiados, ambos se mapean al mismo origen del proxy, de modo que una petición de uno al otro es ahora genuinamente del mismo origen — las restricciones CORP ya no se aplican entre ellos.


5. COEP y COOP: El Aislamiento Cross-Origin

A partir de Chrome 92 (2021), el acceso a SharedArrayBuffer — y por extensión, los temporizadores de alta resolución utilizados para ciertas APIs de rendimiento — se restringió a las páginas que han optado por el aislamiento cross-origin. Esta habilitación requiere dos cabeceras de respuesta que funcionan en conjunto.

Cross-Origin-Embedder-Policy (COEP)

Cross-Origin-Embedder-Policy: require-corp

Cuando una página envía esta cabecera, el navegador exige que cada subrecurso cargado por esa página:

  1. Sea del mismo origen, O
  2. Envíe Cross-Origin-Resource-Policy: cross-origin explícitamente, O
  3. Tenga una respuesta CORS permisiva para la petición con credenciales

El problema para los proxies web: si un sitio destino envía COEP: require-corp en su documento principal, y ese documento es servido a través del proxy con la cabecera preservada, el navegador ahora exige que cada subrecurso cargado por esa página también opte por ello. Cualquier recurso que no lo haga — y la mayoría no lo hará — provoca un error de red.

Cross-Origin-Opener-Policy (COOP)

Cross-Origin-Opener-Policy: same-origin

COOP controla si una página puede compartir un grupo de contextos de navegación con páginas cross-origin. Cuando se establece en same-origin, una navegación cross-origin abre un nuevo grupo de contextos de navegación, rompiendo las referencias window.opener y los canales postMessage entre la página y cualquier opener cross-origin.

Para un proxy, COOP es menos inmediatamente destructivo que COEP, pero aun así rompe los sitios que dependen de flujos postMessage cross-origin (siendo los flujos de popup OAuth el ejemplo más común).

El Dilema del Operador de Proxy

Este es el compromiso más difícil al que nos enfrentamos en ProxyOrb:

Opción A: Eliminar COEP y COOP de las respuestas.

  • A favor: La carga de subrecursos funciona con normalidad; la página proxiada no aplica estas restricciones.
  • En contra: Estamos eliminando cabeceras de seguridad que el sitio destino estableció intencionalmente. Si el sitio usaba el aislamiento cross-origin para proteger datos sensibles de ataques tipo Spectre, eliminar estas cabeceras vuelve a exponer ese riesgo.

Opción B: Preservar COEP y COOP.

  • A favor: Se respeta la intención de seguridad del sitio destino.
  • En contra: Cualquier subrecurso que no establezca explícitamente CORP: cross-origin no se cargará, rompiendo la mayoría de las aplicaciones web complejas.

La estrategia actual de ProxyOrb es la Opción A: el gateway elimina Cross-Origin-Embedder-Policy y Cross-Origin-Opener-Policy de las respuestas. Esto maximiza la compatibilidad con los sitios. La contrapartida en seguridad se toma conscientemente: los usuarios de un proxy web aceptan que están ejecutando contenido en un entorno diferente al de su contexto de despliegue previsto.

Cada nuevo mecanismo de seguridad del navegador sigue un patrón: se introduce como una opción voluntaria (los sitios pueden enviar la cabecera si quieren protección), luego se convierte gradualmente en predeterminada para las nuevas APIs y finalmente se considera para el cumplimiento obligatorio. COEP siguió este camino: opcional en Chrome 83, requerida para SharedArrayBuffer en Chrome 91. Los sitios que incrustan contenido de terceros sin coordinación encontraron su contenido roto. Los proxies web amplifican este problema porque median contenido de terceros por definición.

La comunidad de seguridad de navegadores es consciente de este problema. La propuesta emergente Document-Isolation-Policy apunta a desacoplar el aislamiento de procesos de los requisitos de aislamiento cross-origin, lo que potencialmente hará posible obtener mitigaciones de Spectre sin requerir que todos los subrecursos opten por ello. Cuando eso llegue, la compatibilidad del proxy con las cabeceras de aislamiento podría mejorar.


6. El Service Worker como Capa de Confianza del Lado del Navegador

El Service Worker no es simplemente una optimización — es arquitectónicamente esencial para el modelo de seguridad de ProxyOrb. Te explicamos por qué.

El Ámbito de Registro se Vincula al Origen

Un Service Worker se registra con un scope que siempre está dentro del origen de la página registrante. Cuando ProxyOrb registra su Service Worker, el ámbito es https://proxyorb.com/, lo que significa que intercepta cada petición fetch de cualquier página bajo ese origen.

Esta es la razón fundamental por la que funciona la reescritura de URLs al mismo origen: una vez que el SW controla un cliente, cada petición de red de ese cliente — independientemente de la URL que el JavaScript de la página intente obtener — pasa primero por el Service Worker.

// Service Worker entry point
self.addEventListener('fetch', (event) => {
  event.respondWith(handleProxyRequest(event))
})

El Pipeline de Interceptación

Cuando el Service Worker intercepta una petición, la procesa a través de varias etapas de decisión:

-- Service Worker pseudocode: request interception pipeline

function handleProxyRequest(fetchEvent):
    request = fetchEvent.request

    -- Stage 1: Synthetic CORS preflight (never hits the network)
    if request.method == "OPTIONS":
        return syntheticCORSResponse(request)

    -- Stage 2: Pass through requests that shouldn't be proxied
    if shouldBypass(request.url):
        return originalFetch(request)

    -- Stage 3: Ensure __pot is present; recover from context if missing
    enrichedRequest = attachOriginToken(request, fetchEvent)

    -- Stage 4: Forward to gateway
    response = originalFetch(enrichedRequest)

    -- Stage 5: Strip and replace security headers in the response
    return sanitizeAndReturn(response)

Reenvío Transparente de Cabeceras

Un desafío sutil: el navegador restringe que JavaScript establezca ciertas cabeceras de petición "prohibidas" (Host, Origin, Referer, etc.) a través de la API Fetch. El Service Worker lo soluciona codificando las cabeceras restringidas en una única cabecera de paso personalizada; el gateway las decodifica y aplica antes de reenviarlas al servidor destino:

-- Pseudocode: encode browser-restricted headers for the gateway

function addPassthroughHeaders(request, outboundHeaders):
    restricted = {}
    for name, value in request.headers:
        if name not in COMMON_ALLOWED_HEADERS:
            restricted[name] = value

    if restricted is not empty:
        outboundHeaders["X-Proxy-Passthrough"] = json_encode(restricted)

-- Gateway side: decode and apply before forwarding upstream
function applyPassthroughHeaders(incomingHeaders):
    encoded = incomingHeaders["X-Proxy-Passthrough"]
    if encoded:
        for name, value in json_decode(encoded):
            request.headers[name] = value
        request.headers.delete("X-Proxy-Passthrough")

Implicaciones de Seguridad del SW como Intermediario de Confianza

Desde una perspectiva de seguridad, el Service Worker ocupa una posición privilegiada: puede inspeccionar, modificar y falsificar cualquier petición realizada desde cualquier página en su ámbito. Para uso legítimo del proxy, este es precisamente el objetivo. Para un proxy malicioso, esto sería una superficie de ataque extremadamente poderosa.

Por eso la fiabilidad del propio script del Service Worker es primordial. ProxyOrb sirve el script interceptor desde su propio origen a través de HTTPS con controles de caché estrictos. Si un atacante pudiera inyectar un Service Worker modificado, podría interceptar todo el tráfico de los usuarios afectados — no solo el tráfico del proxy, sino cualquier petición de páginas bajo ese origen.


7. Los iframes: El Problema de Frame-Ancestors

Los iframes representan un desafío distinto al de los subrecursos regulares porque implican un contexto de navegación anidado con su propia navegación, su propio límite SOP y su propio conjunto de restricciones de incrustación.

X-Frame-Options y frame-ancestors

Dos mecanismos impiden que las páginas sean incrustadas en iframes:

Legado: X-Frame-Options: DENY o X-Frame-Options: SAMEORIGIN

Moderno: Content-Security-Policy: frame-ancestors 'none' o frame-ancestors 'self'

Cuando el gateway proxia una página destino que envía cualquiera de estos, la página no puede ser incrustada en un iframe bajo el origen del proxy. Dado que X-Frame-Options: SAMEORIGIN hace referencia al origen destino (example.com), y el proxy sirve la página desde proxyorb.com, la verificación de mismo origen del navegador falla.

El proxy debe eliminar estas cabeceras de las respuestas proxiadas y reemplazar el CSP con una versión permisiva:

-- Gateway pseudocode: override embedding-restrictive headers

response.headers.delete("X-Frame-Options")
response.headers.delete("Content-Security-Policy")
response.headers.delete("Content-Security-Policy-Report-Only")

-- Set a permissive replacement that allows the proxy to embed content
response.headers["Content-Security-Policy"] =
    "upgrade-insecure-requests; frame-ancestors 'self'; " +
    "default-src * data: blob: about: ws: wss: 'unsafe-inline' 'unsafe-eval'"

Interceptación de Iframes e Inyección de Scripts

Los iframes incrustados presentan un segundo problema: su contenido se carga desde el proxy, pero el contexto de navegación del iframe no tiene automáticamente el Service Worker o el interceptor del hilo principal activo. Si la página proxiada crea un <iframe src="https://widget.example.com/...">, esa URL del iframe también debe reescribirse al formato del proxy, y los scripts interceptores del proxy deben inyectarse en el documento del iframe.

El interceptor de iframes opera en dos niveles:

Nivel 1 — Parcheo de prototipos (captura la creación programática de iframes):

-- Pseudocode: intercept iframe src assignment

override HTMLIFrameElement.prototype.src setter:
    if value is not already a proxy URL:
        value = toProxyURL(value)   -- rewrite to proxy format
    call original setter(value)
    scheduleProxyInjection(this)    -- inject interceptor into iframe document

Nivel 2 — Fallback con MutationObserver (captura iframes insertados en el DOM):

-- Pseudocode: observe DOM for dynamically added iframes

observer = new MutationObserver(mutations):
    for mutation in mutations:
        for node in mutation.addedNodes:
            if node is an <iframe>:
                rewriteSrcIfNeeded(node)
                injectProxyScript(node)

observer.observe(document, { childList: true, subtree: true })

El Problema del Atributo sandbox

El atributo HTML sandbox restringe lo que un iframe puede hacer: sandbox="allow-scripts allow-same-origin" controla la ejecución de scripts, el envío de formularios, el acceso cross-origin, etc. Para que la inyección del proxy funcione, el iframe necesita ejecutar scripts y tener acceso al contexto del proxy del padre.

El token allow-same-origin es particularmente matizado: cuando está presente junto con allow-scripts, anula el aislamiento de origen del sandbox — el iframe se ejecuta con el origen de la página incrustadora. Cuando está ausente, el iframe obtiene un origen opaco único, lo que rompería completamente la inyección de scripts del proxy.

El enfoque actual de ProxyOrb para los atributos sandbox es eliminar el atributo sandbox por completo antes de inyectar el script del proxy:

-- Pseudocode: handle sandbox attribute before proxy injection

function handleSandboxedIframe(iframe):
    if iframe.hasAttribute("sandbox"):
        -- Remove so the proxy can inject scripts and access contentWindow
        iframe.removeAttribute("sandbox")
    injectProxyScript(iframe)

Este es un compromiso de seguridad significativo: el sandboxing fue colocado intencionalmente por el sitio original para restringir las capacidades del contenido incrustado. Eliminarlo amplía lo que ese contenido puede hacer. Es el tipo de decisión que es invisible para los usuarios finales pero que afecta materialmente la postura de seguridad de la sesión del proxy.


8. Implicaciones de Seguridad para los Operadores de Proxy

El Panorama de la Superficie de Ataque

Cuando los usuarios navegan a través de ProxyOrb, varias categorías de datos sensibles fluyen a través del origen del proxy:

Cookies de Sesión: Dado que todos los sitios destino se proxian a través de proxyorb.com, las cookies se almacenan bajo ese origen. Las sesiones autenticadas de un usuario con su banco, correo electrónico y redes sociales son todas cookies bajo un único dominio. El credentials: 'include' del Service Worker significa que estas cookies acompañan a cada petición proxiada.

Cabeceras Referer: La cabecera Referer enviada a los servidores upstream revela qué página estaba viendo el usuario. El proxy elimina cuidadosamente su propio dominio de los valores de referer antes de reenviarlos al servidor destino. Si una petición se origina desde https://proxyorb.com/some/path?__pot=..., la cabecera Referer saliente se reconstruye como la URL destino original (https://example.com/some/path) — el dominio del proxy nunca se filtra a los servidores upstream.

-- Pseudocode: normalize referer before forwarding upstream

function sanitizeReferer(referer):
    if referer is a proxy URL:
        return reconstruct_original_url(referer)   -- e.g. "https://example.com/path"
    if referer's origin equals proxy_origin:
        return ""   -- suppress: don't leak proxy domain to upstream
    return referer

Contexto de Ejecución de JavaScript: Cada archivo JavaScript servido a través del proxy es modificado (URLs reescritas) y ejecutado en el origen del proxy. Un script malicioso de evil.example.com proxiado a través de ProxyOrb se ejecuta en el origen proxyorb.com y tiene acceso completo al almacenamiento local, las cookies y el IndexedDB de ese origen — lo que incluye datos de cada otro sitio destino al que el usuario ha accedido a través del proxy.

El Modelo de Amenaza del Proxy Malicioso

Con fines educativos, vale la pena ser explícito sobre lo que un proxy malicioso podría hacer y que ProxyOrb deliberadamente no hace:

  1. Recolección de Credenciales: Un proxy malicioso podría registrar cada cuerpo de petición (que contiene contraseñas y datos de formularios) a medida que pasa por el gateway.

  2. Secuestro de Sesión: Dado que todas las cookies de sitios destino se almacenan bajo el dominio del proxy, un operador de proxy malicioso podría acceder a esas cookies del lado del servidor a través del gateway.

  3. Inyección de Contenido: El proxy necesariamente modifica el contenido de la página (para inyectar el Service Worker y reescribir URLs). Un proxy malicioso podría inyectar JavaScript arbitrario — keyloggers, miners de criptomonedas, scripts de fraude publicitario — invisible para el usuario.

  4. SSL Stripping: Si el proxy degrada las conexiones HTTPS a HTTP para el tramo gateway-usuario, todo el tráfico es texto plano.

ProxyOrb opera sobre HTTPS de extremo a extremo y no registra los cuerpos de las peticiones. El operador de cualquier proxy web tiene estas capacidades, por lo que elegir un operador de proxy de confianza importa tanto como elegir un proveedor de VPN de confianza.

Contenido Mixto

Los navegadores modernos aplican el bloqueo de contenido mixto: una página HTTPS no puede cargar subrecursos HTTP. ProxyOrb lo gestiona aplicando HTTPS en todas las conexiones salientes desde el gateway, incluso cuando la URL destino utiliza HTTP. La anulación del CSP incluye upgrade-insecure-requests para indicar al navegador que actualice cualquier URL de subrecurso HTTP a HTTPS antes de cargarlo.

Esto es generalmente deseable, pero puede romper sitios que sirven recursos deliberadamente sobre HTTP (CDNs heredados, recursos localhost, etc.).


9. La Carrera Armamentística Continua: Conclusión

Cuando empezamos a construir ProxyOrb, los principales desafíos de compatibilidad eran CORS y X-Frame-Options. Desde entonces, Chromium ha lanzado CORB, CORP, COEP, COOP y está desarrollando Document-Isolation-Policy. La dirección de avance es clara: los navegadores son cada vez más agresivos en la aplicación de los límites cross-origin, y cada nuevo mecanismo requiere que la infraestructura del proxy se adapte.

El desafío más significativo que se avecina es probablemente el despliegue completo de COEP en las principales aplicaciones web. Los sitios que usan SharedArrayBuffer por rendimiento (editores de vídeo, IDEs, herramientas de colaboración) están estableciendo cada vez más COEP: require-corp. A medida que ProxyOrb elimina esta cabecera para mantener la compatibilidad, los usuarios que necesiten las funcionalidades de alto rendimiento que COEP habilita las encontrarán degradadas en un contexto de proxy.

Para los investigadores de seguridad, la arquitectura del proxy revela algo importante: la política de mismo origen no es un firewall. Es un modelo de confianza basado en la estructura de URLs. Los proxies web explotan el hecho de que el SOP no hace ninguna distinción intrínseca entre "estos dos recursos son legítimamente del mismo origen" y "estos dos recursos han sido reescritos en URL para parecer del mismo origen". Todo el stack de seguridad del navegador por encima del SOP — CORS, CORB, CORP, COEP, COOP — puede verse como un intento de añadir defensas más robustas que la identidad de origen basada en URL.

Comprender esto es esencial para cualquiera que audite servicios de proxy, diseñe políticas de seguridad del navegador o evalúe la postura de seguridad en el mundo real de aplicaciones a las que se puede acceder a través de proxies.


Preguntas Frecuentes

¿Un proxy web evita la política de mismo origen?

No exactamente. Un proxy web funciona mediante la reescritura de URLs: transforma todas las URLs cross-origin en URLs del mismo origen, haciendo que las restricciones cross-origin del SOP sean irrelevantes en lugar de evitarlas. Desde la perspectiva del navegador, todo el contenido parece provenir del propio origen del proxy, por lo que no se cruzan límites cross-origin. Esto es arquitectónicamente diferente de una elusión — el SOP sigue aplicando sus reglas; el proxy simplemente diseña el contenido de tal forma que esas reglas nunca se disparan.

¿Cómo afecta CORS a los servidores proxy web?

CORS afecta a los proxies web en dos niveles. Primero, el proxy debe interceptar las peticiones de preflight OPTIONS y responder con cabeceras CORS permisivas, ya que la respuesta de preflight del servidor destino real (que referencia el origen destino) no satisfaría la verificación CORS del navegador para el origen del proxy. Segundo, el proxy debe eliminar las cabeceras CORS de las respuestas del servidor destino y reemplazarlas con cabeceras que hagan referencia al origen del proxy. La configuración Access-Control-Allow-Credentials: true, combinada con credentials: 'include' en el Service Worker, significa que todas las cookies del origen del proxy acompañan a cada petición proxiada.

¿Qué es CORB y cómo afecta a los servicios de proxy?

Cross-Origin Read Blocking (CORB) es una defensa de Chromium que impide que ciertas respuestas cross-origin sensibles (HTML, JSON, XML) entren en el proceso del renderizador cuando se cargan como subrecursos cross-origin. Para los proxies web correctamente configurados, CORB generalmente no se activa porque la reescritura de URLs hace que todas las peticiones sean del mismo origen. Sin embargo, CORB puede causar problemas durante el período previo al registro completo del Service Worker, cuando algunas peticiones pueden escapar a la reescritura de URLs y enviarse como peticiones cross-origin genuinas. CORB fue introducido como una mitigación de Spectre y está definido en la especificación WHATWG Fetch.

¿Pueden los proxies web acceder a las cookies HTTP-only?

No. Las cookies HttpOnly son establecidas por el servidor destino y no pueden ser leídas por JavaScript — incluyendo el JavaScript que se ejecuta en un Service Worker. Sin embargo, el gateway recibe estas cookies al reenviar peticiones en nombre del usuario, ya que el navegador las envía en las cabeceras de las peticiones. El flag HttpOnly previene el robo por JavaScript del lado del cliente, pero no impide que el propio servidor proxy vea los valores de las cookies en tránsito. Esto es análogo a cómo HttpOnly protege contra XSS pero no contra la interceptación del lado del servidor.

¿Es seguro usar un proxy web desde la perspectiva de la seguridad del navegador?

Depende del modelo de amenaza. Desde la perspectiva del navegador, todo el contenido servido a través de un proxy web se ejecuta en el origen del proxy, lo que significa que una vulnerabilidad en el JavaScript de un sitio proxiado podría potencialmente acceder a datos de la sesión de otro sitio proxiado (dado que comparten el almacenamiento de cookies y el espacio de almacenamiento del mismo origen). El operador del proxy también es una parte de confianza con acceso a todo el tráfico en tránsito. Para acceder a contenido no sensible en entornos restringidos, el riesgo es típicamente aceptable. Para sesiones autenticadas con servicios sensibles (banca, salud, gobierno), los usuarios deben entender que el operador del proxy tiene la capacidad técnica de observar ese tráfico, y deben usar únicamente servicios de proxy con políticas de no registro auditables y prácticas sólidas de seguridad operacional.


Este artículo refleja la arquitectura de ProxyOrb a principios de 2026. Los mecanismos de seguridad de los navegadores evolucionan rápidamente; se recomienda a los lectores consultar el Estándar Fetch de WHATWG, la especificación de Content Security Policy del W3C y los documentos de diseño de seguridad de Chromium para obtener la información más actualizada.