El 404 es lo de menos, porque en la raíz del bucket no hay ningún archivo. Lo que importa es la última línea: el bucket da por bueno un origen que he escrito a mano.
Si el dominio de producción se puede falsear con una sola línea, la lista de orígenes de CORS no puede ser un control de acceso. Y si no lo es, tampoco supone un problema que en esa misma lista figuren localhost:4321 y localhost:8787, que son las direcciones con las que trabajo en mi equipo. Para entender por qué, hay que saber quién escribe esa cabecera y a quién protege.
Quién escribe la cabecera
La cabecera Origin la escribe el navegador, y solo el navegador. Cuando el código de una página pide un recurso a otro dominio, el navegador añade a la petición el origen de esa página, y el código no puede cambiarlo ni omitirlo. El servidor contesta con otra cabecera, Access-Control-Allow-Origin, y es de nuevo el navegador quien la lee y decide si entrega o no la respuesta al código que la pidió.
Fuera de un navegador no hay nadie que cumpla ese papel. curl, un script o cualquier otro programa pueden escribir en Origin lo que quieran, como en la petición del principio, y reciben el archivo igualmente, con cabecera o sin ella. Por eso la lista de orígenes no decide quién puede descargar un archivo.
A quién protege
Lo que nadie puede falsear es el origen de una página abierta en el navegador de otra persona. Una web ajena puede incluir código que pida un archivo de mi bucket, pero la cabecera Origin de esa petición la escribe el navegador del visitante, y lleva el dominio real de esa web.
Ese es el caso para el que existe CORS. Protege al visitante, porque impide que una página no autorizada lea, desde su navegador y con su sesión, una respuesta que iba dirigida a él. El servidor se limita a declarar en qué páginas confía, y quien hace cumplir esa declaración es el navegador de cada visitante.
El simulador siguiente recorre los cuatro casos posibles, con las cabeceras que viajan en cada uno.
Quién pide el archivo
Una página de julio.works, abierta en el navegador de un visitante.
Petición
GET /clips/tecleo.m4a
Origin: https://julio.works
Respuesta del bucket
200 OK
Access-Control-Allow-Origin: https://julio.works
Lectura permitida
El navegador ha escrito la cabecera, comprueba que la respuesta nombra ese mismo origen y entrega el archivo al código de la página.
Una página de otro dominio, abierta en el navegador de un visitante.
Petición
GET /clips/tecleo.m4a
Origin: https://otra-web.example
Respuesta del bucket
200 OK
Sin cabeceras de CORS
Lectura bloqueada
El bucket sirve el archivo igualmente, pero el navegador no deja que el código de esa página lea la respuesta. Una etiqueta de vídeo o de imagen corriente lo mostraría sin problema.
Una página que alguien sirve en el localhost:4321 de su propio equipo.
Petición
GET /clips/tecleo.m4a
Origin: http://localhost:4321
Respuesta del bucket
200 OK
Access-Control-Allow-Origin: http://localhost:4321
Lectura permitida
Lee un archivo que ya era público, y solo dentro de su máquina. Para usar ese origen contra otra persona necesitaría tener ya un servidor en el equipo de esa persona.
Un programa cualquiera, como curl, con la cabecera escrita a mano.
Petición
GET /clips/tecleo.m4a
Origin: https://julio.works
Respuesta del bucket
200 OK
Access-Control-Allow-Origin: https://julio.works
Sin política que aplicar
Fuera de un navegador nadie comprueba nada. El programa recibe el archivo con la cabecera que quiera escribir, o sin ninguna.
Las cabeceras de CORS de cada caso son las que devuelve hoy el bucket de este sitio. La ruta del archivo es un ejemplo.
El tercer caso es el de localhost. Quien sirve una página en su localhost:4321 consigue leer el archivo, pero lo consigue en su propia máquina y con unos archivos que ya podía descargar sin más. Para usar ese origen contra otra persona tendría que lograr que el navegador de esa persona abriese una página servida desde el localhost de su propio equipo; y quien es capaz de levantar un servidor en un equipo ajeno ya no necesita CORS para nada.
Cuándo importa
La lista de orígenes importa cuando la respuesta cambia según quién la pide. Eso ocurre siempre que hay credenciales de por medio —cookies, una sesión abierta o un certificado de cliente— y el servidor responde además con Access-Control-Allow-Credentials.
En ese supuesto, un origen de más en la lista es una fuga de datos. Si la API de un banco admitiera cualquier origen con credenciales, la página de un tercero podría leer el saldo de quien la visita mientras tiene la sesión del banco abierta en otra pestaña. El mismo error se comete cuando el servidor copia en la respuesta el origen que recibe sin compararlo con ninguna lista.
En una API con credenciales, localhost tampoco es un origen inocente, porque autoriza a cualquier programa que consiga servir una página en ese puerto del equipo del usuario.
Un bucket público sin credenciales está en el extremo contrario. En la respuesta no hay nada que no sea ya público, de modo que la lista de orígenes solo decide qué páginas pueden manejar esos archivos desde su código.
El caso de esta web
Esta es la política del bucket, con el nombre local de mi equipo sustituido por uno genérico.
Solo admite lectura, con GET y HEAD, y una única cabecera, Range, que es la que usa el navegador para pedir un tramo de un vídeo en lugar del archivo entero.
En este sitio hay dos usos que necesitan CORS y uno que no:
Lo necesita el sonido de las teclas. El clip se descarga con fetch y se decodifica con decodeAudioData. Es código que lee una respuesta de otro origen: el caso de manual.
Lo necesita el analizador de audio de los builds. Los clips se reproducen con una etiqueta de vídeo o de audio y, al mismo tiempo, su sonido pasa por un analizador de Web Audio que dibuja el espectro. Para que eso funcione, la etiqueta tiene que pedir el archivo con CORS.
No lo necesita un vídeo o una imagen corrientes. Una etiqueta que solo muestra el archivo lo carga desde cualquier página, esté o no en la lista.
El segundo uso tiene una trampa. Si la etiqueta no lleva el atributo crossorigin, el navegador reproduce el vídeo con normalidad, pero al analizador le entrega una señal en silencio. No aparece ningún error en la consola ni falla ninguna petición: las barras del espectro, sencillamente, no se mueven.
El tercer uso explica por qué CORS no impide que otra web enlace tus archivos. Una página ajena puede poner un vídeo de mi bucket en una etiqueta y se verá sin problema; lo que no puede hacer es leerlo desde su código.
Cómo se restringe de verdad
Si lo que se quiere es que un archivo solo lo descargue quien debe, CORS no sirve para eso y hay que recurrir a otras herramientas:
Las URL firmadas con caducidad. El servidor genera una dirección que solo vale durante unos minutos y para un archivo concreto. Funciona igual dentro y fuera de un navegador, porque no depende de ninguna cabecera que se pueda falsear.
Las reglas por cabecera Referer. Sirven para frenar a la web que enlaza tus archivos sin más intención que ahorrarse el alojamiento, pero fuera de un navegador se falsean igual que Origin.
La autenticación. Cuando el archivo es privado, lo correcto es exigir una sesión; y, en cuanto hay sesión, la lista de orígenes pasa a ser una pieza de seguridad, como se ha visto más arriba.
Quitar localhost de la política de producción, o usar un bucket distinto para desarrollar, puede ser una cuestión de orden, pero no añade ninguna protección.
La regla práctica
En un bucket público, localhost puede quedarse en la lista, porque no le da a nadie un acceso que no tuviera ya.
La misma regla sirve para cualquier otra lista de orígenes y se resume en una pregunta: ¿la respuesta lleva credenciales? Si no las lleva, la lista solo ordena qué páginas pueden usar el recurso desde su código. Si las lleva, cada origen de esa lista es una página a la que se le están confiando los datos de quien la visita.
NewsletterNuevas publicaciones de Labs en tu correo
Te envío un aviso por correo cada vez que publico un artículo o una investigación nueva. Puedes darte de baja desde cualquier envío.
Newsletter
Confirma la suscripción desde tu correo
He enviado un enlace de confirmación a . La suscripción queda activa en cuanto lo pulses, y el enlace caduca en 7 días.
Si no lo recibes en unos minutos, revisa la carpeta de spam o de promociones.
Responsable: Julio José Simón Gil. Finalidad: enviarte las nuevas publicaciones. Más información en la política de privacidad.