Cache key, Vary & kebocoran antar pembaca
Cache key menentukan respons mana yang dianggap sama. Kalau ia tidak memasukkan sesuatu yang membuat respons berbeda, pembaca akan melihat halaman milik orang lain.
Intisari
- Cache key bawaan: host + jalur + query string. Cookie dan header tidak termasuk.
- Artinya: respons yang berbeda karena cookie akan saling menimpa di cache kalau kamu memaksanya di-cache.
Varydihormati sebagian; jangan mengandalkannya untuk memisahkan data personal.- Aturan yang benar: jangan cache yang personal, jangan mencoba memisahkannya lewat cache key.
- Pengecualian yang sah: memisahkan berdasarkan perangkat atau bahasa, bukan berdasarkan identitas.
Cache key bawaan
https://portal.contoh.id/berita/rupiah-menguat?hal=2
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
host jalur query
TIDAK termasuk: cookie, Authorization, User-Agent, Accept-Language
Skenario kegagalan
1. Member A membuka /akun. Respons berisi namanya, saldonya, riwayat tagihannya.
2. Cache Rule keliru menandainya "eligible for cache".
3. Cloudflare menyimpannya dengan key: portal.contoh.id/akun
4. Member B membuka /akun.
5. Cache HIT. Member B melihat data Member A.
Ini bukan bug yang menghasilkan error. Halamannya dimuat sempurna. Tidak ada apa pun di log aplikasimu, karena request-nya tidak pernah sampai ke origin. Yang terjadi berikutnya adalah laporan dari pembaca, dan pada titik itu data pribadi sudah tersaji ke jumlah orang yang tidak bisa kamu hitung.
Kenapa Vary: Cookie bukan jawabannya
// Terlihat masuk akal. Tidak menyelesaikan masalah.
res.headers.set("Vary", "Cookie");
| Masalah | Penjelasan |
|---|---|
| Dukungan CDN terbatas | Cloudflare hanya menghormati Vary pada header tertentu; Cookie tidak diperlakukan sebagai pemisah cache umum |
| Kardinalitas tak terbatas | Tiap kombinasi cookie jadi entri sendiri โ cache hit ratio jatuh ke nol |
| Cookie pihak ketiga ikut | Cookie analitik yang berbeda per pembaca membuat tiap orang punya entri sendiri |
Aturan yang benar dan sederhana: respons personal tidak di-cache di edge sama sekali. Bukan di-cache dengan pemisahan yang cerdik โ tidak di-cache.
Pertahanan berlapis
// Lapis 1: kode halaman
setCache(Astro.response, "personal"); // โ private, no-store
// Lapis 2: middleware, untuk halaman yang lupa
if (jalurPersonal(ctx.url.pathname)) {
res.headers.set("Cache-Control", "private, no-store");
}
// Lapis 3: Cache Rule bypass untuk jalur personal (aturan 1 di materi sebelumnya)
// Lapis 4: Cache Rule bypass saat cookie sesi ada
Empat lapis untuk satu aturan terdengar berlebihan sampai kamu mempertimbangkan konsekuensinya. Halaman baru akan ditambahkan oleh orang yang tidak membaca materi ini; lapis 2 sampai 4 melindungi dari itu.
Pemisahan cache yang sah
Ada kasus di mana memisahkan cache memang benar โ yang membedakannya: pemisahannya berdasarkan kelompok, bukan berdasarkan identitas.
| Pemisahan | Kardinalitas | Sah? |
|---|---|---|
| Perangkat (ponsel / desktop) | 2โ3 | Ya |
| Bahasa | 2โ5 | Ya |
| Negara | Puluhan | Hati-hati |
| Tier langganan | 3 | Bisa, tapi server island lebih baik |
| ID member | Ratusan ribu | Tidak pernah |
| User-Agent penuh | Praktis tak terbatas | Tidak pernah |
Cache Key โ Custom:
โ Device Type (mobile / desktop / tablet)
โ Cookie
โ Header
โ Query String: ignore utm_*, fbclid, gclid
Pemisahan per tier langganan terdengar menarik โ tiga versi halaman untuk gratis, dasar, dan pro. Tapi ia menggandakan jumlah entri cache, membuat purge tiga kali lebih rumit, dan tetap membutuhkan cookie di cache key untuk menentukan tier pembaca. Server island menyelesaikan hal yang sama dengan satu versi halaman ter-cache dan satu request kecil per pembaca. Pilih itu.
Bahaya yang berlawanan: Set-Cookie yang ter-cache
// BAHAYA: menyetel cookie di halaman yang boleh di-cache
Astro.cookies.set("pengunjung", id, { maxAge: 86400 });
setCache(Astro.response, "artikel"); // โ publik!
Kalau Cloudflare meng-cache respons ini beserta header Set-Cookie-nya, maka setiap
pembaca berikutnya menerima cookie yang sama. Untuk cookie pengunjung, itu berarti analitikmu
menghitung sejuta orang sebagai satu. Untuk cookie sesi, itu berarti pengambilalihan akun massal.
Cloudflare secara bawaan tidak akan meng-cache respons yang punya Set-Cookie โ itu
perlindungan yang bagus. Tapi Cache Rule yang memaksa "Eligible for cache" bisa menimpanya. Jadi:
halaman yang di-cache tidak boleh menyetel cookie apa pun. Kalau butuh, setel dari
server island yang memang tidak di-cache.
Audit
U=https://portal.contoh.id
# Tidak boleh ada Set-Cookie pada halaman publik
curl -sI $U/berita/artikel-uji | grep -i set-cookie # harus kosong
curl -sI $U/ | grep -i set-cookie # harus kosong
# Jalur personal harus BYPASS, apa pun cookie-nya
for p in /akun /akun/tagihan /langganan; do
printf '%s โ ' "$p"
curl -sI "$U$p" -H 'Cookie: __Host-sesi=uji' | grep -i cf-cache-status
done
# Uji kebocoran: dua cookie berbeda, bandingkan isinya
curl -s $U/akun -H 'Cookie: __Host-sesi=A' -o /tmp/a.html
curl -s $U/akun -H 'Cookie: __Host-sesi=B' -o /tmp/b.html
diff -q /tmp/a.html /tmp/b.html # HARUS berbeda
Latihan: jalankan seluruh audit di atas pada portalmu. Lalu lakukan uji yang paling penting: buat dua sesi member berbeda, muat halaman akun dengan masing-masing beberapa kali, dan bandingkan hasilnya byte per byte. Tambahkan uji ini ke pipeline sebagai pemeriksaan asap setelah tiap deploy โ ini kelas bug yang harganya terlalu mahal untuk hanya diperiksa manual.
Rangkuman ini sengaja dipangkas ke bagian yang dipakai di roadmap. Buka sumber aslinya saat kamu butuh detail lengkap atau referensi parameter.