Cuando cargues secuencias de comandos, hojas de estilo, fuentes o imágenes de CDN de terceros o de orígenes externos, verifica su integridad criptográfica con la integridad de subrecursos (integrity en HTML y integrity() en CSS url()), configura el manejo de credenciales de origen cruzado (crossorigin y cross-origin()) y restringe los encabezados Referer salientes (referrerpolicy y referrer-policy()). Para conocer la política de seguridad del contenido a nivel del documento y las listas de entidades permitidas de conexiones salientes, consulta Seguridad web y Restringe las conexiones de red salientes con listas de entidades permitidas de conexiones.
1. Generar resúmenes de integridad criptográfica para recursos inmutables
La integridad de subrecursos (SRI) garantiza que el navegador solo ejecute o renderice un recurso externo si sus bytes coinciden con el hash criptográfico declarado por tu aplicación.
- Calcula los resúmenes en el momento de la compilación o el lanzamiento, nunca a partir de respuestas en vivo que no sean de confianza: Genera hashes de integridad durante el paso de compilación o lanzamiento a partir de bytes de recursos locales que se sabe que son correctos (o copia el resumen oficial publicado por el autor del paquete) y codifícalos en tu HTML, CSS o JavaScript. Nunca recuperes una dirección URL de terceros en el navegador durante el tiempo de ejecución para calcular su propio hash
integrity, que confía en los bytes que devuelve el servidor remoto y anula la SRI. - Usa
sha384-de forma predeterminada: Aplica formato a los metadatos de integridad como<algorithm>-<base64-digest>consha384-(opción predeterminada recomendada),sha512-osha256-. - Fija los recursos secundarios externos a URLs inmutables con versiones: Siempre haz referencia a los recursos de terceros con URLs inmutables, con versiones fijas o con direcciones de contenido (como
https://cdn.example.com/ui@2.4.0/app.min.js) en lugar de URLs flotantes mutables (latest/app.min.js), que exponen tu sitio a cambios silenciosos en la cadena de suministro y rompen la SRI en cuanto se actualiza el archivo remoto. Si debes cargar una secuencia de comandos mutable de terceros que se actualiza in situ (como una etiqueta de estadísticas de un proveedor), omiteintegrityen esa URL y, en su lugar, restringe la secuencia de comandos con la Política de Seguridad del Contenido (strict-dynamico nonces) o la zona de pruebas de iframe. - Separa con espacios varios resúmenes cuando un extremo pueda entregar varias cargas útiles válidas: Todos los navegadores que admiten SRI implementan
sha256-,sha384-ysha512-. Si una URL puede devolver legítimamente una de varias cargas útiles conocidas como correctas (como durante una prueba A/B, un lanzamiento de CDN por etapas o una compilación negociada por contenido), enumera varios resúmenes del mismo algoritmo separados por espacios en blanco (por ejemplo,integrity="sha384-variantA... sha384-variantB..."). El navegador acepta la respuesta si coincide con cualquier resumen enumerado del algoritmo más sólido presente. - Entrega recursos secundarios de origen cruzado con encabezados de CORS: Los navegadores solo pueden verificar la integridad de una respuesta de origen cruzado cuando es legible por CORS (
Access-Control-Allow-Origin: *para solicitudes anónimas o el origen exacto de la solicitud conAccess-Control-Allow-Credentials: truepara solicitudes con credenciales).
# Generate a sha384- SRI hash at build time from a known-good local file
printf "sha384-%s\n" "$(openssl dgst -sha384 -binary app.min.js | openssl base64 -A)"
// Build-time / tooling helper (Node.js 19+, Deno, Bun) to compute an SRI string from known-good local bytes
export async function computeIntegrityMetadata(bytes) {
const rawBytes =
typeof bytes === 'string' ? new TextEncoder().encode(bytes) : bytes;
const digestBuffer = await crypto.subtle.digest('SHA-384', rawBytes);
const binary = Array.from(new Uint8Array(digestBuffer), (b) =>
String.fromCharCode(b),
).join('');
return `sha384-${btoa(binary)}`;
}
2. Protege las secuencias de comandos, las hojas de estilo y las precargas de HTML
Establece un encabezado de respuesta HTTP Referrer-Policy: strict-origin-when-cross-origin a nivel del documento (o incluye <meta name="referrer" content="strict-origin-when-cross-origin"> en <head>) como un valor predeterminado de referencia para que cualquier subrecurso o url() de CSS que omita un modificador de referencia por solicitud siga quitando los parámetros de consulta y ruta de acceso sensibles en las solicitudes de origen cruzado.
Luego, en los elementos externos <script>, <link rel="stylesheet">, <link rel="preload"> y <link rel="modulepreload">, siempre configura integrity, crossorigin y referrerpolicy juntos:
integrity="sha384-...": Bloquea la ejecución o la aplicación del recurso si los bytes de respuesta no coinciden con el resumen esperado (para un<link rel="stylesheet">bloqueado,linkElement.sheetsigue siendonully se activa un eventoerror).crossorigin="anonymous"(predeterminado recomendado para recursos de CDN públicos) ocrossorigin="use-credentials": Es obligatorio cuando se estableceintegrityen un recurso de origen cruzado. Omitircrossoriginhace que el navegador recupere el recurso en modono-cors(lo que produce una respuesta opaca que no se puede verificar con hash) y bloquee el recurso.referrerpolicy="no-referrer"(opción predeterminada recomendada para CDN de terceros) oreferrerpolicy="strict-origin-when-cross-origin": Anula la política de referencia del documento para evitar que se filtre información de origen o ruta de acceso en el encabezado de la solicitudRefereral host de terceros.
<!-- Document-level baseline referrer policy in <head> (or via Referrer-Policy HTTP header) -->
<meta name="referrer" content="strict-origin-when-cross-origin" />
<!-- External stylesheet with SRI, anonymous CORS, and no referrer -->
<link
rel="stylesheet"
href="https://cdn.example.com/ui@2.4.0/styles.css"
integrity="sha384-bs0EksOYBbU/g0IAHMXbSyH1Mgw83GKn+EZlSI+VE+zVchXmSbLwhVLrPzsOWnc5"
crossorigin="anonymous"
referrerpolicy="no-referrer"
/>
<!-- External script with SRI, anonymous CORS, and no referrer -->
<script
src="https://cdn.example.com/ui@2.4.0/bundle.min.js"
integrity="sha384-oqVuAfXRKap7fdgcCY5uykM6+R9GqQ8K/uxy9rx7HNQlGYl1kPzQho1wx4JwY8wC"
crossorigin="anonymous"
referrerpolicy="no-referrer"
></script>
Cuando recuperes subrecursos externos de manera programática con fetch(), pasa las opciones integrity, mode, credentials y referrerPolicy correspondientes (un resumen que no coincida rechazará la promesa con TypeError):
const response = await fetch('https://cdn.example.com/ui@2.4.0/manifest.json', {
integrity: 'sha384-oqVuAfXRKap7fdgcCY5uykM6+R9GqQ8K/uxy9rx7HNQlGYl1kPzQho1wx4JwY8wC',
mode: 'cors',
credentials: 'omit',
referrerPolicy: 'no-referrer',
});
3. Protege las imágenes, las fuentes y las hojas de estilo importadas de CSS con modificadores url()
Los modificadores de solicitudes url() de CSS (cross-origin(), referrer-policy() y integrity()) controlan el modo de recuperación, el encabezado de referencia y la verificación de integridad de subrecursos de los recursos cargados con CSS, incluidas las reglas background-image, content, mask-image, border-image-source, filter, @font-face src y @import, directamente desde CSS:
| Función modificadora | Sintaxis de argumentos y valores permitidos | Objetivo |
|---|---|---|
cross-origin(<ident>) |
Palabra clave sin comillas: anonymous (predeterminada recomendada) o use-credentials |
Recupera el recurso CSS con CORS y controla si se envían las credenciales. Inclúyelo siempre junto con integrity() en los recursos de CSS de origen cruzado. |
referrer-policy(<ident>) |
Palabra clave sin comillas: no-referrer (opción predeterminada recomendada para recursos de terceros), strict-origin-when-cross-origin, origin, same-origin, strict-origin, origin-when-cross-origin, no-referrer-when-downgrade o unsafe-url |
Anula la política de referencia del documento para esta solicitud de CSS específica. |
integrity("<string>") |
Cadena entrecomillada: "sha384-<base64>" (o resúmenes separados por espacios "sha384-... sha384-...") |
Verifica el recurso CSS recuperado con el resumen criptográfico esperado antes de renderizarlo o aplicarlo. |
Sigue estas reglas de sintaxis y seguridad de CSS cuando crees modificadores de url():
- Coloca la cadena de la URL entre comillas dentro de
url("..."): Los modificadores de solicitudes solo son válidos cuando la URL dentro deurl(...)es una<string>entre comillas (las URLs sin comillas no pueden usar modificadores). - Especifica cada tipo de modificador como máximo una vez por
url(): Puedes combinarcross-origin(),referrer-policy()yintegrity()en cualquier orden dentro deurl("..." ...), pero repetir el mismo tipo de modificador dos veces (como dos modificadoresintegrity()) no es válido. Para proporcionar varios hashes, sepáralos con espacios dentro de una sola cadenaintegrity("sha384-... sha384-..."). - Siempre vincula el origen cruzado
integrity()concross-origin(anonymous): Si bien@font-facesrcymask-imageya se recuperan en modocorsanónimo de forma predeterminada,background-image,content,border-image-sourcey@importse establecen de forma predeterminada en el modono-cors(lo que produce una respuesta opaca que no se puede verificar con hash) y el SVGfilterexterno se establece de forma predeterminada en el modosame-origin(lo que bloquea las URLs de origen cruzado por completo). Incluircross-origin(anonymous)(ocross-origin(use-credentials)) garantiza una verificación de CORS coherente en todas las propiedades de CSS. - Coloca los modificadores dentro de
url("...")en las reglas@importy@font-face: Manténcross-origin(),referrer-policy()yintegrity()dentro deurl("..." ...), y los descriptores de fuentes, comoformat("woff2")ytech(...), fuera deurl(...).
@import url(
"https://cdn.example.com/theme@1.2.0/tokens.css"
cross-origin(anonymous)
referrer-policy(no-referrer)
integrity("sha384-bs0EksOYBbU/g0IAHMXbSyH1Mgw83GKn+EZlSI+VE+zVchXmSbLwhVLrPzsOWnc5")
);
@font-face {
font-family: "BrandSans";
src: url(
"https://cdn.example.com/fonts@1.0.0/brand-sans.woff2"
cross-origin(anonymous)
referrer-policy(no-referrer)
integrity("sha384-oqVuAfXRKap7fdgcCY5uykM6+R9GqQ8K/uxy9rx7HNQlGYl1kPzQho1wx4JwY8wC")
) format("woff2");
font-display: swap;
}
.verified-graphic {
background-image: url(
"https://cdn.example.com/media@1.0.0/badge.svg"
cross-origin(anonymous)
referrer-policy(no-referrer)
integrity("sha384-Iw/0p59dP96nGTzHvf2XIfqcBwBW7RM6JfOij8uZxyg5azfNaOOUW8SQy6H/hU91")
);
}
Estrategias de resguardo
Si tu objetivo de Baseline aún no admite los modificadores de solicitud de CSS url() (cross-origin(), referrer-policy() o integrity()), combina las políticas a nivel del documento con declaraciones de CSS en cascada para que los recursos secundarios sigan funcionando y protegidos:
- Prefiere HTML
<link rel="stylesheet">en lugar de CSS@importcuando se requiera SRI entre navegadores: Debido a quesubresource-integrityyreferrer-policyen<link rel="stylesheet">están ampliamente disponibles en todos los navegadores, carga hojas de estilo externas con<link rel="stylesheet" href="..." integrity="sha384-..." crossorigin="anonymous" referrerpolicy="no-referrer">en lugar de@importsiempre que se deba aplicar la verificación de integridad en todos los navegadores. - Confía en el encabezado
Referrer-Policyo el elemento<meta name="referrer">del paso 2 a nivel del documento: En los navegadores que no admitenreferrer-policy()dentro de CSSurl(), el encabezadoReferrer-Policy: strict-origin-when-cross-origino el elemento<meta name="referrer" content="strict-origin-when-cross-origin">a nivel del documento quitan automáticamente los parámetros de ruta de acceso y los parámetros de consulta sensibles en las solicitudes de CSS de origen cruzado. Declaraciones de CSS independientes en cascada para la mejora progresiva: Los navegadores que no reconocen un modificador dentro de
url("..." ...)descartan toda la declaración en el momento del análisis de CSS. Declara primero unurl("...")simple, seguido de una declaracióncross-origin()yreferrer-policy()(para los navegadores que admiten esos dos modificadores, pero nointegrity()), seguido de la declaración completa, incluidointegrity("sha384-..."):- Nunca coloques un
url()no verificado dentro de la misma lista de@font-facesrcseparada por comas: Escribirsrc: url("font.woff2" cross-origin(anonymous) integrity("sha384-...")), url("font.woff2");en un solo descriptorsrcomite la integridad de subrecursos en el entorno de ejecución. Si una CDN entrega una fuente alterada que no pasa la verificación deintegrity(), el navegador la trata como un error de carga y recurre automáticamente al segundourl("font.woff2")separado por comas. El uso de declaraciones independientes desrc:garantiza que los navegadores compatibles reemplacen la declaración no verificada en el momento del análisis de CSS y bloqueen las fuentes alteradas en el tiempo de ejecución.
@font-face { font-family: "BrandSans"; /* 1. Parse-time fallback for browsers without url() modifier support */ src: url("https://cdn.example.com/fonts@1.0.0/brand-sans.woff2") format("woff2"); /* 2. Parse-time fallback for browsers supporting cross-origin() and referrer-policy() */ src: url( "https://cdn.example.com/fonts@1.0.0/brand-sans.woff2" cross-origin(anonymous) referrer-policy(no-referrer) ) format("woff2"); /* 3. Full protection in browsers supporting cross-origin(), referrer-policy(), and integrity() */ src: url( "https://cdn.example.com/fonts@1.0.0/brand-sans.woff2" cross-origin(anonymous) referrer-policy(no-referrer) integrity("sha384-oqVuAfXRKap7fdgcCY5uykM6+R9GqQ8K/uxy9rx7HNQlGYl1kPzQho1wx4JwY8wC") ) format("woff2"); font-display: swap; } .verified-graphic { background-image: url("https://cdn.example.com/media@1.0.0/badge.svg"); background-image: url( "https://cdn.example.com/media@1.0.0/badge.svg" cross-origin(anonymous) referrer-policy(no-referrer) ); background-image: url( "https://cdn.example.com/media@1.0.0/badge.svg" cross-origin(anonymous) referrer-policy(no-referrer) integrity("sha384-Iw/0p59dP96nGTzHvf2XIfqcBwBW7RM6JfOij8uZxyg5azfNaOOUW8SQy6H/hU91") ); }- Nunca coloques un
Detecta la compatibilidad con el modificador
url()de CSS en JavaScript conCSS.supports():
export function getCssUrlModifierSupport() {
const supportsCrossOriginAndReferrer =
typeof CSS !== 'undefined' &&
typeof CSS.supports === 'function' &&
CSS.supports(
'background-image',
'url("https://example.com/a.svg" cross-origin(anonymous) referrer-policy(no-referrer))',
);
const supportsUrlIntegrity =
typeof CSS !== 'undefined' &&
typeof CSS.supports === 'function' &&
CSS.supports(
'background-image',
'url("https://example.com/a.svg" cross-origin(anonymous) referrer-policy(no-referrer) integrity("sha384-Iw/0p59dP96nGTzHvf2XIfqcBwBW7RM6JfOij8uZxyg5azfNaOOUW8SQy6H/hU91"))',
);
return { supportsCrossOriginAndReferrer, supportsUrlIntegrity };
}