~/zafer
Tüm yazılara dön
2 dk okuma

Kullanıcı Kontrollü Markdown İçeriğini XSS-Güvenli Render Etmek

Yazı içeriği (contentTr/contentEn) admin formunda düz bir <textarea>'ya markdown olarak yazılıyor ve veritabanına string olarak kaydediliyor. Bu, herhangi bir HTML/script parçasının da içeriğe girebileceği anlamına geliyor — admin panelinin kendisi güvenli (Bearer token arkasında) ama render edilen çıktı, tarayıcıda çalışan gerçek HTML.

İki adımlı render zinciri

// apps/web/src/app/post/[slug]/page.tsx
const html = useMemo(
  () => DOMPurify.sanitize(marked.parse(localized.content, { async: false })),
  [localized?.content],
);

marked markdown'ı HTML'e çeviriyor, DOMPurify de o HTML'i dangerouslySetInnerHTML'e vermeden önce temizliyor — <script> etiketleri, onerror/onclick gibi event handler attribute'ları, javascript: URI'leri gibi çalıştırılabilir her şeyi söküyor.

Neden iki ayrı kütüphane, tek "güvenli markdown" paketi değil

Markdown-to-HTML dönüşümü ve HTML sanitization birbirinden bağımsız problemler. marked render kalitesine, DOMPurify güvenliğe odaklanan, aktif geliştirilen, kendi alanında olgun kütüphaneler. İkisini birleştiren "hepsi bir arada" bir paket yerine, her adımı kendi işini en iyi yapan araçla çözüp aralarına sanitize() çağrısını koymak — sanitization adımının yanlışlıkla atlanma ihtimalini de useMemo içinde her render'da garanti altına alıyor.

dangerouslySetInnerHTML hâlâ "dangerous" mi

İsim React'te değişmiyor ama içerik DOMPurify'dan geçtiği için tehlike gerçek anlamda ortadan kalkıyor — API sanitize edilmemiş content'i hâlâ olduğu gibi döndürüyor (bu doğru: sanitization bir render-katmanı sorumluluğu, veri katmanının değil), ama tarayıcıya asla ham haliyle basılmıyor. Bu ayrım önemli: veriyi API'de "temizlemek" ileride farklı bir tüketici (örneğin PDF export, RSS feed) geldiğinde onun kendi sanitization ihtiyacını gizler; her tüketici kendi render bağlamında kendi sanitize adımını çalıştırmalı.