Externe Unterressourcen mit Unterressourcenintegrität, CORS und Referrer-Richtlinie sichern

Wenn Sie Skripts, Stylesheets, Schriftarten oder Bilder von Drittanbieter-CDNs oder externen Quellen laden, sollten Sie deren kryptografische Integrität mit Subresource Integrity (integrity in HTML und integrity() in CSS url()) überprüfen, die ursprungsübergreifende Verarbeitung von Anmeldedaten (crossorigin und cross-origin()) konfigurieren und ausgehende Referer-Header (referrerpolicy und referrer-policy()) einschränken. Informationen zu Content Security Policies auf Dokumentebene und Zulassungslisten für ausgehende Verbindungen finden Sie unter Web Security und Ausgehende Netzwerkverbindungen mit Zulassungslisten für Verbindungen einschränken.

1. Kryptografische Integritäts-Digests für unveränderliche Assets generieren

Subresource Integrity (SRI) sorgt dafür, dass der Browser ein externes Asset nur dann ausführt oder rendert, wenn seine Bytes mit dem von Ihrer Anwendung deklarierten kryptografischen Hash übereinstimmen.

  • Prüfsummen während des Build- oder Release-Prozesses berechnen, niemals aus nicht vertrauenswürdigen Live-Antworten: Generieren Sie Integritäts-Hashes während des Build- oder Release-Schritts aus bekannten, fehlerfreien lokalen Asset-Bytes (oder kopieren Sie die offizielle Prüfsumme, die vom Paketautor veröffentlicht wurde) und codieren Sie sie fest in Ihren HTML-, CSS- oder JavaScript-Code. Rufen Sie niemals zur Laufzeit eine Drittanbieter-URL im Browser ab, um den eigenen integrity-Hash zu berechnen. Dadurch wird allen vom Remote-Server zurückgegebenen Bytes vertraut und SRI wird umgangen.
  • Standardmäßig sha384- verwenden: Integritätsmetadaten als <algorithm>-<base64-digest> mit sha384- (empfohlener Standard), sha512- oder sha256- formatieren.
  • Externe Unterressourcen an unveränderliche, versionierte URLs anpinnen: Verweisen Sie immer auf Drittanbieter-Assets mit unveränderlichen, versionsgebundenen oder inhaltsadressierten URLs (z. B. https://cdn.example.com/ui@2.4.0/app.min.js) anstatt mit veränderlichen, dynamischen URLs (latest/app.min.js). Letztere setzen Ihre Website unbemerkten Änderungen in der Lieferkette aus und beeinträchtigen die SRI, sobald die Remote-Datei aktualisiert wird. Wenn Sie ein veränderliches Drittanbieter-Script laden müssen, das direkt aktualisiert wird (z. B. ein Analyse-Tag eines Anbieters), lassen Sie integrity für diese URL weg und schränken Sie sie stattdessen mit der Content Security Policy (strict-dynamic/Nonces) oder iFrame-Sandboxing ein.
  • Trennen Sie mehrere Digests durch Leerzeichen, wenn ein Endpunkt mehrere gültige Nutzlasten bereitstellen kann: Alle Browser, die SRI unterstützen, implementieren sha256-, sha384- und sha512-. Wenn eine URL legitimerweise eine von mehreren bekannten, fehlerfreien Nutzlasten zurückgeben kann (z. B. während eines A/B-Tests, eines gestaffelten CDN-Rollouts oder eines Builds mit Inhaltsverhandlung), listen Sie mehrere Digests desselben Algorithmus durch Leerzeichen getrennt auf (z. B. integrity="sha384-variantA... sha384-variantB..."). Der Browser akzeptiert die Antwort, wenn sie mit einem der aufgeführten Digests des stärksten vorhandenen Algorithmus übereinstimmt.
  • Ursprungsübergreifende Unterressourcen mit CORS-Headern bereitstellen: Browser können die Integrität einer ursprungsübergreifenden Antwort nur überprüfen, wenn sie CORS-lesbar ist (Access-Control-Allow-Origin: * für anonyme Anfragen oder der genaue anfragende Ursprung mit Access-Control-Allow-Credentials: true für Anfragen mit Anmeldedaten).
# 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. Sichere HTML-Skripts, Stylesheets und Preloads

Legen Sie einen HTTP-Antwortheader Referrer-Policy: strict-origin-when-cross-origin auf Dokumentebene fest (oder fügen Sie <meta name="referrer" content="strict-origin-when-cross-origin"> in <head> ein) als Standard-Baseline, damit alle Unterressourcen oder CSS-url(), die einen Referrer-Modifikator pro Anfrage weglassen, weiterhin sensible Pfad- und Suchparameter bei ursprungsübergreifenden Anfragen entfernen.

Legen Sie dann für die externen Elemente <script>, <link rel="stylesheet">, <link rel="preload"> und <link rel="modulepreload"> immer integrity, crossorigin und referrerpolicy zusammen fest:

  • integrity="sha384-...": Blockiert die Ausführung oder Anwendung der Ressource, wenn die Antwortbytes nicht mit dem erwarteten Digest übereinstimmen. Bei einer blockierten <link rel="stylesheet"> bleibt linkElement.sheet auf null und ein error-Ereignis wird ausgelöst.
  • crossorigin="anonymous" (empfohlener Standardwert für öffentliche CDN-Assets) oder crossorigin="use-credentials": Erforderlich, wenn integrity für eine ursprungsübergreifende Ressource festgelegt ist. Wenn Sie crossorigin weglassen, ruft der Browser die Ressource im no-cors-Modus ab (was zu einer intransparenten Antwort führt, die nicht per Hash-Wert überprüft werden kann) und blockiert die Ressource.
  • referrerpolicy="no-referrer" (empfohlener Standard für CDNs von Drittanbietern) oder referrerpolicy="strict-origin-when-cross-origin": Überschreibt die Referrer-Richtlinie des Dokuments, um zu verhindern, dass Ursprungs- oder Pfadinformationen im Anfrageheader Referer an den Drittanbieter-Host weitergegeben werden.
<!-- 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>

Wenn Sie externe Unterressourcen programmatisch mit fetch() abrufen, übergeben Sie die entsprechenden Optionen integrity, mode, credentials und referrerPolicy. Bei einer Abweichung des Digests wird das Promise mit TypeError abgelehnt:

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. CSS-Bilder, ‑Schriftarten und importierte Stylesheets mit url()-Modifikatoren sichern

Mit den CSS-url()-Anfragemodifikatoren (cross-origin(), referrer-policy() und integrity()) können Sie den Abrufmodus, den Referrer-Header und die Subresource Integrity-Prüfung von über CSS geladenen Ressourcen direkt über CSS steuern. Dazu gehören auch die Regeln background-image, content, mask-image, border-image-source, filter, @font-face, src und @import:

Modifikatorfunktion Argumentsyntax und zulässige Werte Zweck
cross-origin(<ident>) Keyword ohne Anführungszeichen: anonymous (empfohlener Standardwert) oder use-credentials Ruft die CSS-Ressource mithilfe von CORS ab und steuert, ob Anmeldedaten gesendet werden. Fügen Sie sie immer zusammen mit integrity() in ursprungsübergreifenden CSS-Ressourcen ein.
referrer-policy(<ident>) Keyword ohne Anführungszeichen: no-referrer (empfohlener Standardwert für Drittanbieter-Assets), strict-origin-when-cross-origin, origin, same-origin, strict-origin, origin-when-cross-origin, no-referrer-when-downgrade oder unsafe-url Überschreibt die Referrer-Richtlinie des Dokuments für diese spezifische CSS-Anfrage.
integrity("<string>") String in Anführungszeichen: "sha384-<base64>" (oder durch Leerzeichen getrennte Digests "sha384-... sha384-...") Prüft die abgerufene CSS-Ressource vor dem Rendern oder Anwenden anhand des erwarteten kryptografischen Digests.

Beachten Sie beim Erstellen von url()-Modifikatoren die folgenden CSS-Syntax- und Sicherheitsregeln:

  • Setzen Sie den URL-String in url("...") in Anführungszeichen: Anfragemodifikatoren sind nur gültig, wenn die URL in url(...) ein <string> in Anführungszeichen ist. URLs ohne Anführungszeichen können keine Modifikatoren verwenden.
  • Geben Sie jeden Modifikatortyp höchstens einmal pro url() an: Sie können cross-origin(), referrer-policy() und integrity() in beliebiger Reihenfolge innerhalb von url("..." ...) kombinieren. Es ist jedoch nicht zulässig, denselben Modifikatortyp zweimal zu wiederholen (z. B. zwei integrity()-Modifikatoren). Wenn Sie mehrere Hashes angeben möchten, trennen Sie sie innerhalb eines einzelnen integrity("sha384-... sha384-...")-Strings durch Leerzeichen.
  • Ursprungsübergreifende integrity() immer mit cross-origin(anonymous) kombinieren: Während @font-face, src und mask-image standardmäßig im anonymen cors-Modus abgerufen werden, werden background-image, content, border-image-source und @import standardmäßig im no-cors-Modus abgerufen (was zu einer intransparenten Antwort führt, die nicht per Hash-Wert überprüft werden kann). Externe SVG-filter werden standardmäßig im same-origin-Modus abgerufen (was ursprungsübergreifende URLs direkt blockiert). Wenn Sie cross-origin(anonymous) (oder cross-origin(use-credentials)) einfügen, wird eine konsistente CORS-Überprüfung für alle CSS-Properties sichergestellt.
  • Modifikatoren in url("...") in @import- und @font-face-Regeln platzieren: Behalten Sie cross-origin(), referrer-policy() und integrity() in url("..." ...) bei, während Sie Schriftdeskriptoren wie format("woff2") und tech(...) außerhalb von url(...) beibehalten.
@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")
  );
}

