Als je scripts, stylesheets, lettertypen of afbeeldingen laadt van CDN's van derden of externe bronnen, controleer je de cryptografische integriteit met Subresource Integrity (integrity in HTML en integrity() in CSS url()), stel je de verwerking van cross-origin-inloggegevens in (crossorigin en cross-origin()) en beperk je uitgaande Referer-headers (referrerpolicy en referrer-policy()). Ga naar Webbeveiliging en Uitgaande netwerkverbindingen beperken met toelatingslijsten voor verbindingen voor Content Security Policy op documentniveau en toelatingslijsten voor uitgaande verbindingen.
1. Cryptografische integriteitsdigests genereren voor onveranderbare items
Subresource Integrity (SRI) zorgt ervoor dat de browser een externe component alleen uitvoert of rendert als de bytes overeenkomen met de cryptografische hash-waarde die door je app is gedeclareerd.
- Bereken digests op het moment van build of release, nooit op basis van live niet-vertrouwde reacties: Genereer integriteitshashes tijdens de build- of releasestap op basis van bekende goede lokale assetbytes (of kopieer de officiële digest die is gepubliceerd door de auteur van het pakket) en hardcodeer deze in je HTML, CSS of JavaScript. Haal nooit een URL van derden op in de browser tijdens de runtime om de eigen
integrity-hash te berekenen. Hierdoor worden de bytes die de externe server retourneert vertrouwd en wordt SRI omzeild. sha384-standaard gebruiken: Maak de integriteitsmetadata op als<algorithm>-<base64-digest>metsha384-(aanbevolen standaard),sha512-ofsha256-.- Zet externe subbronnen vast aan onveranderlijke URL's met versies: Verwijs altijd naar componenten van derden met onveranderlijke URL's met versies of contentgeadresseerde URL's (zoals
https://cdn.example.com/ui@2.4.0/app.min.js) in plaats van veranderlijke zwevende URL's (latest/app.min.js). Deze stellen je site bloot aan ongemerkte wijzigingen in de toeleveringsketen en verbreken SRI zodra het externe bestand wordt geüpdatet. Als je een veranderlijk script van derden moet laden dat ter plekke wordt geüpdatet (zoals een analysetag van een leverancier), laat jeintegrityweg in die URL en beperk je deze met Content Security Policy (strict-dynamic/nonces) of iframe-sandboxing. - Scheid meerdere digests met een spatie als een eindpunt meerdere geldige payloads kan leveren: Alle browsers die SRI ondersteunen, implementeren
sha256-,sha384-ensha512-. Als een URL rechtmatig een van de verschillende bekende goede payloads kan retourneren (zoals tijdens een A/B-test, gefaseerde CDN-implementatie of build waarover is onderhandeld over content), vermeld je meerdere samenvattingen van hetzelfde algoritme, gescheiden door witruimte (bijvoorbeeldintegrity="sha384-variantA... sha384-variantB..."). De browser accepteert de reactie als deze overeenkomt met een vermelde samenvatting van het sterkste aanwezige algoritme. - Cross-origin subresources leveren met CORS-headers: Browsers kunnen de integriteit van een cross-origin reactie alleen verifiëren als deze CORS-leesbaar is (
Access-Control-Allow-Origin: *voor anonieme verzoeken of de exacte bron van het verzoek metAccess-Control-Allow-Credentials: truevoor verzoeken met inloggegevens).
# 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. Beveiligde HTML-scripts, stylesheets en preloads
Stel een HTTP-reactieheader Referrer-Policy: strict-origin-when-cross-origin op documentniveau in (of neem <meta name="referrer" content="strict-origin-when-cross-origin"> op in <head>) als basisstandaard, zodat elke subbron of CSS url() die een verwijzingsmodifier per verzoek weglaat, nog steeds gevoelige pad- en queryparameters verwijdert bij cross-origin-verzoeken.
Stel daarna voor externe <script>-, <link rel="stylesheet">-, <link rel="preload">- en <link rel="modulepreload">-elementen altijd integrity, crossorigin en referrerpolicy samen in:
integrity="sha384-...": Blokkeert de uitvoering of toepassing van de bron als de reactiebytes niet overeenkomen met de verwachte digest (voor een geblokkeerde<link rel="stylesheet">blijftlinkElement.sheetnullen wordt eenerror-gebeurtenis geactiveerd).crossorigin="anonymous"(aanbevolen standaard voor openbare CDN-items) ofcrossorigin="use-credentials": Vereist alsintegrityis ingesteld voor een cross-origin-resource. Als jecrossoriginweglaat, haalt de browser de bron op in deno-cors-modus (wat een ondoorzichtige reactie oplevert die niet kan worden geverifieerd met een hash) en blokkeert de bron.referrerpolicy="no-referrer"(aanbevolen standaardinstelling voor CDN's van derden) ofreferrerpolicy="strict-origin-when-cross-origin": Overschrijft het documentverwijzingsbeleid om te voorkomen dat er oorsprong- of padinformatie in deReferer-verzoekheader naar de host van derden wordt gelekt.
<!-- 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>
Als je externe subbronnen programmatisch ophaalt met fetch(), geef je de overeenkomende opties integrity, mode, credentials en referrerPolicy door (een digest-mismatch wijst de promise af met 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. Beveilig CSS-afbeeldingen, lettertypen en geïmporteerde stylesheets met url()-modifiers
CSS url()-verzoekmodifiers (cross-origin(), referrer-policy() en integrity()) bepalen de ophaalmodus, de referrer-header en de Subresource Integrity-verificatie van door CSS geladen bronnen, waaronder de regels background-image, content, mask-image, border-image-source, filter, @font-face src en @import, rechtstreeks vanuit CSS:
| Functie voor modificatie | Argumentsyntaxis en toegestane waarden | Doel |
|---|---|---|
cross-origin(<ident>) |
Zoekwoord zonder aanhalingstekens: anonymous (aanbevolen standaard) of use-credentials |
Haalt de CSS-bron op met CORS en bepaalt of inloggegevens worden gestuurd. Voeg dit altijd toe naast integrity() in cross-origin CSS-bronnen. |
referrer-policy(<ident>) |
Zoekwoord zonder aanhalingstekens: no-referrer (aanbevolen standaard voor componenten van derden), strict-origin-when-cross-origin, origin, same-origin, strict-origin, origin-when-cross-origin, no-referrer-when-downgrade of unsafe-url |
Overschrijft het documentverwijzingsbeleid voor dit specifieke CSS-verzoek. |
integrity("<string>") |
Tekenreeks tussen aanhalingstekens: "sha384-<base64>" (of door spaties gescheiden overzichten "sha384-... sha384-...") |
Verifieert de opgehaalde CSS-bron met de verwachte cryptografische digest voordat deze wordt gerenderd of toegepast. |
Volg deze CSS-syntaxis- en beveiligingsregels als je url()-modifiers maakt:
- Plaats de URL-tekenreeks tussen aanhalingstekens in
url("..."): Verzoekmodifiers zijn alleen geldig als de URL inurl(...)een<string>tussen aanhalingstekens is (URL's zonder aanhalingstekens kunnen geen modifiers gebruiken). - Voer elk type modifier maximaal één keer per
url()in: U kuntcross-origin(),referrer-policy()enintegrity()in elke volgorde combineren binnenurl("..." ...), maar als u hetzelfde type modifier 2 keer herhaalt (zoals 2integrity()-modifiers), is dit ongeldig. Als je meerdere hashes wilt invoeren, scheid je ze met een spatie in éénintegrity("sha384-... sha384-...")-tekenreeks. - Koppel cross-origin
integrity()altijd aancross-origin(anonymous): Hoewel@font-facesrcenmask-imagestandaard al worden opgehaald in de anoniemecors-modus, wordenbackground-image,content,border-image-sourceen@importstandaard ingesteld op deno-cors-modus (wat een ondoorzichtige reactie oplevert die niet kan worden geverifieerd met een hash) en wordt externe SVGfilterstandaard ingesteld op desame-origin-modus (waardoor cross-origin URL's direct worden geblokkeerd). Als jecross-origin(anonymous)(ofcross-origin(use-credentials)) opneemt, zorg je voor consistente CORS-verificatie voor elke CSS-property. - Plaats modifiers binnen
url("...")in@import- en@font-face-regels: Houdcross-origin(),referrer-policy()enintegrity()binnenurl("..." ...)en houd lettertypebeschrijvingen zoalsformat("woff2")entech(...)buitenurl(...).
@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")
);
}
Reservestrategieën
Als je Baseline-doel nog geen CSS url()-verzoekmodifiers (cross-origin(), referrer-policy() of integrity()) ondersteunt, combineer je beleid op documentniveau met trapsgewijze CSS-declaraties, zodat subbronnen functioneel en beschermd blijven:
- Geef de voorkeur aan HTML
<link rel="stylesheet">boven CSS@importals SRI voor meerdere browsers is vereist: Omdatsubresource-integrityenreferrer-policyop<link rel="stylesheet">breed beschikbaar zijn in alle browsers, laad je externe stylesheets met<link rel="stylesheet" href="..." integrity="sha384-..." crossorigin="anonymous" referrerpolicy="no-referrer">in plaats van@importals integriteitsverificatie moet worden afgedwongen in elke browser. - Vertrouw op de
Referrer-Policy-header of<meta name="referrer">op documentniveau uit stap 2: In browsers diereferrer-policy()niet ondersteunen in CSSurl(), verwijdert deReferrer-Policy: strict-origin-when-cross-origin-header of het<meta name="referrer" content="strict-origin-when-cross-origin">-element op documentniveau automatisch gevoelige pad- en queryparameters in cross-origin CSS-verzoeken. Cascadeer afzonderlijke CSS-declaraties voor progressive enhancement: Browsers die een modifier in
url("..." ...)niet herkennen, negeren de hele declaratie tijdens de CSS-parse. Declareer eerst een gewoneurl("..."), gevolgd door eencross-origin()- enreferrer-policy()-declaratie (voor browsers die deze 2 modifiers ondersteunen, maar nietintegrity()), gevolgd door de volledige declaratie inclusiefintegrity("sha384-..."):- Zet nooit een niet-geverifieerde
url()in dezelfde door komma's gescheiden@font-facesrc-lijst: Als jesrc: url("font.woff2" cross-origin(anonymous) integrity("sha384-...")), url("font.woff2");in éénsrc-descriptor schrijft, wordt de integriteit van subbronnen tijdens de runtime omzeild. Als een CDN een gemanipuleerd lettertype levert dat deintegrity()-check niet doorstaat, behandelt de browser dit als een laadfout en valt deze automatisch terug op de 2e door komma's gescheidenurl("font.woff2"). Als je afzonderlijkesrc:-declaraties gebruikt, overschrijven ondersteunende browsers de niet-geverifieerde declaratie tijdens de CSS-parse en blokkeren ze gemanipuleerde lettertypen tijdens de 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") ); }- Zet nooit een niet-geverifieerde
Functiedetectie van ondersteuning voor de CSS-modifier
url()in JavaScript metCSS.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 };
}