Jaga'yı neden yazdım: XSS'in kenarda kalan yerleri
Doğukan Batal
Bu yazıda
React ve Vue seni XSS saldırılarının çoğundan varsayılan olarak korur. Değerleri kendileri escape eder, sık yapılan hataları yapmayı zorlaştırırlar.
Geriye kalan sorunlar uçlarda birikir. Uç dediğim, o varsayılan korumanın dışına adım attığın yerler:
- ham HTML basmak
- dinamik attribute vermek
- kullanıcıya özel stil izni tanımak
- script’in içine veri gömmek
Gerçek projelerde bunlara tekrar tekrar takıldım ve sonunda hepsi için küçük bir kütüphane yazdım: Jaga (yeni sekmede açılır). Bu yazıda her ucu sırayla ele alıyorum: orada ne ters gidiyor, daha güvenli kural ne, Jaga bu kuralı nasıl uyguluyor.

Jaga, HTML şablonları için bağımlılığı olmayan, çok hafif bir güvenlik katmanı. Kullanıcı girdisiyle DOM arasında durur ve XSS’e karşı bağlama göre koruma sağlar.
1. Ham HTML (örneğin React’teki dangerouslySetInnerHTML)
Bazen gerçekten HTML basman gerekir:
<div dangerouslySetInnerHTML={{ __html: content }} />
content temizlenmemişse kod çalıştırabilir:
<img src="x" onerror="alert(1)">
Akla gelen çözümlerin hepsinin bir bedeli var. Her şeyi escape edersen geçerli HTML de bozulur. Regex ile filtrelemek güvensizdir ve bazı durumları kaçırır. DOM tabanlı sanitizer’lar daha güvenli ama genelde ağırdır, sunucu tarafında daha da ağır.
Daha iyi kural, HTML’i yapısına bakarak temizlemek. script gibi tehlikeli etiketleri çıkarırsın, onerror ve onclick gibi güvensiz attribute’ları atarsın, güvenli işaretlemeye dokunmazsın.
Jaga bunu şablon seviyesinde yapıyor. HTML’i bir string gibi görmüyor; render etmeden önce parse edip filtreliyor. Güvensiz düğümler çıktıya hiç ulaşmıyor.
2. Attribute’lar
Escape etmek attribute’ları korumaz:
<a href={userInput}>Click</a>
userInput şuysa:
javascript:alert(1)
tarayıcı bunu çalıştırır. Escape karakterlerle ilgilenir, buradaki sorun ise değerin ne anlama geldiği. javascript:alert(1) içinde escape edilecek tek bir karakter yok, ama yine de bir script.
Güvenli kural şu: href’in bir URL olduğunu bil, yalnızca https ve mailto gibi güvenli protokollere izin ver, gerisini engelle.
Jaga href’in URL bağlamı olduğunu tanıyor. Değeri inceliyor, normalize ediyor ve javascript: gibi tehlikeli protokolleri, gizlenmiş hallerini de dahil ederek render’dan önce engelliyor.
3. CSS injection
CSS’in de bir güvenlik riski olduğu kolayca unutuluyor:
<div style={userStyle}></div>
Kontrol edilmezse saldırgan şunu gömebilir:
background: url("javascript:alert(1)");
ya da gizlenmiş bir değer:
color: \6a avascript:alert(1);
CSS’i filtrelemek zor. Kendi sözdizimi ve parse kuralları var, url() gibi fonksiyonlara izin veriyor, yukarıdaki gibi escape numaralarını destekliyor.
Sağlam kural, CSS’i düzgünce parse etmek, color ve margin gibi güvenli bir özellik kümesine izin vermek, tehlikeli değerleri ve fonksiyonları engellemek.
Jaga’da bunun için küçük bir sözcüksel CSS sanitizer var. Stil string’ini token’lara ayırıyor ve yalnızca güvenli özellik ve değerlerin geçmesine izin veriyor. Beklenen kalıplara uymayan her şey reddediliyor.
4. Sunucu tarafında render
Artık birçok uygulama sunucuda render ediliyor (Next.js ve benzerleri), sanitizer’ların çoğu ise tarayıcıdaki DOM’a dayanıyor. Sunucuda bu, jsdom gibi ek bağımlılıklar, daha fazla bellek ve daha yavaş çalışma demek.
Burada DOM gerektirmeyen, tarayıcıda da sunucuda da aynı davranan ve küçük kalan bir araç işini kolaylaştırır.
Jaga DOM API’lerini kullanmıyor; Node, Bun, Deno ve tarayıcıda aynı şekilde çalışıyor. Hiç bağımlılığı yok, yani projeni şişirmiyor ve tedarik zinciri riski eklemiyor. Bir de HTML’deki gereksiz boşlukları temizleyen bir minifier’ı var; <pre> ya da <textarea> içindeki boşluklara dokunmuyor. SSR’da küçük bir hız kazancı sağlıyor.
5. Script bağlamı ve JSON
Script’in içine veri gömmek de ters gidebilir:
<script>
window.__STATE__ = ${data}
</script>
data şunu içeriyorsa:
"</script><script>alert(1)</script>"
script erken kapanır ve kendi kodunu çalıştırır. Bunu önlemek için JSON’ı güvenli biçimde encode etmek ve </script> gibi dizileri escape etmek gerekiyor.
Jaga’da bunun için j.json() yardımcısı var. Veriyi serialize ediyor ve script’ten taşabilecek her şeyi escape ediyor, böylece veri script’in içinde kalıyor. Jaga Trusted Types’ı da destekliyor: modern tarayıcıların güvendiği nesneler döndürebiliyor. Sıkı bir Content Security Policy (CSP) uygularken ekstra iş çıkarmıyor.
Güvenlik bağlama bağlı
Bütün örneklerde aynı şey tekrar ediyor: her yer kendi kuralını istiyor.
| Bağlam | Risk | Kural | Jaga’da |
|---|---|---|---|
| Ham HTML | script çalışması | yapıya göre temizle | güvensiz etiket ve attribute’ları atan yapısal sanitizer |
Attribute’lar (href, src) |
javascript: URL’leri |
yalnızca güvenli protokoller | URL bağlamını tanır, güvensiz protokolü engeller |
| CSS | zararlı stiller, gizli injection | parse et, bilinen özellik ve değerlere izin ver | sözcüksel CSS sanitizer |
| Script / JSON | </script> ile dışarı taşma |
JSON’ı güvenli encode et | j.json() |
| SSR | ağır bağımlılıklar, DOM şartı | DOM’suz araçlar | sıfır bağımlılık; Node, Bun, Deno ve tarayıcıda çalışır |
Her durumu elle de çözebilirsin. Ama bağlamı bilen tek bir katman her seferinde doğru kuralı uygular. Kendi yazdığın doğrulama azalır, uç durum hataları azalır, korumanın seviyesi uygulamanın her yerinde aynı olur.
Jaga’yı denemek
Jaga npm’de jagajs adıyla duruyor:
npm install jagajs
j template tag’i her değerin nereye gittiğini biliyor ve ona uyan kuralı uyguluyor:
import { j } from "jagajs";
const userUrl = "javascript:alert(1)";
const userName = '"><img src=x onerror=alert(1)>';
const html = j`
<div title="${userName}">
<a href="${userUrl}">Profile</a>
</div>
`;
// <div title=""><img src=x onerror=alert(1)>">
// <a href="about:blank">Profile</a>
// </div>
Ham HTML için, örneğin bir zengin metin editöründen gelen çıktıda, ayrı bir sanitizer var:
import { sanitize } from "jagajs/sanitize";
const clean = sanitize(userRichText, {
allowedTags: ["b", "i", "p", "a"],
allowedAttrs: { a: ["href"] },
});
Bu sorunları çözmenin tek yolu Jaga değil. Ama aynı uçlarla uğraşıyorsan iyi bir başlangıç noktası olabilir, en azından probleme nasıl yaklaşılacağına dair bir referans. Kodu ve dokümantasyonun tamamı GitHub’da (yeni sekmede açılır).