Saat memuat skrip, stylesheet, font, atau gambar dari CDN pihak ketiga atau origin eksternal, verifikasi integritas kriptografinya dengan Subresource Integrity (integrity di HTML dan integrity() di CSS url()), konfigurasi penanganan kredensial lintas origin (crossorigin dan cross-origin()), dan batasi header Referer keluar (referrerpolicy dan referrer-policy()). Untuk Kebijakan Keamanan Konten tingkat dokumen dan daftar yang diizinkan untuk koneksi keluar, lihat Keamanan Web dan Membatasi koneksi jaringan keluar dengan Daftar Koneksi yang Diizinkan.
1. Membuat ringkasan integritas kriptografis untuk aset yang tidak dapat diubah
Subresource Integrity (SRI) memastikan browser hanya mengeksekusi atau merender aset eksternal jika byte-nya cocok dengan hash kriptografis yang dideklarasikan oleh aplikasi Anda.
- Hitung ringkasan pada waktu build atau rilis, jangan pernah dari respons langsung yang tidak tepercaya: Buat hash integritas selama langkah build atau rilis dari byte aset lokal yang diketahui baik (atau salin ringkasan resmi yang dipublikasikan oleh pembuat paket) dan hardcode ke dalam HTML, CSS, atau JavaScript Anda. Jangan pernah mengambil URL pihak ketiga di browser saat runtime untuk menghitung hash
integrity-nya sendiri, yang memercayai byte apa pun yang ditampilkan server jarak jauh dan mengabaikan SRI. - Gunakan
sha384-secara default: Format metadata integritas sebagai<algorithm>-<base64-digest>menggunakansha384-(default yang direkomendasikan),sha512-, atausha256-. - Sematkan subresource eksternal ke URL yang tidak dapat diubah dan berversi: Selalu referensikan aset pihak ketiga menggunakan URL yang tidak dapat diubah, disematkan versi, atau beralamat konten (seperti
https://cdn.example.com/ui@2.4.0/app.min.js), bukan URL mengambang yang dapat diubah (latest/app.min.js), yang mengekspos situs Anda ke perubahan supply chain yang tidak terlihat dan merusak SRI segera setelah file jarak jauh diperbarui. Jika Anda harus memuat skrip pihak ketiga yang dapat diubah dan diperbarui di tempat (seperti tag analisis vendor), hapusintegritydi URL tersebut dan batasi dengan Kebijakan Keamanan Konten (strict-dynamic/nonce) atau iframe sandboxing. - Pisahkan beberapa ringkasan dengan spasi jika endpoint dapat menyajikan beberapa payload yang valid: Semua browser yang mendukung SRI menerapkan
sha256-,sha384-, dansha512-. Jika URL dapat secara sah menampilkan salah satu dari beberapa payload yang diketahui baik (seperti selama pengujian A/B, peluncuran CDN bertahap, atau build yang dinegosiasikan konten), cantumkan beberapa ringkasan algoritma yang sama yang dipisahkan oleh spasi kosong (misalnya,integrity="sha384-variantA... sha384-variantB..."). Browser akan menerima respons jika cocok dengan setiap ringkasan yang tercantum dari algoritma terkuat yang ada. - Menyajikan subresource lintas origin dengan header CORS: Browser hanya dapat memverifikasi integritas respons lintas origin jika respons tersebut dapat dibaca CORS (
Access-Control-Allow-Origin: *untuk permintaan anonim, atau origin permintaan yang tepat denganAccess-Control-Allow-Credentials: trueuntuk permintaan yang menggunakan kredensial).
# 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. Mengamankan skrip, stylesheet, dan pramuat HTML
Tetapkan header respons HTTP Referrer-Policy: strict-origin-when-cross-origin tingkat dokumen (atau sertakan <meta name="referrer" content="strict-origin-when-cross-origin"> di <head>) sebagai default dasar pengukuran sehingga subresource atau url() CSS yang menghilangkan pengubah perujuk per permintaan tetap menghapus parameter jalur dan kueri sensitif pada permintaan lintas origin.
Kemudian, pada elemen <script>, <link rel="stylesheet">, <link rel="preload">, dan <link rel="modulepreload"> eksternal, selalu tetapkan integrity, crossorigin, dan referrerpolicy bersama-sama:
integrity="sha384-...": Memblokir eksekusi atau penerapan resource jika byte respons tidak cocok dengan ringkasan yang diharapkan (untuk<link rel="stylesheet">yang diblokir,linkElement.sheettetapnulldan peristiwaerrordiaktifkan).crossorigin="anonymous"(default yang direkomendasikan untuk aset CDN publik) ataucrossorigin="use-credentials": Diperlukan setiap kaliintegrityditetapkan pada resource lintas origin. Menghilangkancrossoriginakan menyebabkan browser mengambil resource dalam modeno-cors(menghasilkan respons buram yang tidak dapat diverifikasi hash-nya) dan memblokir resource.referrerpolicy="no-referrer"(default yang direkomendasikan untuk CDN pihak ketiga) ataureferrerpolicy="strict-origin-when-cross-origin": Mengganti kebijakan perujuk dokumen untuk mencegah kebocoran informasi asal atau jalur di header permintaanRefererke host pihak ketiga.
<!-- 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>
Saat mengambil subresource eksternal secara terprogram dengan fetch(), teruskan opsi integrity, mode, credentials, dan referrerPolicy yang cocok (ketidakcocokan ringkasan akan menolak promise dengan 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. Mengamankan gambar, font, dan stylesheet yang diimpor CSS dengan pengubah url()
Pengubah permintaan url() CSS (cross-origin(), referrer-policy(), dan integrity()) mengontrol mode pengambilan, header perujuk, dan verifikasi Subresource Integrity resource yang dimuat CSS—termasuk aturan background-image, content, mask-image, border-image-source, filter, @font-face src, dan @import—langsung dari CSS:
| Fungsi pengubah | Sintaksis argumen & nilai yang diizinkan | Tujuan |
|---|---|---|
cross-origin(<ident>) |
Kata kunci tanpa tanda petik: anonymous (default yang direkomendasikan) atau use-credentials |
Mengambil resource CSS menggunakan CORS dan mengontrol apakah kredensial dikirim. Selalu sertakan bersama integrity() pada resource CSS lintas origin. |
referrer-policy(<ident>) |
Kata kunci tanpa tanda petik: no-referrer (default yang direkomendasikan untuk aset pihak ketiga), strict-origin-when-cross-origin, origin, same-origin, strict-origin, origin-when-cross-origin, no-referrer-when-downgrade, atau unsafe-url |
Mengganti kebijakan perujuk dokumen untuk permintaan CSS spesifik ini. |
integrity("<string>") |
String kutipan: "sha384-<base64>" (atau ringkasan yang dipisahkan spasi "sha384-... sha384-...") |
Memverifikasi resource CSS yang diambil terhadap ringkasan kriptografis yang diharapkan sebelum merender atau menerapkannya. |
Ikuti aturan keamanan dan sintaksis CSS berikut saat menulis pengubah url():
- Mengutip string URL di dalam
url("..."): Pengubah permintaan hanya valid jika URL di dalamurl(...)adalah<string>yang dikutip (URL yang tidak dikutip tidak dapat menggunakan pengubah). - Tentukan setiap jenis pengubah maksimal sekali per
url(): Anda dapat menggabungkancross-origin(),referrer-policy(), danintegrity()dalam urutan apa pun di dalamurl("..." ...), tetapi mengulangi jenis pengubah yang sama dua kali (seperti dua pengubahintegrity()) tidak valid. Untuk memberikan beberapa hash, pisahkan dengan spasi di dalam satu stringintegrity("sha384-... sha384-..."). - Selalu pasangkan
integrity()lintas origin dengancross-origin(anonymous): Meskipun@font-facesrcdanmask-imagesudah mengambil dalam modecorsanonim secara default,background-image,content,border-image-source, dan@importsecara default menggunakan modeno-cors(menghasilkan respons buram yang tidak dapat diverifikasi hash) danfilterSVG eksternal secara default menggunakan modesame-origin(memblokir URL lintas origin secara langsung). Menyertakancross-origin(anonymous)(ataucross-origin(use-credentials)) akan memastikan verifikasi CORS yang konsisten di setiap properti CSS. - Tempatkan pengubah di dalam
url("...")dalam aturan@importdan@font-face: Pertahankancross-origin(),referrer-policy(), danintegrity()di dalamurl("..." ...), sambil mempertahankan deskriptor font sepertiformat("woff2")dantech(...)di luarurl(...).
@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")
);
}
Strategi penggantian
Jika target Dasar Pengukuran Anda belum mendukung pengubah permintaan url() CSS (cross-origin(), referrer-policy(), atau integrity()), gabungkan kebijakan tingkat dokumen dengan deklarasi CSS bertingkat sehingga subresource tetap berfungsi dan terlindungi:
- Lebih pilih HTML
<link rel="stylesheet">daripada CSS@importjika SRI lintas browser diperlukan: Karenasubresource-integritydanreferrer-policydi<link rel="stylesheet">tersedia secara luas di semua browser, muat stylesheet eksternal dengan<link rel="stylesheet" href="..." integrity="sha384-..." crossorigin="anonymous" referrerpolicy="no-referrer">, bukan@import, setiap kali verifikasi integritas harus diterapkan di setiap browser. - Mengandalkan header
Referrer-Policyatau<meta name="referrer">tingkat dokumen dari Langkah 2: Di browser yang tidak mendukungreferrer-policy()di dalamurl()CSS, headerReferrer-Policy: strict-origin-when-cross-originatau elemen<meta name="referrer" content="strict-origin-when-cross-origin">tingkat dokumen akan otomatis menghapus parameter jalur dan kueri sensitif pada permintaan CSS lintas origin. Gabungkan deklarasi CSS terpisah untuk progressive enhancement: Browser yang tidak mengenali pengubah di dalam
url("..." ...)akan menghapus seluruh deklarasi pada waktu penguraian CSS. Deklarasikanurl("...")biasa terlebih dahulu, diikuti dengan deklarasicross-origin()danreferrer-policy()(untuk browser yang mendukung kedua pengubah tersebut, tetapi tidak mendukungintegrity()), diikuti dengan deklarasi lengkap termasukintegrity("sha384-..."):- Jangan pernah menempatkan
url()yang belum diverifikasi di dalam daftar@font-facesrcyang dipisahkan koma yang sama: Menulissrc: url("font.woff2" cross-origin(anonymous) integrity("sha384-...")), url("font.woff2");dalam satu deskriptorsrcakan mengabaikan Subresource Integrity saat runtime—jika CDN menyajikan font yang dimodifikasi dan gagal dalam pemeriksaanintegrity(), browser akan memperlakukannya sebagai kegagalan pemuatan dan otomatis beralih keurl("font.woff2")kedua yang dipisahkan koma. Menggunakan deklarasisrc:terpisah akan memastikan browser pendukung menimpa deklarasi yang tidak diverifikasi pada waktu penguraian CSS dan memblokir font yang diubah pada waktu proses.
@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") ); }- Jangan pernah menempatkan
Deteksi fitur dukungan pengubah
url()CSS di JavaScript denganCSS.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 };
}