Fallback-Strategien

Wenn Ihr Baseline-Ziel noch keine CSS-url()-Anfragemodifikatoren (cross-origin(), referrer-policy() oder integrity()) unterstützt, kombinieren Sie Richtlinien auf Dokumentebene mit kaskadierenden CSS-Deklarationen, damit Unterressourcen funktionsfähig und geschützt bleiben:

  1. HTML-<link rel="stylesheet"> gegenüber CSS-@import bevorzugen, wenn browserübergreifende SRI erforderlich ist: Da subresource-integrity und referrer-policy für <link rel="stylesheet"> in allen Browsern weitgehend verfügbar sind, sollten Sie externe Stylesheets mit <link rel="stylesheet" href="..." integrity="sha384-..." crossorigin="anonymous" referrerpolicy="no-referrer"> anstelle von @import laden, wenn die Integritätsprüfung in jedem Browser erzwungen werden muss.
  2. Auf den Header Referrer-Policy oder das Element <meta name="referrer"> auf Dokumentebene aus Schritt 2 verlassen: In Browsern, die referrer-policy() in CSS-url() nicht unterstützen, werden durch den Header Referrer-Policy: strict-origin-when-cross-origin oder das Element <meta name="referrer" content="strict-origin-when-cross-origin"> auf Dokumentebene automatisch vertrauliche Pfad- und Suchparameter bei ursprungsübergreifenden CSS-Anfragen entfernt.
  3. Separate CSS-Deklarationen für die progressive Verbesserung kaskadieren: Browser, die einen Modifikator in url("..." ...) nicht erkennen, verwerfen die gesamte Deklaration beim Parsen des CSS. Deklarieren Sie zuerst ein einfaches url("..."), gefolgt von einer cross-origin()- und referrer-policy()-Deklaration (für Browser, die diese beiden Modifikatoren, aber nicht integrity() unterstützen), gefolgt von der vollständigen Deklaration einschließlich integrity("sha384-..."):

    • Fügen Sie niemals eine nicht verifizierte url() in dieselbe kommagetrennte @font-face-src-Liste ein: Wenn Sie src: url("font.woff2" cross-origin(anonymous) integrity("sha384-...")), url("font.woff2"); in einem einzelnen src-Deskriptor angeben, wird die Subresource Integrity zur Laufzeit umgangen. Wenn ein CDN eine manipulierte Schriftart bereitstellt, die die integrity()-Prüfung nicht besteht, behandelt der Browser dies als Ladefehler und greift automatisch auf die zweite kommagetrennte url("font.woff2") zurück. Wenn Sie separate src:-Deklarationen verwenden, überschreiben unterstützende Browser die nicht verifizierte Deklaration beim Parsen des CSS und blockieren manipulierte Schriftarten zur Laufzeit.
    @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. Feature-Erkennung für die Unterstützung des CSS-Modifikators url() in JavaScript mit 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 };
}