Audit keamanan frontend buatan AI harus dimulai dengan satu asumsi: semua kode yang berjalan di browser, semua nilai yang dikirim browser, dan semua keputusan yang hanya diterapkan oleh UI dapat dilihat serta dimanipulasi pengguna. Kode hasil generasi AI sering tampak lengkap, tetapi dapat melewatkan batas kepercayaan, jalur error, validasi server, atau skenario penyalahgunaan.
Karena itu, audit bukan sekadar mencari pemanggilan API yang salah. Tujuannya adalah memastikan secret tidak pernah dikirim ke browser, token dikelola dengan aman, input tidak menjadi XSS, server mengulang validasi dan otorisasi, serta upload diperlakukan sebagai data tidak tepercaya. Pendekatan ini menerapkan gagasan mengurangi kecerobohan teknis: pecah sistem menjadi asumsi yang dapat diperiksa dan buktikan setiap kontrol dengan pengujian negatif.
Mulai dari threat model dan batas kepercayaan
Sebelum membaca komponen satu per satu, gambarkan aliran data dan tentukan aset yang perlu dilindungi. Untuk aplikasi web umum, aset tersebut dapat berupa sesi pengguna, data pribadi, hak akses administratif, file privat, kuota penyimpanan, dan kredensial layanan pihak ketiga.
Petakan aktor, permukaan serangan, dan dampak
- Pengguna anonim: dapat memanggil endpoint secara langsung tanpa melewati UI.
- Pengguna biasa: memiliki sesi valid, tetapi mungkin mencoba mengakses objek milik pengguna lain.
- Browser yang terkena XSS: dapat menjalankan JavaScript dalam origin aplikasi dan melakukan tindakan sebagai korban.
- Situs eksternal: dapat mencoba mengirim permintaan lintas situs untuk mengeksploitasi sesi berbasis cookie.
- Pengunggah berbahaya: dapat memalsukan nama, ekstensi, MIME type, ukuran, atau isi file.
Tandai setiap perpindahan data yang melewati batas kepercayaan: browser ke API, API ke database, aplikasi ke penyimpanan objek, webhook ke backend, dan proses pemindai ke area penyimpanan final. Browser selalu berada di luar batas kepercayaan server, termasuk ketika frontend dibuat dan dihosting oleh tim yang sama.
DevTools, validasi form, route guard, tombol yang disembunyikan, variabel JavaScript, dan header yang dibuat oleh frontend bukan kontrol keamanan. Semuanya dapat diubah atau dilewati.
Pertanyaan awal untuk setiap fitur
- Siapa yang boleh melakukan operasi ini?
- Objek mana yang boleh mereka baca atau ubah?
- Data mana yang berasal dari pihak tidak tepercaya?
- Apa yang terjadi jika frontend tidak mengirim field yang diharapkan?
- Apa yang terjadi jika permintaan dikirim berulang, paralel, atau berukuran sangat besar?
- Kontrol mana yang benar-benar ditegakkan server?
Alur audit keamanan frontend buatan AI
Jangan mengaudit hanya dari tampilan akhir. Mulailah dari perubahan kode dan ikuti data hingga backend. Kode generatif perlu diperlakukan seperti patch dari kontributor yang belum memahami seluruh arsitektur.
- Inventarisasi: daftar route, endpoint, form, mekanisme login, storage browser, komponen yang merender HTML, dan fitur upload.
- Telusuri sumber data: identifikasi query string, fragment URL, respons API, WebSocket, file, clipboard, dan input pengguna.
- Cari sink sensitif: rendering HTML mentah, eksekusi kode, pembuatan URL, penyimpanan token, operasi mutasi, dan pembukaan file.
- Periksa pasangan backend: setiap kontrol UI harus memiliki validasi dan otorisasi ekuivalen di server.
- Uji kegagalan: kirim data yang hilang, salah tipe, terlalu besar, bukan milik pengguna, atau menggunakan sesi yang tidak valid.
Pencarian teks membantu menemukan pola awal, misalnya localStorage, innerHTML, API HTML mentah milik framework, Authorization, FileReader, accept=, dan variabel lingkungan. Namun hasil pencarian bukan bukti kerentanan; periksa konteks dan aliran datanya.
Secret, token, cookie, dan CSRF
Secret di bundle frontend bukan secret
Variabel lingkungan yang digunakan saat proses build biasanya digantikan menjadi nilai literal atau disertakan dalam aset yang dikirim ke browser. Mengganti nama variabel menjadi API_SECRET tidak membuatnya rahasia.
// Rentan: nilai akan tersedia bagi siapa pun yang mengunduh bundle.
const client = createServiceClient({
apiKey: import.meta.env.PUBLIC_PAYMENT_SECRET
});Pindahkan operasi yang membutuhkan kredensial ke backend atau backend-for-frontend. Browser memanggil endpoint milik aplikasi, lalu server menggunakan secret dari secret manager atau environment runtime yang tidak pernah dikirim ke klien.
Beberapa layanan memang menyediakan publishable key untuk browser. Kunci seperti itu adalah pengenal publik, bukan bukti otorisasi. Batasi origin, scope, kuota, dan operasi yang diizinkan sesuai kemampuan penyedia. Jangan memperlakukan pembatasan tersebut sebagai pengganti otorisasi server.
Audit juga source map, file konfigurasi statis, riwayat Git, log build, dan artefak deployment. Jika secret pernah masuk ke bundle atau repositori, hapus saja tidak cukup: cabut dan rotasi kredensial tersebut.
Risiko token di localStorage
// Pola yang perlu dicurigai
localStorage.setItem('access_token', response.accessToken);
fetch('/api/profile', {
headers: { Authorization: 'Bearer ' + localStorage.getItem('access_token') }
});localStorage mudah digunakan dan tidak otomatis dikirim lintas situs. Namun semua JavaScript yang berjalan pada origin yang sama dapat membacanya. Satu XSS atau dependensi frontend yang disusupi dapat mengekstrak token dan menggunakannya dari perangkat lain hingga token kedaluwarsa atau dicabut.
Untuk aplikasi web same-origin, pertimbangkan sesi dalam cookie dengan atribut berikut:
Set-Cookie: __Host-session=nilaiacak; Path=/; Secure; HttpOnly; SameSite=LaxSecuremembatasi pengiriman cookie ke HTTPS.HttpOnlymencegah JavaScript membaca nilai cookie, sehingga mempersulit pencurian token secara langsung melalui XSS.SameSitemembatasi pengiriman cookie dalam konteks lintas situs.Strictlebih ketat tetapi dapat mengganggu alur navigasi tertentu;Laxsering menjadi kompromi untuk aplikasi biasa.- Prefiks
__Host-mensyaratkanSecure,Path=/, dan tidak menggunakan atributDomain, sehingga mempersempit risiko penimpaan cookie.
Cookie HttpOnly mengurangi peluang ekstraksi token, tetapi tidak menghilangkan XSS: skrip berbahaya masih dapat mengirim operasi melalui browser korban. Gunakan masa sesi terbatas, rotasi setelah login atau perubahan hak akses, mekanisme pencabutan, dan autentikasi ulang untuk tindakan sensitif.
Cookie membutuhkan proteksi CSRF
Karena browser mengirim cookie secara otomatis, endpoint yang mengubah data harus dilindungi dari Cross-Site Request Forgery. Jangan gunakan metode GET untuk mutasi. Terapkan token CSRF pola synchronizer atau signed double-submit, kemudian verifikasi token di server.
// Token diperoleh dari bootstrap aplikasi, bukan ditanam saat build.
await fetch('/api/email', {
method: 'PATCH',
credentials: 'same-origin',
headers: {
'Content-Type': 'application/json',
'X-CSRF-Token': csrfToken
},
body: JSON.stringify({ email })
});Validasi header Origin atau Referer untuk request mutasi dapat menjadi pertahanan tambahan. Jangan hanya mengandalkan SameSite, karena kebutuhan integrasi, subdomain, dan perubahan konfigurasi dapat membuka jalur lain. CORS juga bukan mekanisme autentikasi dan bukan pengganti proteksi CSRF.
XSS dan sanitasi sesuai konteks
XSS terjadi ketika data tidak tepercaya diperlakukan sebagai kode atau markup aktif. Pola yang sering muncul pada frontend hasil generasi AI adalah merender deskripsi, hasil Markdown, atau parameter URL sebagai HTML tanpa kontrak sanitasi yang jelas.
// Rentan jika bio berasal dari pengguna atau API.
profileElement.innerHTML = user.bio;
// Aman untuk teks biasa.
profileElement.textContent = user.bio;Gunakan rendering teks atau interpolasi default framework selama nilainya tidak masuk ke API yang sengaja melewati escaping. Jika fitur memang harus menerima HTML, sanitasi dengan pustaka yang terpelihara dan kebijakan elemen serta atribut yang sempit. Hindari membuat sanitizer sendiri menggunakan ekspresi reguler.
Konteks menentukan perlindungan
- Konten HTML: gunakan encoding teks atau sanitizer HTML.
- Atribut HTML: gunakan binding atribut dari framework dan hindari penggabungan string markup.
- URL: parse dengan API URL dan izinkan protokol yang diperlukan, misalnya
https:. Men-escape string saja tidak menolak skema berbahaya. - JavaScript dan CSS: jangan menyisipkan data tidak tepercaya ke kode dinamis. Hindari
eval, konstruktor fungsi, dan handler inline.
Sanitasi saat input tidak selalu cukup karena data yang sama dapat digunakan dalam konteks berbeda. Simpan data kanonis bila memungkinkan, lalu lakukan output encoding atau sanitasi sesuai konteks saat dirender.
CSP sebagai pertahanan berlapis
Content Security Policy dapat membatasi sumber skrip, frame, koneksi, dan objek. Mulailah dalam mode laporan jika aplikasi lama memiliki banyak skrip inline, lalu perketat secara bertahap.
Content-Security-Policy: default-src 'self'; script-src 'self' 'nonce-NILAI_ACAK_PER_RESPONS'; object-src 'none'; base-uri 'none'; frame-ancestors 'none'Nonce harus acak untuk setiap respons dan hanya ditempelkan pada skrip yang dipercaya. Hindari melonggarkan kebijakan dengan 'unsafe-inline' tanpa alasan yang dikaji. CSP membatasi dampak sebagian XSS, tetapi tidak memperbaiki sink berbahaya atau otorisasi yang salah.
Validasi dan otorisasi wajib di server
Validasi klien hanya untuk pengalaman pengguna
Atribut HTML seperti required, batas panjang, pilihan dropdown, dan schema frontend membantu pengguna mendapatkan umpan balik cepat. Penyerang dapat mengirim request langsung, menghapus field, mengubah tipe data, atau mengirim nilai di luar pilihan UI.
// Pseudocode backend: autentikasi, validasi, lalu otorisasi objek.
const actor = await requireSession(request);
const input = validateUpdateProject(await request.json());
const project = await projects.findById(input.projectId);
if (!project) return notFound();
if (!canEditProject(actor, project)) return forbidden();
const updated = await projects.update(project.id, {
name: input.name,
visibility: input.visibility
});
return json(updated);Validasi server perlu memeriksa tipe, field wajib, batas panjang, format, nilai yang diizinkan, hubungan antar-field, dan aturan bisnis. Tolak field yang tidak dikenal atau petakan hanya field yang diizinkan agar pengguna tidak dapat menyisipkan properti seperti role, ownerId, atau isApproved.
UI bukan lapisan otorisasi
// Ini hanya mengatur tampilan, bukan keamanan.
if (currentUser.role === 'admin') {
showDeleteButton();
}Endpoint penghapusan tetap harus memverifikasi sesi, peran, tenant, dan kepemilikan objek. Jangan menerima userId atau tenantId dari body sebagai dasar kepercayaan. Ambil identitas aktor dari sesi yang telah diverifikasi, lalu cocokkan dengan objek yang dibaca dari database.
Periksa khususnya risiko insecure direct object reference: pengguna mengganti /projects/123 menjadi /projects/124. Terapkan otorisasi pada setiap objek, bukan hanya pemeriksaan bahwa pengguna telah login. Keputusan mengembalikan 403 atau 404 bergantung pada apakah keberadaan objek boleh diketahui, tetapi keduanya harus konsisten.
Upload file, rate limit, dan pesan error
Jangan percaya nama, ekstensi, MIME type, atau Content-Length
Atribut accept pada input file hanya membantu pemilih file di browser. Field Content-Type, nama file, dan Content-Length dikendalikan klien serta dapat dipalsukan.
Alur upload yang lebih aman adalah:
- Autentikasi pengguna dan periksa izin serta kuotanya sebelum menerima upload.
- Batasi ukuran saat membaca stream. Jangan menunggu seluruh file masuk ke memori atau mempercayai
Content-Length. - Tulis ke area karantina dengan nama acak yang dibuat server.
- Deteksi format berdasarkan byte aktual menggunakan parser yang sesuai, lalu cocokkan dengan daftar tipe yang diizinkan.
- Validasi struktur file. Untuk gambar, lakukan decode penuh; jika sesuai kebutuhan, encode ulang ke format aman untuk membuang data tambahan yang tidak dibutuhkan.
- Pindai malware atau lakukan pemrosesan asinkron jika model ancaman memerlukannya.
- Pindahkan file yang lolos ke penyimpanan final. Simpan di luar web root atau gunakan bucket privat dengan URL akses berumur pendek.
// Pseudocode: pembatasan harus berlangsung saat stream dibaca.
const upload = await streamToQuarantine(request.body, {
maxBytes: policy.maxUploadBytes
});
const detectedType = await detectTypeFromContent(upload.path);
if (!policy.allowedTypes.has(detectedType)) {
await remove(upload.path);
return badRequest('Tipe file tidak didukung');
}
await validateFileStructure(upload.path, detectedType);
const objectKey = createRandomObjectKey(detectedType);
await promoteToPrivateStorage(upload.path, objectKey);Pertimbangkan file terkompresi, arsip dengan ekspansi sangat besar, dokumen aktif, SVG yang mengandung skrip, metadata sensitif, dan file poliglot. Bila upload langsung dilakukan ke object storage menggunakan URL bertanda tangan, endpoint finalisasi tetap harus memeriksa ukuran, tipe aktual, kepemilikan object key, dan status pemindaian.
Rate limit dan pengendalian biaya
Terapkan rate limit di lapisan yang dapat dipercaya, seperti API gateway atau backend. Gunakan kombinasi identitas pengguna, API key, dan alamat jaringan sesuai model ancaman; alamat IP saja dapat menghukum pengguna yang berbagi jaringan dan dapat berubah.
Beri batas khusus pada login, reset password, pembuatan akun, upload, pencarian mahal, ekspor, dan endpoint yang memicu layanan berbayar. Rate limit perlu dilengkapi batas konkurensi, timeout, kuota ukuran, pembatalan pekerjaan, dan idempotensi untuk operasi yang dapat dikirim ulang.
Pesan error aman tetapi tetap dapat ditelusuri
Jangan mengirim stack trace, query database, path internal, token, isi konfigurasi, atau detail penyedia ke browser. Kembalikan kode status yang tepat, pesan yang dapat ditindaklanjuti tanpa membuka detail internal, dan ID korelasi.
{
"error": {
"code": "UPLOAD_TYPE_NOT_ALLOWED",
"message": "Tipe file tidak didukung.",
"requestId": "req_acak"
}
}Log server boleh memiliki detail diagnostik, tetapi harus melakukan redaksi cookie, header otorisasi, token reset, data pribadi, dan isi file. Pesan login juga sebaiknya tidak mengungkap apakah akun tertentu terdaftar jika hal itu memungkinkan enumerasi pengguna.
Pengujian negatif yang dapat dimasukkan ke CI
Pengujian positif membuktikan alur normal bekerja. Audit keamanan membutuhkan pengujian negatif yang membuktikan server menolak kondisi berbahaya. Jalankan pengujian terhadap environment terisolasi dengan database dan penyimpanan khusus pengujian.
Matriks pengujian minimum
- Request tanpa sesi ditolak dengan
401. - Pengguna biasa tidak dapat menjalankan operasi admin.
- Pengguna tenant A tidak dapat membaca atau mengubah objek tenant B.
- Field sensitif tambahan seperti
roledanownerIdditolak atau diabaikan secara aman. - Nilai kosong, salah tipe, terlalu panjang, negatif, atau di luar enum ditolak server.
- Request mutasi berbasis cookie tanpa token CSRF atau dengan origin tidak sah ditolak.
- Payload XSS disajikan sebagai teks atau disanitasi tanpa eksekusi.
- File dengan ekstensi gambar tetapi isi bukan gambar ditolak.
- Upload satu byte di atas batas ditolak tanpa menyimpan object final.
- Permintaan melebihi rate limit menerima respons terkontrol dan tidak menjalankan operasi mahal.
- Respons error tidak mengandung stack trace, secret, path internal, atau query.
// Pseudocode integration test; adaptasikan ke test runner proyek.
test('anggota tidak dapat mengubah proyek tenant lain', async () => {
const response = await api.patch('/projects/project-tenant-b', {
session: memberOfTenantA,
json: { name: 'diambil alih' },
csrf: 'valid'
});
expect([403, 404]).toContain(response.status);
expect(await database.projectName('project-tenant-b'))
.toBe('nama semula');
});
test('upload palsu tidak lolos pemeriksaan konten', async () => {
const response = await api.upload('/uploads', {
session: validUser,
filename: 'avatar.png',
contentType: 'image/png',
bytes: bytes('<script>alert(1)</script>')
});
expect(response.status).toBe(400);
expect(await storage.finalObjectCount()).toBe(0);
});Selain status respons, periksa efek samping: database tidak berubah, file final tidak tercipta, pekerjaan antrean tidak dikirim, dan audit log tetap tercatat. Ini mencegah kasus server mengembalikan error setelah perubahan telanjur terjadi.
Tambahkan pemindaian secret pada source dan artefak build, pemeriksaan dependensi, serta pengujian header keamanan ke pipeline. Pemindaian berbasis pola atau entropi dapat menghasilkan false positive dan tidak menggantikan review manual. Jika alat menemukan kredensial nyata, perlakukan sebagai insiden dan rotasi kredensial, bukan sekadar menghapus baris.
Checklist review akhir
Arsitektur dan kepercayaan
- Threat model mencantumkan aset, aktor, endpoint, dan batas browser-server.
- Setiap kontrol UI memiliki kontrol server yang setara.
- CORS, route guard, dan elemen tersembunyi tidak dianggap sebagai otorisasi.
Secret dan sesi
- Tidak ada secret dalam bundle, source map, konfigurasi publik, atau log.
- Secret yang pernah terpapar telah dicabut dan dirotasi.
- Token sesi tidak disimpan di
localStoragetanpa analisis risiko yang terdokumentasi. - Cookie sesi menggunakan
Secure,HttpOnly, dan kebijakanSameSiteyang sesuai. - Endpoint mutasi berbasis cookie memiliki proteksi CSRF.
- Sesi dapat kedaluwarsa, dirotasi, dan dicabut.
Input, output, dan otorisasi
- Semua input divalidasi server, termasuk tipe, panjang, enum, dan aturan bisnis.
- Mass assignment dicegah dengan pemetaan field yang diizinkan.
- Otorisasi diperiksa pada tindakan dan objek yang dituju.
- Rendering menggunakan encoding atau sanitasi sesuai konteks.
- Tidak ada penggunaan HTML mentah atau URL dinamis tanpa peninjauan.
- CSP diterapkan sebagai pertahanan berlapis dan dipantau.
Upload dan ketahanan API
- Ukuran upload dibatasi saat streaming.
- Tipe file diverifikasi dari isi dan struktur, bukan hanya ekstensi atau header.
- File menggunakan nama buatan server serta disimpan secara privat atau di luar web root.
- Ada karantina, pemindaian, kuota, dan kebijakan retensi sesuai risiko.
- Endpoint mahal memiliki rate limit, timeout, dan batas konkurensi.
- Error publik aman, sedangkan log internal memiliki redaksi dan ID korelasi.
CI dan bukti keamanan
- Kasus tanpa autentikasi, lintas tenant, bypass validasi, CSRF, XSS, dan upload palsu diuji otomatis.
- Pengujian memeriksa efek samping, bukan hanya status HTTP.
- Artefak frontend dipindai untuk secret setelah build.
- Kegagalan pengujian keamanan memblokir merge atau deployment.
Audit selesai bukan ketika kode terlihat aman, melainkan ketika asumsi keamanan utama memiliki kontrol di sisi tepercaya dan dapat dibuktikan melalui pengujian. Frontend buatan AI dapat mempercepat implementasi, tetapi keputusan tentang autentikasi, otorisasi, validasi, dan penanganan file tetap harus ditinjau sebagai desain sistem, bukan detail tampilan.
Komentar
0 komentar
Masuk ke akun kamu untuk ikut berkomentar.
Belum ada komentar
Jadilah yang pertama ikut berdiskusi!