Studi Kasus Atmo LMS: Trusted Types API Pangkas DOM XSS Finding dari 11 ke 0 dalam 21 Hari di 2026
TL;DR: Audit keamanan Q1 2026 di Atmo LMS menemukan 11 DOM-based XSS finding lewat Quill rich text editor dan widget komentar. Setelah Vito Atmo memasang Trusted Types API secara bertahap (report-only 14 hari, enforcing hari ke-22), seluruh finding hilang tanpa keluhan pengguna atau drop di engagement halaman pembelajaran.
Pada Februari 2026, tim Atmo LMS menerima laporan audit keamanan dari pihak ketiga. Hasilnya mengejutkan: 11 finding DOM-based XSS di tiga modul utama. Tujuh di antaranya berasal dari Quill rich text editor yang dipakai instruktur menulis materi, tiga dari widget komentar mahasiswa, dan satu dari preview HTML email broadcast.
Yang lebih mengkhawatirkan, CSP yang sudah aktif penuh tidak menangkap satupun. Semua eksploitasi terjadi lewat sink internal seperti innerHTML dan dangerouslySetInnerHTML React yang menerima string mentah dari user-generated content. Lihat juga Content Security Policy.
Konteks Aplikasi
Atmo LMS adalah Learning Management System internal yang dipakai 4.200 mahasiswa aktif dan 180 instruktur. Stack utama Next.js 15 + Supabase. Tiga sink berbahaya teridentifikasi:
| Sink | Modul | Severity |
|---|---|---|
dangerouslySetInnerHTML | Quill content render | High (7 finding) |
innerHTML | Komentar mahasiswa | Medium (3 finding) |
document.write di preview iframe | Email broadcast | Critical (1 finding) |
Mengapa CSP Tidak Cukup
CSP standar memblokir loading script eksternal yang tidak whitelist. Tapi untuk DOM XSS, payload sudah ada di dalam string yang valid dari sisi CSP (tidak ada <script> tag eksternal). Payload klasik <img src=x onerror=alert(document.cookie)> lolos CSP script-src 'self' karena <img> bukan kategori script.
Praktik standar di industri menunjukkan DOM XSS mengisi mayoritas finding sisa setelah CSP aktif. Untuk SPA yang sering memanipulasi DOM dengan data tidak tepercaya, Trusted Types API adalah lapisan pelengkap wajib.
Rencana Implementasi 21 Hari
Vito Atmo memetakan rollout jadi tiga fase:
Fase 1: Report-Only Mode (Hari 1-14)
Header Content-Security-Policy-Report-Only ditambahkan di middleware Next.js:
response.headers.set(
'Content-Security-Policy-Report-Only',
"require-trusted-types-for 'script'; trusted-types default quill-policy comment-policy; report-uri /api/csp-report"
);
Tujuannya: kumpulkan data violation real tanpa break aplikasi. Selama 14 hari, endpoint /api/csp-report menerima 847 laporan unik. Setelah dikelompokkan, hanya 23 lokasi kode yang melanggar (banyak duplikasi dari Quill yang rerender).
Fase 2: Patch Sink (Hari 15-21)
Tiga policy didaftarkan:
trustedTypes.createPolicy('quill-policy', {
createHTML: (s) => DOMPurify.sanitize(s, {ALLOWED_TAGS: ['p','b','i','ul','ol','li','a','img']})
});
trustedTypes.createPolicy('comment-policy', {
createHTML: (s) => DOMPurify.sanitize(s, {ALLOWED_TAGS: ['p','br','a'], ALLOWED_ATTR: ['href']})
});
trustedTypes.createPolicy('default', {
createHTML: (s) => DOMPurify.sanitize(s)
});
Tiap policy punya allowlist HTML tag berbeda sesuai konteks. Quill butuh tag rich text, komentar hanya butuh teks dan link. Pemisahan ini mencegah pengguna komentar menyusupkan <img> yang bisa dipakai tracking pixel tidak resmi.
Fase 3: Enforcing (Hari 22)
Pindah header dari Report-Only ke Content-Security-Policy mengaktifkan enforcing. Audit ulang Acunetix hari ke-25 menunjukkan 0 finding DOM XSS.
Hasil Pasca-Implementasi
| Metrik Keamanan | Sebelum | Sesudah |
|---|---|---|
| DOM XSS finding (Acunetix) | 11 | 0 |
| Burp Suite Active Scan | 6 issues | 0 issues |
| Manual penetration test | 4 exploit success | 0 exploit success |
| Metrik UX | Sebelum | Sesudah |
|---|---|---|
| Quill editor load time | 380 ms | 412 ms (+32 ms) |
| Komentar submission rate | 14 per hari | 14 per hari |
| Halaman materi engagement | 8 menit 14 detik | 8 menit 9 detik |
| Keluhan support terkait fitur | 0 baseline | 2 keluhan (resolved hari 2) |
Penambahan 32 ms di Quill load berasal dari DOMPurify (18 KB gzipped) yang dieksekusi. Dari pengamatan Web Vitals field data, dampak ke INP sangat kecil, masih dalam zona "good".
Dua keluhan support muncul di hari 1 dan 2: instruktur tidak bisa paste table dari Microsoft Word. Solusinya: tambahkan 'table','tr','td','th' ke ALLOWED_TAGS policy Quill. Resolved dalam 6 jam.
Pelajaran Penting
Dari pengalaman implementasi ini, ada tiga catatan untuk tim lain:
Pertama, jangan langsung enforcing. Report-only mode wajib minimal 14 hari supaya data violation lengkap. Aplikasi besar punya banyak edge case yang baru muncul di traffic real.
Kedua, policy per modul, bukan satu policy global. Quill butuh tag berbeda dari komentar. Default policy paling sederhana, modul khusus lebih ketat. Lihat juga Sec-Fetch Headers sebagai pelengkap di sisi server.
Ketiga, siapkan komunikasi internal. Instruktur dan moderator perlu diberi tahu bahwa beberapa fitur paste akan terbatas. Kirim memo 3 hari sebelum enforcing rilis. Dokumentasi lengkap tersedia di web.dev Trusted Types.
Pertanyaan Umum
Apakah ROI implementasi Trusted Types sebanding?
Untuk Atmo LMS dengan 4.200 mahasiswa aktif yang menyimpan data pembelajaran sensitif, satu insiden XSS yang berhasil bisa berarti reset password massal dan churn 5-10 persen. Investasi 21 hari engineering jauh lebih murah.
Apakah React 19 menambah kerumitan?
React 19 sudah punya integrasi Trusted Types native lewat policy react-renderer. Tidak perlu daftar manual. Tapi dangerouslySetInnerHTML tetap butuh wrapping eksplisit.
Bagaimana kalau pengguna pakai browser lawas?
Browser yang tidak support Trusted Types akan mengabaikan header require-trusted-types-for. Aplikasi tetap berfungsi normal di Safari 16 dan Firefox 119 ke bawah, tanpa proteksi tambahan. Untuk audit, persentase pengguna browser tersebut dikalkulasi dari GA4. Di Atmo LMS, persentasenya 4 persen.
Apakah ini menggantikan code review?
Tidak. Trusted Types adalah jaring pengaman, bukan pengganti review. Review tetap dilakukan, tapi human error yang tidak tertangkap reviewer akan ditolak browser sebagai lapisan kedua.
Penutup
Setelah enforcing aktif, tim engineering Atmo LMS punya kebiasaan baru: setiap PR yang menyentuh sink DOM otomatis ditest di staging dengan CSP report-only aktif. Jika ada violation, PR ditolak sebelum merge. Trusted Types tidak hanya menutup celah, tapi juga mengubah pola kerja jadi lebih defensif.
Artikel Terkait

Case Study
Studi Kasus Vetmo: Incrementality Test Ungkap 34 Persen Spend Iklan Meta Tidak Berkontribusi pada Konversi di 2026
Saat Vetmo lari incrementality test selama 6 minggu, terungkap 34 persen budget Meta hanya menyentuh audiens yang akan beli secara organik. Pelajari metode dan hasilnya.
Case Study
Studi Kasus Aris Setiawan: MQL Scoring Berbasis WhatsApp Engagement Pangkas Sales Cycle dari 28 ke 11 Hari di 2026
Bagaimana setup MQL scoring berbobot WhatsApp engagement memendekkan siklus closing konsultasi Aris Setiawan dari 28 ke 11 hari dengan threshold 65 poin.
Case Study
Studi Kasus Vetmo: Pindah ke Attention Metrics Pangkas Spend Display Boros dari 38 ke 9 Persen di 2026
Vetmo memakai Attention Metrics dari DoubleVerify menggantikan viewability klasik. Spend Display non-konversi turun dari 38 ke 9 persen dalam 42 hari.
Butuh website yang benar-benar bekerja?
Hubungi Vito untuk konsultasi gratis 15 menit.
WhatsApp Sekarang