Podczas wczytywania skryptów, arkuszy stylów, czcionek lub obrazów z sieci CDN innych firm lub źródeł zewnętrznych sprawdź ich integralność kryptograficzną za pomocą integralności zasobów podrzędnych (integrity w HTML i integrity() w CSS url()), skonfiguruj obsługę danych logowania ze współdzieleniem (crossorigin i cross-origin()) i ogranicz wychodzące nagłówki Referer (referrerpolicy i referrer-policy()). Informacje o (standard) Content Security Policy na poziomie dokumentu i listach dozwolonych połączeń wychodzących znajdziesz w artykułach o bezpieczeństwie w internecie i ograniczaniu wychodzących połączeń sieciowych za pomocą list dozwolonych połączeń.
1. Generowanie kryptograficznych podsumowań integralności dla zasobów niezmiennych
Subresource Integrity (SRI) zapewnia, że przeglądarka wykonuje lub renderuje zasób zewnętrzny tylko wtedy, gdy jego bajty są zgodne z hashem kryptograficznym zadeklarowanym przez aplikację.
- Obliczaj skróty podczas kompilacji lub publikowania, nigdy na podstawie niezweryfikowanych odpowiedzi na żywo: generuj skróty integralności podczas kompilacji lub publikowania na podstawie znanych, prawidłowych bajtów zasobów lokalnych (lub skopiuj oficjalny skrót opublikowany przez autora pakietu) i zakoduj je na stałe w kodzie HTML, CSS lub JavaScript. Nigdy nie pobieraj adresu URL innej firmy w przeglądarce w czasie działania, aby obliczyć własny skrót
integrity, który ufa wszystkim bajtom zwracanym przez serwer zdalny i uniemożliwia działanie SRI. - Domyślnie używaj
sha384-: formatuj metadane integralności jako<algorithm>-<base64-digest>, używającsha384-(zalecane ustawienie domyślne),sha512-lubsha256-. - Przypnij zewnętrzne zasoby podrzędne do niezmiennych adresów URL z numerem wersji: zawsze odwołuj się do zasobów innych firm, używając niezmiennych adresów URL z numerem wersji lub adresów URL z zawartością (np.
https://cdn.example.com/ui@2.4.0/app.min.js), a nie zmiennych adresów URL (latest/app.min.js), które narażają witrynę na niezauważalne zmiany w łańcuchu dostaw i powodują błędy SRI, gdy tylko plik zdalny zostanie zaktualizowany. Jeśli musisz wczytać zmienny skrypt innej firmy, który aktualizuje się na miejscu (np. tag analityczny dostawcy), pomińintegrityw tym adresie URL i zamiast tego ogranicz go za pomocą (standard) Content Security Policy (strict-dynamic/ liczby jednorazowe) lub piaskownicy elementu iframe. - Oddziel spacjami wiele skrótów, gdy punkt końcowy może obsługiwać wiele prawidłowych ładunków: wszystkie przeglądarki obsługujące SRI implementują
sha256-,sha384-isha512-. Jeśli adres URL może zgodnie z prawem zwracać jeden z kilku znanych prawidłowych ładunków (np. podczas testu A/B, etapowego wdrażania CDN lub kompilacji z negocjacją treści), podaj kilka skrótów tego samego algorytmu rozdzielonych spacjami (np.integrity="sha384-variantA... sha384-variantB..."). Przeglądarka zaakceptuje odpowiedź, jeśli będzie ona zgodna z dowolnym podanym skrótem najsilniejszego obecnego algorytmu. - Udostępniaj zasoby podrzędne ze współdzieleniem z nagłówkami CORS: przeglądarki mogą weryfikować integralność odpowiedzi ze współdzieleniem tylko wtedy, gdy jest ona możliwa do odczytania przez CORS (
Access-Control-Allow-Origin: *w przypadku żądań anonimowych lub dokładne źródło żądania zAccess-Control-Allow-Credentials: truew przypadku żądań z danymi logowania).
# 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. Bezpieczne skrypty HTML, arkusze stylów i wstępne wczytywanie
Ustaw nagłówek odpowiedzi HTTP Referrer-Policy: strict-origin-when-cross-origin na poziomie dokumentu (lub uwzględnij <meta name="referrer" content="strict-origin-when-cross-origin"> w <head>) jako podstawową wartość domyślną, aby każdy zasób podrzędny lub CSS url(), który pomija modyfikator strony odsyłającej dla żądania, nadal usuwał poufne parametry ścieżki i zapytania w żądaniach z innych domen.
Następnie w zewnętrznych elementach <script>, <link rel="stylesheet">, <link rel="preload"> i <link rel="modulepreload"> zawsze ustawiaj razem integrity, crossorigin i referrerpolicy:
integrity="sha384-...": blokuje wykonanie lub zastosowanie zasobu, jeśli bajty odpowiedzi nie pasują do oczekiwanego skrótu (w przypadku zablokowanego elementu<link rel="stylesheet">elementlinkElement.sheetpozostajenulli wywoływane jest zdarzenieerror).crossorigin="anonymous"(zalecana wartość domyślna dla zasobów publicznej sieci CDN) lubcrossorigin="use-credentials": wymagane, gdy parametrintegrityjest ustawiony w zasobie współdzielonym. Pominięciecrossoriginpowoduje, że przeglądarka pobiera zasób w trybieno-cors(co powoduje nieprzezroczystą odpowiedź, której nie można zweryfikować za pomocą haszowania) i blokuje zasób.referrerpolicy="no-referrer"(zalecane ustawienie domyślne dla sieci CDN innych firm) lubreferrerpolicy="strict-origin-when-cross-origin": zastępuje zasady dotyczące dokumentu odsyłającego, aby zapobiec wyciekowi informacji o źródle lub ścieżce w nagłówku żądaniaRefererdo hosta zewnętrznego.
<!-- 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>
Podczas pobierania zewnętrznych zasobów podrzędnych za pomocą fetch() przekaż pasujące opcje integrity, mode, credentials i referrerPolicy (niezgodność skrótu odrzuca obietnicę z 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. Zabezpieczanie obrazów, czcionek i importowanych arkuszy stylów CSS za pomocą modyfikatorów url()
Modyfikatory żądań CSS url() (cross-origin(), referrer-policy() i integrity()) kontrolują tryb pobierania, nagłówek strony odsyłającej i weryfikację integralności zasobów podrzędnych w przypadku zasobów wczytywanych przez CSS – w tym reguł background-image, content, mask-image, border-image-source, filter, @font-face src i @import – bezpośrednio z CSS:
| Funkcja modyfikatora | Składnia argumentów i dozwolone wartości | Cel |
|---|---|---|
cross-origin(<ident>) |
Słowo kluczowe bez cudzysłowu: anonymous (zalecane ustawienie domyślne) lub use-credentials |
Pobiera zasób CSS za pomocą CORS i kontroluje, czy dane logowania są wysyłane. Zawsze dołączaj razem z integrity() w zasobach CSS współdzielonych między domenami. |
referrer-policy(<ident>) |
Słowo kluczowe bez cudzysłowu: no-referrer (zalecana wartość domyślna w przypadku zasobów zewnętrznych), strict-origin-when-cross-origin, origin, same-origin, strict-origin, origin-when-cross-origin, no-referrer-when-downgrade lub unsafe-url. |
Zastępuje zasadę dotyczącą strony odsyłającej dokumentu w przypadku tego konkretnego żądania CSS. |
integrity("<string>") |
Ciąg znaków w cudzysłowie: "sha384-<base64>" (lub podsumowania rozdzielone spacjami "sha384-... sha384-...") |
Przed wyrenderowaniem lub zastosowaniem pobranego zasobu CSS sprawdza go pod kątem oczekiwanego skrótu kryptograficznego. |
Podczas tworzenia modyfikatorów url() przestrzegaj tych reguł dotyczących składni CSS i bezpieczeństwa:
- Umieść ciąg adresu URL w
url("..."): modyfikatory żądań są prawidłowe tylko wtedy, gdy adres URL wurl(...)jest ujęty w<string>(adresy URL bez cudzysłowów nie mogą używać modyfikatorów). - Każdy typ modyfikatora możesz określić maksymalnie raz na
url(): możesz łączyćcross-origin(),referrer-policy()iintegrity()w dowolnej kolejności w ramachurl("..." ...), ale powtórzenie tego samego typu modyfikatora (np. 2 modyfikatorówintegrity()) jest nieprawidłowe. Aby podać kilka skrótów, rozdziel je spacjami w jednym ciąguintegrity("sha384-... sha384-..."). - Zawsze łącz współdzielenie
integrity()zcross-origin(anonymous): podczas gdy@font-facesrcimask-imagedomyślnie pobierają już w trybie anonimowymcors,background-image,content,border-image-sourcei@importdomyślnie używają trybuno-cors(generując nieprzezroczystą odpowiedź, której nie można zweryfikować za pomocą haszowania), a zewnętrzny SVGfilterdomyślnie używa trybusame-origin(blokując bezpośrednio adresy URL współdzielenia). Uwzględnieniecross-origin(anonymous)(lubcross-origin(use-credentials)) zapewnia spójną weryfikację CORS we wszystkich właściwościach CSS. - Umieść modyfikatory wewnątrz
url("...")w regułach@importi@font-face: zachowajcross-origin(),referrer-policy()iintegrity()wewnątrzurl("..." ...), a deskryptory czcionek, takie jakformat("woff2")itech(...), na zewnątrzurl(...).
@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 zastępcze
Jeśli Twój cel Baseline nie obsługuje jeszcze modyfikatorów żądań CSS url() (cross-origin(), referrer-policy() lub integrity()), połącz zasady na poziomie dokumentu z kaskadowymi deklaracjami CSS, aby zasoby podrzędne pozostały funkcjonalne i chronione:
- Jeśli wymagana jest integralność zasobów podrzędnych w różnych przeglądarkach, preferuj HTML
<link rel="stylesheet">zamiast CSS@import: ponieważ atrybutysubresource-integrityireferrer-policyw<link rel="stylesheet">są powszechnie dostępne we wszystkich przeglądarkach, wczytuj zewnętrzne arkusze stylów za pomocą<link rel="stylesheet" href="..." integrity="sha384-..." crossorigin="anonymous" referrerpolicy="no-referrer">zamiast@import, gdy weryfikacja integralności musi być wymuszana w każdej przeglądarce. - Korzystaj z nagłówka
Referrer-Policylub elementu<meta name="referrer">na poziomie dokumentu z kroku 2: w przeglądarkach, które nie obsługująreferrer-policy()wewnątrz CSSurl(), nagłówekReferrer-Policy: strict-origin-when-cross-originlub element<meta name="referrer" content="strict-origin-when-cross-origin">na poziomie dokumentu automatycznie usuwa poufne parametry ścieżki i parametry zapytania w żądaniach CSS współdzielenia. Kaskadowe oddzielne deklaracje CSS na potrzeby progresywnego ulepszania: przeglądarki, które nie rozpoznają modyfikatora wewnątrz
url("..." ...), odrzucają całą deklarację podczas analizowania kodu CSS. Najpierw zadeklaruj zwykłyurl("..."), a następnie deklaracjęcross-origin()ireferrer-policy()(w przypadku przeglądarek, które obsługują te 2 modyfikatory, ale nieintegrity()), a potem pełną deklarację, w tymintegrity("sha384-..."):- Nigdy nie umieszczaj niezweryfikowanego elementu
url()na tej samej liście@font-facesrcrozdzielonej przecinkami: zapisaniesrc: url("font.woff2" cross-origin(anonymous) integrity("sha384-...")), url("font.woff2");w jednym deskryptorzesrcomija integralność zasobów podrzędnych w czasie działania – jeśli sieć CDN udostępnia zmodyfikowaną czcionkę, która nie przechodzi kontroliintegrity(), przeglądarka traktuje to jako błąd wczytywania i automatycznie wraca do drugiego elementuurl("font.woff2")rozdzielonego przecinkami. Użycie oddzielnych deklaracjisrc:gwarantuje, że obsługujące je przeglądarki zastąpią niezweryfikowaną deklarację podczas analizowania kodu CSS i zablokują zmodyfikowane czcionki w czasie działania.
@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") ); }- Nigdy nie umieszczaj niezweryfikowanego elementu
Wykrywanie obsługi modyfikatora CSS
url()w języku JavaScript za pomocą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 };
}