June 30, 2026
Content Security Policy’yi Sıfırdan Yazarken Yapılan Yaygın Hatalar
Production’da en sık karşılaşılan dört CSP yanlışı ve nereden başlamalı sorusuna pratik bir yaklaşım.
By Kadir Büyükgüçlü
3 min read
Bir gün CSP yazmanız gerekir. Belki bir güvenlik taraması uygulaması sitenize düşük puan vermiş, belki güvenlik incelemesinde "CSP nerede?" sorusu ortaya çıkmış veya belki de bir uyumluluk zorunluluğu ortaya çıkmış olabilir. Tarayıcıya Content Security Policy yazmaya çalışıyorsunuz ve iki üç saat sonra hala ilk policy'nizi yazmamışsınızdır. CSP'nin bir öğrenme eğrisi var. Bu eğriyi geçmeden doğru kullanımı genelde pek mümkün olmuyor.
Bu yazıda CSP'nin niye bu kadar zor olduğunu anlatacağım. Sahada sıkça karşılaşılan dört hatayı listeleyeceğiz. Sonunda da iyi bir CSP'nin nasıl görünmesi gerektiğini, hangi tool'lar ile işinizin çok kolaylaşabileceğini göstermeye çalışacağım.
CSP'nin diğer header'lardan farkı
HTTP güvenlik header'larının çoğu binary çalışır. HSTS var ya da yok. X-Frame-Options DENY ya da SAMEORIGIN. X-Content-Type-Options nosniff. Set ettiniz, geçtiniz, yanlış yapma şansınız pek yok.
CSP ise farklı. Kendi syntax'ı, kendi mantığı var. Aşağıdaki örneğe bakalım:
default-src 'self';
script-src 'self' 'nonce-abc123' https://cdn.example.com;
style-src 'self' 'unsafe-inline';
img-src 'self' data: https:;
font-src 'self';
connect-src 'self' wss://realtime.example.com;
frame-ancestors 'none';
base-uri 'self';
form-action 'self';
object-src 'none';default-src 'self';
script-src 'self' 'nonce-abc123' https://cdn.example.com;
style-src 'self' 'unsafe-inline';
img-src 'self' data: https:;
font-src 'self';
connect-src 'self' wss://realtime.example.com;
frame-ancestors 'none';
base-uri 'self';
form-action 'self';
object-src 'none';On-onbeş direktif var. Her birinin allowlist'i, özel keyword'ü ('self', 'none', 'unsafe-inline', 'strict-dynamic'), scheme source'u (data:, https:), nonce ve hash mantığı bulunuyor.
Bunu sıfırdan doğru yazmak için MDN dokümantasyonunu dört-beş tab'da açık tutmak gerekiyor. Üstüne sıkılaştırma dengesi var. Çok gevşek yazarsanız XSS koruması olmuyor, çok sıkı yazarsanız Google Analytics çalışmıyor, kendi inline script'iniz patlıyor.
Sahada gördüğüm dört yaygın hata
script-src 'unsafe-inline'
Uygulama inline script kullanmak zorunda kaldığında, birçok ekip ilk refleks olarak script-src 'unsafe-inline' tanımını ekliyor. Ancak bu yaklaşım, CSP'nin XSS'e karşı sağladığı korumanın büyük ölçüde etkisiz hale gelmesine neden olur. Bunun temel sebebi, XSS saldırılarının büyük çoğunluğunun zaten inline script enjeksiyonu üzerinden gerçekleştirilmesidir. Dolayısıyla unsafe-inline kullanımı, CSP'nin en önemli güvenlik kazanımlarından birini önemli ölçüde ortadan kaldırır.
Doğru çözüm unsafe-inline değil, nonce kullanmak.
Mantık aslında oldukça basittir. Sunucu her sayfa isteğinde tek kullanımlık, rastgele bir nonce değeri üretir. Bu değer hem CSP header'ına hem de çalışmasına izin verilen
Tarayıcı sadece doğru nonce değerine sahip script'leri çalıştırır. Saldırgan bu değeri önceden bilemeyeceği için sayfaya enjekte ettiği zararlı script çalışmaz. Böylece hem gerekli inline script'ler kullanılabilir hem de CSP'nin sağladığı XSS koruması korunmuş olur.
Wildcard host'lar
script-src https: yazmak "her HTTPS domain'den script yüklenebilir" demek. Bir saldırgan kendi domain'inden saldırı script'ini yükletebilir. Bu direktif aslında yazılmamış gibi sayılır. Amacımıza çok da hizmet etmedi.
- yazmak ise daha kötü. script-src * CSP'nin var olmadığı duruma çok yakın bir koruma sağlar. Sadece data: ve blob: URI'lerini engeller, başka bir şeyi engellemez.
Allowlist'ler spesifik olmalı. https://cdn.jsdelivr.net/npm/jquery@3.6.0/ gibi.
CDN allowlist bypass
Genelde script-src cdn.jsdelivr.net yazılır ve geçilir. Çünkü oradan jQuery çekersiniz. Sorun şu. jsdelivr public package CDN' idir. Saldırgan npm'e bir paket push edebilir, sonra sizin sitenizde
Bu saldırı vektörüne "CDN allowlist bypass" denir. cdnjs, unpkg, jsdelivr gibi public CDN'ler aynı kategoride. Çözüm allowlist'i daraltmak. Sadece spesifik path veya hash kullanmak.
Eksik object-src ve base-uri
Bu direktifler default-src'den otomatik kalıtım almaz. Eklemezseniz browser sınırsız ve yüklemesine izin verir. Saldırgan tag'i ile sayfanın resource yükleme kökünü değiştirebilir.
Minimum güvenli baseline:
object-src 'none';
base-uri 'self';object-src 'none';
base-uri 'self';İk satır eklenir ve iki risk vektörü kapanır.
Modern bir CSP'nin hedefi
Günümüzde önerilen yaklaşım, nonce + strict-dynamic kullanımına dayanan Strict CSP modelidir. Bu modelde tarayıcı yalnızca geçerli nonce değerine sahip script'leri çalıştırır; güvenilen script'lerin dinamik olarak yüklediği diğer script'lere de izin verir. Böylece hem güçlü bir XSS koruması sağlanır hem de uzun allowlist'leri yönetme ihtiyacı ortadan kalkar. Şöyle görünür:
default-src 'none';
script-src 'nonce-abc123' 'strict-dynamic';
style-src 'nonce-abc123';
img-src 'self' data:;
connect-src 'self';
font-src 'self';
object-src 'none';
base-uri 'self';
form-action 'self';
frame-ancestors 'none';default-src 'none';
script-src 'nonce-abc123' 'strict-dynamic';
style-src 'nonce-abc123';
img-src 'self' data:;
connect-src 'self';
font-src 'self';
object-src 'none';
base-uri 'self';
form-action 'self';
frame-ancestors 'none';'strict-dynamic' sayesinde dinamik yüklenen script'ler nonce'lu parent'tan kalıtım alır.
Bu yaklaşım her projede uygulanamayabilir. Özellikle eski (legacy) uygulamalarda tüm inline script'leri nonce kullanacak şekilde dönüştürmek zor olabilir. Ancak yeni geliştirilen uygulamalarda hedef mimari olarak tercih edilmesi gereken CSP modeli budur.
Önce report-only deploy
CSP'yi production'a deploy ederken kesinlikle önce report-only modunda bir süre deneyin:
Content-Security-Policy-Report-Only: default-src 'self'; …
Bu mod browser'a "policy'yi enforce etme ama ihlalleri raporla" der. Bir hafta sunucu log'larında veya report-uri reporlarına bakın. Gerçek hata var mı kontrol edin ve allowlist güncellemesi gerekiyorsa düzenlemeleri yapın. Sonra Content-Security-Policy: ile enforce moda geçin.
Bu adımı atlamak, "production'da CSP yüzünden site patladı" hikayesinin en yaygın sebebi.
CSP yazmak için çok pratik yol
Manuel olarak yazmak öğrenme aşamasında kıymetli. Spec'leri okuyup, direktifleri elle ekleyip, hataları gözle yakalamak. Ama pratikte tekrar tekrar bu işi yapmak verimsiz.
HeaderLab CSP Builder açık kaynak bir tool. Visual editor ile direktifleri ekliyorsunuz, source'ları belirtiyorsunuz, output text'i kopyalayıp sunucu config'ine yapıştırıyorsunuz. 'unsafe-inline' gibi tehlikeli source'lar otomatik flag'leniyor. Mevcut CSP'leri başlangıç template'i olarak kullanabilirsiniz (strict, balanced, permissive üç hazır şablon var).
Çıktıyı doğrudan Nginx, Apache veya Cloudflare config'inize yapıştırabilirsiniz. Tüm analiz tarayıcıda, hiçbir veri sunucuya gitmiyor. Source code GitHub'da.
Alternatif olarak Google'ın csp-evaluator.withgoogle.com sitesi var, ama daha çok mevcut CSP'lerinizi analiz etmeye odaklı.
Bir cümlelik özet
CSP'yi doğru yazmak için 'unsafe-inline' ve wildcard'lardan kaçının, object-src 'none' ve base-uri 'self' mutlaka ekleyin, yeni projelerde modern pattern olarak 'strict-dynamic' + nonce kullanın, production'a geçmeden önce mutlaka report-only modunda test edin.
Mevcut bir CSP'yi analiz etmek başka bir konu, onu bir sonraki yazıda anlatmaya çalışacağım.