Proteggi le risorse secondarie esterne con l'integrità delle risorse secondarie, CORS e il criterio del referrer

Quando carichi script, fogli di stile, caratteri o immagini da CDN di terze parti o origini esterne, verifica la loro integrità crittografica con Subresource Integrity (integrity in HTML e integrity() in CSS url()), configura la gestione delle credenziali multiorigine (crossorigin e cross-origin()) e limita le intestazioni Referer in uscita (referrerpolicy e referrer-policy()). Per la Content Security Policy a livello di documento e le liste consentite delle connessioni in uscita, consulta Sicurezza web e Limita le connessioni di rete in uscita con le liste consentite delle connessioni.

1. Genera digest di integrità crittografica per asset immutabili

La funzionalità Subresource Integrity (SRI) garantisce che il browser esegua o esegua il rendering di un asset esterno solo se i suoi byte corrispondono all'hash di crittografia dichiarato dalla tua applicazione.

  • Calcola i digest al momento della build o della release, mai da risposte non attendibili in tempo reale: genera gli hash di integrità durante la fase di build o release dai byte degli asset locali noti (o copia il digest ufficiale pubblicato dall'autore del pacchetto) e impostali come hardcoded nel codice HTML, CSS o JavaScript. Non recuperare mai un URL di terze parti nel browser in fase di runtime per calcolare il proprio hash integrity, che considera attendibili i byte restituiti dal server remoto e annulla l'SRI.
  • Utilizza sha384- per impostazione predefinita: formatta i metadati di integrità come <algorithm>-<base64-digest> utilizzando sha384- (impostazione predefinita consigliata), sha512- o sha256-.
  • Fissa le risorse secondarie esterne a URL immutabili con versione: fai sempre riferimento ad asset di terze parti utilizzando URL immutabili, con versione fissa o indirizzati ai contenuti (come https://cdn.example.com/ui@2.4.0/app.min.js) anziché URL mobili mutabili (latest/app.min.js), che espongono il tuo sito a modifiche silenziose della catena di fornitura e interrompono l'SRI non appena il file remoto viene aggiornato. Se devi caricare uno script di terze parti modificabile che si aggiorna in loco (ad esempio un tag di analisi del fornitore), ometti integrity su quell'URL e limitane l'accesso con Content Security Policy (strict-dynamic/nonce) o con il sandboxing degli iframe.
  • Separa più digest con spazi quando un endpoint può pubblicare più payload validi: tutti i browser che supportano SRI implementano sha256-, sha384- e sha512-. Se un URL può legittimamente restituire uno dei diversi payload noti (ad esempio durante un test A/B, un'implementazione CDN in più fasi o una build con negoziazione dei contenuti), elenca più digest dello stesso algoritmo separati da spazi vuoti (ad esempio integrity="sha384-variantA... sha384-variantB..."). Il browser accetta la risposta se corrisponde a qualsiasi digest elencato dell'algoritmo più efficace presente.
  • Pubblica risorse secondarie multiorigine con intestazioni CORS: i browser possono verificare l'integrità di una risposta multiorigine solo quando è leggibile da CORS (Access-Control-Allow-Origin: * per le richieste anonime o l'origine esatta della richiesta con Access-Control-Allow-Credentials: true per le richieste con credenziali).
# 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. Script, fogli di stile e precaricamenti HTML sicuri

Imposta un'intestazione della risposta HTTP Referrer-Policy: strict-origin-when-cross-origin a livello di documento (o includi <meta name="referrer" content="strict-origin-when-cross-origin"> in <head>) come valore predefinito di riferimento, in modo che qualsiasi sottorisorsa o CSS url() che omette un modificatore del referrer per richiesta rimuova comunque i parametri di query e percorso sensibili nelle richieste multiorigine.

Poi, sugli elementi esterni <script>, <link rel="stylesheet">, <link rel="preload"> e <link rel="modulepreload">, imposta sempre integrity, crossorigin e referrerpolicy insieme:

  • integrity="sha384-...": blocca l'esecuzione o l'applicazione della risorsa se i byte della risposta non corrispondono al digest previsto (per un <link rel="stylesheet"> bloccato, linkElement.sheet rimane null e viene attivato un evento error).
  • crossorigin="anonymous" (impostazione predefinita consigliata per gli asset CDN pubblici) o crossorigin="use-credentials": obbligatorio ogni volta che integrity è impostato su una risorsa multiorigine. Se ometti crossorigin, il browser recupera la risorsa in modalità no-cors (generando una risposta opaca che non può essere verificata tramite hash) e blocca la risorsa.
  • referrerpolicy="no-referrer" (impostazione predefinita consigliata per le CDN di terze parti) o referrerpolicy="strict-origin-when-cross-origin": esegue l'override della policy del referrer del documento per impedire la divulgazione di informazioni sull'origine o sul percorso nell'intestazione della richiesta Referer all'host di terze parti.
<!-- 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>

Quando recuperi risorse secondarie esterne in modo programmatico con fetch(), passa le opzioni integrity, mode, credentials e referrerPolicy corrispondenti (una mancata corrispondenza del digest rifiuta la promessa 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. Proteggi immagini, caratteri e fogli di stile importati CSS con i modificatori url()

I modificatori di richiesta CSS url() (cross-origin(), referrer-policy() e integrity()) controllano la modalità di recupero, l'intestazione del referrer e la verifica dell'integrità delle risorse secondarie delle risorse caricate tramite CSS, incluse le regole background-image, content, mask-image, border-image-source, filter, @font-face src e @import, direttamente dal CSS:

Funzione di modifica Sintassi degli argomenti e valori consentiti Finalità
cross-origin(<ident>) Parola chiave senza virgolette: anonymous (impostazione predefinita consigliata) o use-credentials Recupera la risorsa CSS utilizzando CORS e controlla se vengono inviate le credenziali. Includilo sempre insieme a integrity() nelle risorse CSS multiorigine.
referrer-policy(<ident>) Parola chiave senza virgolette: no-referrer (valore predefinito consigliato per gli asset di terze parti), strict-origin-when-cross-origin, origin, same-origin, strict-origin, origin-when-cross-origin, no-referrer-when-downgrade o unsafe-url Esegue l'override della policy del referrer del documento per questa specifica richiesta CSS.
integrity("<string>") Stringa tra virgolette: "sha384-<base64>" (o digest separati da spazi "sha384-... sha384-...") Verifica la risorsa CSS recuperata rispetto al digest crittografico previsto prima di eseguirne il rendering o di applicarla.

Segui queste regole di sintassi e sicurezza CSS durante la creazione dei modificatori url():

  • Racchiudi la stringa dell'URL tra virgolette all'interno di url("..."): i modificatori di richiesta sono validi solo quando l'URL all'interno di url(...) è una <string> tra virgolette (gli URL senza virgolette non possono utilizzare i modificatori).
  • Specifica ogni tipo di modificatore al massimo una volta per url(): puoi combinare cross-origin(), referrer-policy() e integrity() in qualsiasi ordine all'interno di url("..." ...), ma la ripetizione dello stesso tipo di modificatore due volte (ad esempio due modificatori integrity()) non è valida. Per fornire più hash, separali con uno spazio all'interno di una singola stringa integrity("sha384-... sha384-...").
  • Abbina sempre integrity() multiorigine con cross-origin(anonymous): mentre @font-face src e mask-image recuperano già in modalità cors anonima per impostazione predefinita, background-image, content, border-image-source e @import utilizzano per impostazione predefinita la modalità no-cors (producendo una risposta opaca che non può essere verificata tramite hash) e l'SVG esterno filter utilizza per impostazione predefinita la modalità same-origin (bloccando completamente gli URL multiorigine). L'inclusione di cross-origin(anonymous) (o cross-origin(use-credentials)) garantisce una verifica CORS coerente in ogni proprietà CSS.
  • Posiziona i modificatori all'interno di url("...") nelle regole @import e @font-face: mantieni cross-origin(), referrer-policy() e integrity() all'interno di url("..." ...), mantenendo i descrittori dei caratteri come format("woff2") e tech(...) all'esterno di url(...).
@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")
  );
}

Strategie di riserva

Se il tuo target Baseline non supporta ancora i modificatori di richiesta CSS url() (cross-origin(), referrer-policy() o integrity()), combina le policy a livello di documento con le dichiarazioni CSS in sequenza in modo che le risorse secondarie rimangano funzionali e protette:

  1. Preferisci l'HTML <link rel="stylesheet"> al CSS @import quando è richiesto l'SRI cross-browser: poiché subresource-integrity e referrer-policy su <link rel="stylesheet"> sono ampiamente disponibili in tutti i browser, carica i fogli di stile esterni con <link rel="stylesheet" href="..." integrity="sha384-..." crossorigin="anonymous" referrerpolicy="no-referrer"> anziché @import ogni volta che è necessario applicare la verifica dell'integrità in ogni browser.
  2. Affidati all'intestazione Referrer-Policy o all'elemento <meta name="referrer"> a livello di documento del passaggio 2: nei browser che non supportano referrer-policy() all'interno del CSS url(), l'intestazione Referrer-Policy: strict-origin-when-cross-origin o l'elemento <meta name="referrer" content="strict-origin-when-cross-origin"> a livello di documento rimuove automaticamente i parametri di query e i percorsi sensibili nelle richieste CSS multiorigine.
  3. Utilizza dichiarazioni CSS separate per il potenziamento progressivo: i browser che non riconoscono un modificatore all'interno di url("..." ...) ignorano l'intera dichiarazione al momento dell'analisi del CSS. Dichiara prima un url("...") semplice, seguito da una dichiarazione cross-origin() e referrer-policy() (per i browser che supportano questi due modificatori, ma non integrity()), seguita dalla dichiarazione completa, incluso integrity("sha384-..."):

    • Non inserire mai un url() non verificato nello stesso elenco src @font-face separato da virgole: la scrittura di src: url("font.woff2" cross-origin(anonymous) integrity("sha384-...")), url("font.woff2"); in un singolo descrittore src bypassa l'integrità delle risorse secondarie in fase di runtime. Se una CDN fornisce un font manomesso che non supera il controllo integrity(), il browser lo considera un errore di caricamento e torna automaticamente al secondo url("font.woff2") separato da virgole. L'utilizzo di dichiarazioni separate src: garantisce che i browser supportati sovrascrivano la dichiarazione non verificata al momento dell'analisi del CSS e blocchino i caratteri manomessi in fase di runtime.
    @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")
      );
    }
    
  4. Rileva il supporto del modificatore CSS url() in JavaScript con CSS.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 };
}