SRI (Subresource Integrity) adalah atribut integrity di tag <script> atau <link> yang berisi sidik jari (hash) file yang kamu harapkan. Browser menghitung hash file yang benar-benar datang dari CDN, dan kalau beda, file itu gak dipakai. Bayangin kamu memesan obat, dan apotek memberimu kode yang dihitung dari isi obatnya. Saat paket datang, kamu hitung ulang kodenya dari isi paket. Satu butir saja ditukar, kodenya berubah total, dan obat itu gak kamu minum. Obatnya file JavaScript, kodenya hash di integrity, dan yang menghitung ulang adalah browser.
🧪 Tebakan cepat: file
lib.jsdi CDN diubah satu karakter saja,"Rp"jadi"RP". Hash SHA-384-nya berubah sedikit, atau berubah total? Jawabannya ada di bagian "Hash: sidik jari sebuah file".
Kenapa file dari CDN perlu dicek
Waktu kamu menulis <script src="https://cdn.example.com/lib.js"></script>, file itu jalan di halamanmu dengan hak yang sama dengan kodemu sendiri: bisa membaca isi form, mengubah halaman, dan mengirim data ke mana pun. Kamu percaya CDN-nya gak pernah mengubah file itu. Kalau CDN dibobol, atau domain-nya pindah tangan, kode penyerang ikut jalan di semua situs yang memuatnya.
Ini pernah terjadi. Juni 2024, domain polyfill.io, yang script-nya dimuat lebih dari 100.000 situs, berganti pemilik dan mulai menyisipkan kode jahat ke file yang dikirimnya.
Hash: sidik jari sebuah file
Hash (kamu sudah kenal di pelajaran encryption-hashing-encoding) mengubah isi apa pun jadi sidik jari dengan panjang tetap. SHA-384 menghasilkan 48 byte, yang ditulis dalam base64 jadi 64 karakter. Isi yang sama selalu memberi hash yang sama, dan isi yang beda satu byte saja memberi hash yang beda total.
Ini file lib.js yang dipakai di Lab (80 byte):
function formatRupiah(angka) {
return "Rp" + angka.toLocaleString("id-ID");
}
| Isi file | SHA-384 (base64) |
|---|---|
asli, "Rp" |
4vlMkCBntvautp0XME8R3vfkStzXNiOdjs7eLdGbNQUAyvrGf85spApwnczyyYQP |
diubah, "RP" |
UYjeag08cU8lY+wb6XO31z0n+uxHaSMnWBkhqiz8S4Ys+/0sFZCSFKIbXv7FJkuZ |
Cuma satu huruf p jadi P, tapi hash-nya berubah total, gak ada bagian yang mirip. Itu jawaban tebakannya. Penyerang gak bisa mengubah file dan tetap mempertahankan hash-nya.
Memasang integrity dan crossorigin
<script
src="https://cdn.example.com/lib.js"
integrity="sha384-4vlMkCBntvautp0XME8R3vfkStzXNiOdjs7eLdGbNQUAyvrGf85spApwnczyyYQP"
crossorigin="anonymous"
></script>
integrityberisi nama algoritma (sha256,sha384, atausha512), tanda minus, lalu hash dalam base64.sha384pilihan yang paling umum.- Browser mengunduh file-nya, menghitung SHA-384 dari isinya, lalu membandingkan. Sama: script dijalankan. Beda: script diblokir, dan Chrome menulis:
Failed to find a valid digest in the 'integrity' attribute for resource 'https://cdn.example.com/lib.js' with computed SHA-384 integrity 'UYjeag08cU8lY+wb6XO31z0n+uxHaSMnWBkhqiz8S4Ys+/0sFZCSFKIbXv7FJkuZ'. The resource has been blocked.
crossorigin="anonymous"membuat browser meminta file lewat CORS, tanpa cookie. Browser cuma boleh membaca isi file dari origin lain kalau CORS mengizinkan (ingat pelajaran CORS), dan tanpa membaca isinya, hash-nya gak bisa dihitung. CDN publik seperti cdnjs dan jsDelivr menjawab denganAccess-Control-Allow-Origin: *, jadi cukup tambahkan atribut ini. Tanpacrossorigin, file-nya diblokir walau isinya asli:
Subresource Integrity: The resource 'https://cdn.example.com/lib.js' has an integrity attribute, but the resource requires the request to be CORS enabled to check the integrity, and it is not. The resource has been blocked because the integrity cannot be enforced.
Untuk CSS caranya sama: <link rel="stylesheet" href="..." integrity="sha384-..." crossorigin="anonymous">.
Membuat hash-nya
Di terminal, dengan file aslinya:
openssl dgst -sha384 -binary lib.js | openssl base64 -A
# 4vlMkCBntvautp0XME8R3vfkStzXNiOdjs7eLdGbNQUAyvrGf85spApwnczyyYQP
openssl dgst -sha384 -binarymenghitung SHA-384 dan mengeluarkannya sebagai byte mentah.openssl base64 -Amengubah byte itu jadi base64 dalam satu baris. Tambahkansha384-di depannya.
Atau di browser, dengan Web Crypto, cara yang sama yang dipakai Lab:
async function sriHash(text) {
const bytes = new TextEncoder().encode(text);
const digest = await crypto.subtle.digest("SHA-384", bytes);
const base64 = btoa(String.fromCharCode(...new Uint8Array(digest)));
return "sha384-" + base64;
}
TextEncodermengubah teks jadi byte UTF-8, karena hash dihitung dari byte.crypto.subtle.digestmengembalikan Promise berisi hash, jadi pakaiawait.btoa(...)mengubah byte jadi base64.
Halaman cdnjs dan jsDelivr juga menampilkan tag lengkap dengan integrity yang siap disalin.
💡 Beberapa hash, dan batas SRI
Atribut integrity boleh berisi beberapa hash, dipisah spasi. Browser cuma memakai algoritma yang paling kuat di daftar itu, lalu menerima file kalau cocok dengan salah satu hash algoritma tersebut. Jadi integrity="sha384-AAA sha384-BBB" menerima dua versi file.
Batas yang perlu kamu tahu:
- SRI berlaku untuk
<script>dan<link rel="stylesheet">, gak untuk gambar atau font. - File yang isinya berubah-ubah gak bisa pakai SRI. polyfill.io sendiri contohnya: isinya beda untuk tiap browser. Solusinya simpan salinan file di server-mu sendiri.
- Setiap kali naik versi library, hash-nya ikut berubah. Pakai URL dengan versi tetap, seperti
[email protected], bukanlatest.
⚠️ Jebakan yang sering ketemu
- Lupa
crossorigin="anonymous". File dari origin lain diblokir walau hash-nya benar, karena browser gak boleh membaca isinya untuk dihitung. - URL tanpa versi tetap.
.../latest/lib.jsberubah saat library rilis versi baru, hash-nya gak cocok lagi, dan fitur di halamanmu mendadak mati. - Hash dihitung dari file yang beda. Hasil minify ulang, atau beda satu newline di ujung, sudah memberi hash lain. Hitung dari file persis yang dikirim CDN.
- Mengira SRI menjaga semua resource. Gambar, font, dan script yang isinya dinamis gak terlindungi. Untuk script pihak ketiga yang gak bisa dikunci, lanjut ke pelajaran third-party-scripts.
TL;DR: SRI (Subresource Integrity) menaruh hash file di atribut integrity, dan browser menolak file dari CDN yang hash-nya gak cocok. Formatnya sha384- diikuti hash base64, misalnya sha384-4vlMkCBntvautp0XME8R3vfkStzXNiOdjs7eLdGbNQUAyvrGf85spApwnczyyYQP untuk lib.js di Lab. Ubah satu karakter saja dan hash-nya berubah total. Untuk file dari origin lain wajib tambahkan crossorigin="anonymous", kalau gak, file-nya diblokir karena hash-nya gak bisa dicek. Buat hash dengan openssl dgst -sha384 -binary lib.js | openssl base64 -A atau crypto.subtle.digest("SHA-384", ...). SRI cuma untuk <script> dan stylesheet yang isinya tetap, jadi pakai URL dengan versi tetap.