Mert Özen Her Satırda Daha İleri
Güvenlik Frontend Güvenliği — #03

Angular'a Strict CSP Uygulamak: Nonce, strict-dynamic, autoCsp ve Report-Only ile Kademeli Geçiş

Mert Özen 8 Kas 2026 12 dk 4 görüntülenme
Angular'a Strict CSP Uygulamak: Nonce, strict-dynamic, autoCsp ve Report-Only ile Kademeli Geçiş

Host listesine dayalı CSP çoğu zaman aşılabilir; Angular için doğru yol nonce ya da hash ile 'strict-dynamic' kullanan strict bir policy. Bu yazıda statik hosting için autoCsp'nin ürettiği hash'li policy'yi, ASP.NET Core'dan istek başına nonce ve ngCspNonce ile servisi, base-uri 'none' tuzağını, üçüncü parti script'lerle uzlaşmayı ve Report-Only ile kırmadan enforce'a geçişi headless Chrome çıktılarıyla gösteriyoruz.

Sürüm notu: Bu yazıdaki örnekler Angular 22.2, .NET 10, Node.js 24 ve Chrome 156 ile denenmiştir.

Önce Tahmin Et

Bookstore'un Angular uygulaması bir CDN'den (Content Delivery Network) servis ediliyor ve ekip şu CSP'yi (Content Security Policy) ekledi: script-src 'self' https://cdn.example.com; object-src 'none'. Policy'de 'unsafe-inline' yok, script'ler yalnız iki origin'den yüklenebiliyor. Kâğıt üzerinde sıkı görünüyor. Bir saldırgan sayfaya HTML (HyperText Markup Language) enjekte edebildiğinde bu policy onu durdurur mu?

Cevap, cdn.example.com'da ne olduğuna bağlı ve sen bunu kontrol etmiyorsun. O host'ta eski bir AngularJS sürümü, JSONP (JavaScript Object Notation with Padding) callback'i dönen bir endpoint ya da kullanıcı içeriği barındıran bir klasör varsa saldırgan "izinli" origin'den kendi kodunu çalıştırır. Google'daki araştırmacıların ACM'nin (Association for Computing Machinery) CCS (Conference on Computer and Communications Security) 2016 konferansında yayımladığı "CSP Is Dead, Long Live CSP!" makalesi bunu ölçtü: script çalıştırmayı sınırlamaya çalışan policy'lerin yüzde 94,68'i etkisizdi. Aynı makale bugünkü cevabı da önerdi: host listesi yerine nonce ya da hash ve 'strict-dynamic'. Bu yazıda bu strict policy'yi Angular'a üç farklı servis biçimiyle uyguluyor, üçüncü parti script'lerle uzlaştırıyor ve Report-Only ile kırmadan production'a alıyoruz. CSP'nin ne olduğu ve 'unsafe-inline''ın neden korumayı boşa çıkardığı Web Güvenliği (OWASP Top 10) serisinin "OWASP A05: Injection (2)" yazısında anlatıldı. ASP.NET Core'da CSP header'ını middleware ile üretmek ve rapor endpoint'i yazmak ise "ASP.NET Core Güvenliği" serisinin 04. yazısının konusu; burada Angular tarafına odaklanıyoruz.

Strict CSP'nin Parçaları

web.dev'deki "Mitigate cross-site scripting (XSS) with a strict Content Security Policy" rehberinin önerdiği nonce tabanlı policy üç direktiften oluşuyor:

Content-Security-Policy:
  script-src 'nonce-{RANDOM}' 'strict-dynamic';
  object-src 'none';
  base-uri 'none';

'nonce-{RANDOM}', yalnız aynı nonce attribute'unu taşıyan <script> element'lerinin çalışmasına izin verir. Nonce her yanıtta yeniden üretilen, tahmin edilemez bir değerdir; saldırgan enjekte ettiği script'e doğru nonce'u yazamaz. Hash tabanlı alternatifte nonce yerine inline script içeriğinin SHA (Secure Hash Algorithm) ailesinden SHA-256 hash'i yazılır; HTML statik kalabilir. 'strict-dynamic' güveni yayar: nonce'lu ya da hash'li bir script'in document.createElement('script') ile eklediği script'ler de çalışabilir. Bu sayede lazy loading yapan bundle'lar ve tag manager loader'ları policy'ye tek tek yazılmak zorunda kalmaz. 'strict-dynamic' varken tarayıcı host listelerini ve 'self''i yok sayar.

Rehber eski tarayıcılar için https: ve 'unsafe-inline' eklemeyi de öneriyor: CSP Level 3 (CSP3) destekleyen tarayıcılar nonce ya da hash gördüğünde 'unsafe-inline''ı, 'strict-dynamic' gördüğünde https:'i yok sayar. MDN'in (MDN Web Docs) browser-compat-data verisine göre 'strict-dynamic' Chrome 52, Firefox 52 ve Safari 15.4'ten beri destekleniyor; bu fallback'ler bugün daha çok güvenlik ağı.

base-uri konusunda Angular'a özel bir ayrıntı var. Angular'ın index.html'i <base href="/"> içerir ve router'ın ve göreli script yollarının çalışması bu etikete bağlıdır. Rehberdeki base-uri 'none''ı aynen alıp /books/42 gibi iç içe bir route'u açtığımda:

[console.error] Setting the document's base URI to 'http://localhost:19431/' violates the following Content Security Policy directive: "base-uri 'none'". The action has been blocked.
[console.log] [csp-violation] directive=base-uri blocked=http://localhost:19431/ disposition=enforce sample=
[console.error] Failed to load module script: Expected a JavaScript-or-Wasm module script but the server responded with a MIME type of "text/html". Strict MIME type checking is enforced for module scripts per HTML spec.
[status] 200
[eval] "no h1"

<base> engellenince göreli main-*.js yolu /books/ altına çözümlendi, sunucu SPA (Single-Page Application) fallback'iyle HTML döndü ve uygulama hiç açılmadı. Angular için doğru değer base-uri 'self'; Angular CLI'ın (Command-Line Interface) autoCsp ile ürettiği policy de bunu kullanıyor. object-src 'none' eklentileri kapatır. Clickjacking'e karşı frame-ancestors 'none' de eklenmeli, ama bu direktif yalnız HTTP (HyperText Transfer Protocol) header'ında çalışır; <meta> etiketindeki CSP'de tarayıcı onu yok sayar.

Kurulum 1: Statik Hosting ve autoCsp

Uygulama bir CDN'de ya da object storage'da statik dosya olarak duruyorsa her yanıtta yeni nonce üretecek bir sunucun yok. Angular CLI 19'dan beri bunun için hash tabanlı bir seçenek sunuyor: build sırasında index.html'deki inline script'lerin hash'ini hesaplayıp bir <meta http-equiv="Content-Security-Policy"> etiketi ekliyor. Schema'daki açıklamaya göre özellik hâlâ experimental/preview aşamasında ve varsayılan değeri false. Lab'da production'ın üstüne bir configuration ekledim:

"configurations": {
  "autocsp": {
    "security": {
      "autoCsp": true
    },
    "outputHashing": "all"
  }
}

ng build --configuration production,autocsp sonrası üretilen index.html:

<!doctype html>
<html lang="en" data-beasties-container="">
<head><meta http-equiv="Content-Security-Policy" content="script-src 'strict-dynamic' 'sha256-Jxq7Ku8wPURcenwJcJA2X6jP5a0H6l1jyi50NPSYFi0=' https: 'unsafe-inline';object-src 'none';base-uri 'self';">
  <meta charset="utf-8">
  <title>BookstoreWeb</title>
  <base href="/">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <link rel="icon" type="image/x-icon" href="favicon.ico">
<link rel="stylesheet" href="styles-5INURTSO.css"></head>
<body>
  <app-root></app-root>
<script>(() => {
  const scripts = [['main-DEUWGEUN.js', "module", false, false, null, null]];
  scripts.forEach(function(scriptUrl) {
    const s = document.createElement('script');
    s.src = scriptUrl[0];
    s.type = scriptUrl[1];
    s.async = scriptUrl[2];
    s.defer = scriptUrl[3];
    if (scriptUrl[4]) {
      s.integrity = scriptUrl[4];
    }
    if (scriptUrl[5]) {
      s.crossOrigin = scriptUrl[5];
    }
    document.lastElementChild.appendChild(s);
  });
})();
</script></body>
</html>

CLI, <script src="main-...js"> etiketini kaldırıp yerine onu dinamik olarak ekleyen küçük bir loader koydu ve yalnız o loader'ın hash'ini policy'ye yazdı; main.js'e güven 'strict-dynamic' ile geçiyor. Hash'i kendim de hesapladım, Jxq7Ku8wPURcenwJcJA2X6jP5a0H6l1jyi50NPSYFi0= çıktı. Dosya statik kaldığı için CDN'de rahatça cache'lenebilir.

angular.dev'deki sınırlar önemli: autoCsp yalnız script'leri kapsar, style-src ayrı yapılandırılmalıdır; frame-ancestors, report-uri ve sandbox <meta>'da çalışmadığı için HTTP header'ı olarak gönderilmelidir; ayrıca bir CSP header'ı da gönderiyorsan içinde script-src ve default-src olmamalıdır; SSR (Server-Side Rendering) ile kullanılamaz. Lab'da hosting'in header'ını şöyle verdim: style-src 'self' 'unsafe-inline'; object-src 'none'; base-uri 'self'; frame-ancestors 'none'. Tarayıcı iki policy'yi birlikte uygular. Önceki yazıdaki zafiyetli review component'ini bu build ile açtım:

[console.error] Executing inline event handler violates the following Content Security Policy directive 'script-src 'strict-dynamic' 'sha256-Jxq7Ku8wPURcenwJcJA2X6jP5a0H6l1jyi50NPSYFi0=' https: 'unsafe-inline''. Note that 'unsafe-inline' is ignored if either a hash or nonce value is present in the source list. The action has been blocked.
[console.log] [csp-violation] directive=script-src-attr blocked=inline disposition=enforce sample=
[console.error] Running the JavaScript URL violates the following Content Security Policy directive 'script-src 'strict-dynamic' 'sha256-Jxq7Ku8wPURcenwJcJA2X6jP5a0H6l1jyi50NPSYFi0=' https: 'unsafe-inline''. Note that 'unsafe-inline' is ignored if either a hash or nonce value is present in the source list. The action has been blocked.

Üç sink'teki onerror handler'ları için toplam altı ihlal kaydı düştü, javascript: link'i de engellendi; konsolda tek bir XSS fired satırı yok. Uygulamanın kendisi sorunsuz açıldı.

Kurulum 2: ASP.NET Core'dan Nonce ile Servis

Angular build'ini ASP.NET Core 10 servis ediyorsa her istekte nonce üretebilirsin. Angular tarafında tek gereken root element'e bir placeholder koymak:

<app-root ngCspNonce="__CSP_NONCE__"></app-root>

CLI bu attribute'u görünce değeri ürettiği bütün <script>, <style> ve stylesheet <link> etiketlerine kopyalıyor. Build çıktısının ilgili kısmı:

<style nonce="__CSP_NONCE__">body{font-family:sans-serif}</style><link rel="stylesheet" href="styles-KQ4ZCH26.css" nonce="__CSP_NONCE__" media="print" data-beasties-media="all"><noscript><link rel="stylesheet" href="styles-KQ4ZCH26.css" nonce="__CSP_NONCE__"></noscript></head>
<body>
  <app-root ngcspnonce="__CSP_NONCE__"></app-root>
<script src="main-DEUWGEUN.js" type="module" nonce="__CSP_NONCE__"></script><script nonce="__CSP_NONCE__">document.querySelectorAll('link[data-beasties-media]').forEach(function(l){l.media=l.getAttribute('data-beasties-media');l.removeAttribute('data-beasties-media')})</script></body>

Dikkat: styles.css boş değilken CLI'ın critical CSS (Cascading Style Sheets) inlining'i bir inline <style> ve stylesheet'i asenkron yükleyen bir inline <script> ekliyor. Nonce olmadan bu script de policy'ye takılır. Sunucu tarafı minimal API (Application Programming Interface) ile birkaç satır:

using System.Security.Cryptography;

var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();

// index.html wwwroot dışında durur, nonce'suz asla servis edilmez
string indexTemplate = await File.ReadAllTextAsync(
    Path.Combine(builder.Environment.ContentRootPath, "App", "index.html"));

app.UseStaticFiles();

app.MapFallback(async context =>
{
    string nonce = Convert.ToBase64String(RandomNumberGenerator.GetBytes(16));
    context.Response.Headers.ContentSecurityPolicy =
        $"script-src 'nonce-{nonce}' 'strict-dynamic' https: 'unsafe-inline'; " +
        $"style-src 'self' 'nonce-{nonce}'; object-src 'none'; base-uri 'self'; frame-ancestors 'none'";
    context.Response.Headers.CacheControl = "no-store";
    context.Response.ContentType = "text/html; charset=utf-8";
    await context.Response.WriteAsync(indexTemplate.Replace("__CSP_NONCE__", nonce));
});

app.Run();

Nonce, RandomNumberGenerator ile 16 bayt (128 bit) rastgele değerden üretiliyor. index.html bilerek wwwroot dışında: aksi hâlde /index.html isteği static file middleware'inden placeholder'lı hâliyle dönerdi. İki ardışık istek:

HTTP/1.1 200 OK
Content-Type: text/html; charset=utf-8
Date: Sun, 08 Nov 2026 05:27:15 GMT
Server: Kestrel
Cache-Control: no-store
Transfer-Encoding: chunked
Content-Security-Policy: script-src 'nonce-xSbIMOG5VW0fbCx1EGDiCg==' 'strict-dynamic' https: 'unsafe-inline'; style-src 'self' 'nonce-xSbIMOG5VW0fbCx1EGDiCg=='; object-src 'none'; base-uri 'self'; frame-ancestors 'none'
nonce="M7ZmjQFZH6nNFUb+C61A/g=="
nonce="8N0gcjJJq9r7vSjpP3ZubQ=="

Her yanıt farklı nonce taşıyor. ngCspNonce'ın ikinci işi runtime'da: Angular component style'larını <style> element'i olarak ekliyor ve nonce'u bu attribute'tan okuyor. style-src'de 'unsafe-inline' olmadığı için bunu test ettim. Önce doğru kurulum, sonra ngcspnonce attribute'unu template'ten sildiğim hâl:

[eval] {"styles":["nonce=set body{font-family:sans-serif}","nonce=set h1[_ngcontent-ng-c582792440]{color:#639}"],"h1Color":"rgb(102, 51, 153)","sheets":3}
[console.error] Applying inline style violates the following Content Security Policy directive 'style-src 'self' 'nonce-aNQGzNFuW+FjvhGHLPQKRA==''. Either the 'unsafe-inline' keyword, a hash ('sha256-R8Tr0etxaurKdTOuzau1pawvaKOt9AHTbwflNz/d5tI='), or a nonce ('nonce-...') is required to enable inline execution. The action h
[console.log] [csp-violation] directive=style-src-elem blocked=inline disposition=enforce sample=
[eval] {"h1Color":"rgb(0, 0, 0)"}

Attribute yokken component style'ı engellendi ve başlık mor yerine siyah kaldı. CSP hatası uygulamayı çökertmez, sadece görünümü sessizce bozar; bu yüzden testlerin konsolu okuması şart.

index.html'i cache'leyip nonce'u runtime'da başka bir yerden almak istiyorsan Angular'ın CSP_NONCE injection token'ı var. Lab'da nonce'u bir meta etiketinden okuyan bir provider denedim; edge'in yapacağı işi, yani script'lere ve meta'ya nonce yazmayı, bu denemede elle yaptım:

{
  provide: CSP_NONCE,
  useFactory: () => document.querySelector('meta[name="csp-nonce"]')?.getAttribute('content') ?? null,
},
[eval] {"styles":["nonce=set body{font-family:sans-serif}","nonce=set h1[_ngcontent-ng-c582792440]{color:#639}"],"h1Color":"rgb(102, 51, 153)"}

angular.dev, inline critical CSS kullanıyorsan CSP_NONCE yerine autoCsp ya da ngCspNonce'ı öneriyor; CLI'ın ürettiği inline etiketlere nonce'u build sırasında yalnız ngCspNonce yazabiliyor.

Kurulum 3 ve Seçim

SSR'da HTML zaten her istekte sunucuda üretildiği için nonce doğal seçim ve autoCsp orada kullanılamıyor. SSR'a özgü nonce akışını serinin 07. yazısında ele alacağız.

Servis biçimiScript'lerComponent style'larıNot
Statik hosting, CDNautoCsp hash'istyle-src 'unsafe-inline' ya da edge'de nonceframe-ancestors header'da
ASP.NET CorengCspNonce ile istek başına nonceAynı nonceCache-Control: no-store
Angular SSRİstek başına nonceAynı nonceautoCsp kullanılamaz

Üçüncü Parti Script'lerle Uzlaşma

Analytics ya da tag manager'ın kendi script'ini eklemesi strict CSP'de en sık sorulan soru. 'strict-dynamic' burada işini yapıyor: nonce'lu bir loader'ın eklediği script'e ve onun eklediği script'e güven yayılıyor. Lab'da üçüncü parti origin'i http://127.0.0.1:19424 olarak taklit ettim ve src/index.html'e bir loader koydum:

<script>
  // tag manager loader'ı
  const tm = document.createElement('script');
  tm.src = 'http://127.0.0.1:19424/analytics.js';
  document.head.append(tm);
</script>

CLI bu inline script'e de nonce'u yazdı. analytics.js kendi içinde bir pixel.js ekliyor. Bir de CMS'den (Content Management System) gelen ve sunucunun HTML'e nonce'suz eklediği bir widget etiketi koydum: <script src="http://127.0.0.1:19424/widget.js"></script>. Sonuç:

[console.error] Loading the script 'http://127.0.0.1:19424/widget.js' violates the following Content Security Policy directive: "script-src 'nonce-zfQT/6DTkhpvifWgzNP1KQ==' 'strict-dynamic' https: 'unsafe-inline'". Note that 'script-src-elem' was not explicitl
[requestfailed] http://127.0.0.1:19424/widget.js csp
[console.log] [csp-violation] directive=script-src-elem blocked=http://127.0.0.1:19424/widget.js disposition=enforce sample=
[console.log] analytics.js loaded
[console.log] pixel.js loaded (created by analytics.js)

Loader zinciri çalıştı, HTML'e doğrudan yazılan etiket engellendi. Bu aynı zamanda 'strict-dynamic''in bedelini gösteriyor: güvendiğin bir script'in yüklediği her şeye de güveniyorsun. Tag manager'a nonce verdiğin anda, onun panelinden script ekleyebilen herkes production'a kod koyabilir. Bu riski ve iframe ile izolasyonu serinin 06. yazısında konuşacağız; DOM (Document Object Model) tarafında script.src'ye yazılan URL'i (Uniform Resource Locator) sınırlamanın yolu ise bir sonraki yazının konusu olan Trusted Types.

Report-Only ile Kademeli Geçiş

Mevcut bir uygulamaya strict policy'yi doğrudan enforce etmek, gözden kaçan bir inline script'i production'da kırmak demek. Önce Content-Security-Policy-Report-Only ile gönder. Nonce'suz plain build'e şu header'ı ekledim:

Content-Security-Policy-Report-Only: script-src 'nonce-r4nd0m' 'strict-dynamic'; object-src 'none'; base-uri 'self'; report-uri /csp-report

Report-Only hiçbir şeyi engellemez: zafiyetli sayfada XSS fired satırları yine düştü, ama tarayıcı her ihlali /csp-report'a gönderdi. Gelen raporlardan ikisi:

[report] application/csp-report {"csp-report":{"document-uri":"http://localhost:19423/sinks","referrer":"","violated-directive":"script-src-elem","effective-directive":"script-src-elem","original-policy":"script-src 'nonce-r4nd0m' 'strict-dynamic'; object-src 'none'; base-uri 'self'; report-uri /csp-report","disposition":"report","blocked-uri":"http://localhost:19423/main-ZMTY22ZN.js","status-code":200,"script-sample":""}}
[report] application/csp-report {"csp-report":{"document-uri":"http://localhost:19423/sinks","referrer":"","violated-directive":"script-src-attr","effective-directive":"script-src-attr","original-policy":"script-src 'nonce-r4nd0m' 'strict-dynamic'; object-src 'none'; base-uri 'self'; report-uri /csp-report","disposition":"report","blocked-uri":"inline","line-number":5,"column-number":91475,"source-file":"http://localhost:19423/main-ZMTY22ZN.js","status-code":200,"script-sample":""}}

İlk rapor, policy'yi bugün enforce etseydin uygulamanın kendisinin (main.js) yüklenmeyeceğini söylüyor: nonce'u build'e bağlamadan geçiş yapılmaz. İkincisi gerçek bir bulgu: main.js içinden bir inline event handler çalıştırılmaya çalışıldı. Toplam yedi rapor geldi: main.js, critical CSS loader'ı ve beş inline handler raporu.

Raporlama için yeni mekanizma Reporting-Endpoints header'ı ile report-to direktifi. Chrome, report-to varken report-uri'yi yok sayıyor ve Reporting API raporları toplu ve gecikmeli gönderiyor. Lab'da ikisini birlikte verdiğimde 75 saniye bekledim ve endpoint'e tek rapor gelmedi; browser-compat-data'ya göre Firefox report-to'yu ancak 149'da destekledi. Geçiş döneminde report-uri'yi tek başına kullanmak hâlâ en öngörülebilir yol. Raporları toplayan endpoint'i ASP.NET Core'da yazmak "ASP.NET Core Güvenliği" serisinin 04. yazısında.

Pratik geçiş planı:

  • Report-Only policy'yi production'a al, en az bir tam iş döngüsü (ay sonu kampanyası, fatura dönemi) boyunca raporları topla.
  • Raporları effective-directive, blocked-uri ve source-file'a göre grupla; tarayıcı eklentilerinden gelen gürültüyü ayıkla.
  • Her grubu kod değişikliğiyle kapat: inline handler'ı listener'a, javascript: link'ini click handler'a çevir, üçüncü parti etiketi nonce'lu loader'a taşı.
  • Aynı policy'yi enforce et, Report-Only'yi bir sonraki sıkılaştırma için yanında bırak.

Üretimde Dikkat

CDN'in nonce'lu HTML'i cache'lemesi strict CSP'nin en sinsi tuzağı: aynı nonce bütün kullanıcılara gider ve saldırgan onu sayfadan okuyup enjekte ettiği script'e yazabilir. Nonce'lu yanıtta Cache-Control: no-store gönder ve CDN kurallarında HTML'i cache dışında bırak. angular.dev, CDN kullanıyorsan nonce'u origin sunucuda değil edge'de üretmeyi öneriyor. Edge'de nonce üretemiyorsan autoCsp'nin hash'i doğru seçim, çünkü hash statik içerikle birlikte değişir.

Nonce, kriptografik rastgele kaynaktan gelmeli ve en az 128 bit olmalı. Guid.NewGuid(), istek ID'si ya da zaman damgası nonce değildir. Policy'yi tek bir yerde üret; birden fazla katmanın (reverse proxy, uygulama, CDN) ayrı CSP header'ları eklemesi, tarayıcının hepsini birlikte uygulaması demektir ve en sıkı olan kazanır. Header'ın ve meta etiketinin birlikte kullanıldığı autoCsp kurulumunda bunu bilerek yaptık.

Policy değişikliğini kod gibi yönet: headless tarayıcıda kritik sayfaları açıp securitypolicyviolation event'lerini sayan bir testi CI (Continuous Integration) pipeline'ına koy. Bu yazıdaki bütün çıktılar böyle bir script'ten geliyor.

Sık Düşülen Tuzaklar

Birincisi, 'unsafe-inline''ı geçici diye ekleyip kalıcı bırakmak. Nonce ya da hash yoksa 'unsafe-inline' inline script'in ve handler'ın tamamına izin verir; strict CSP'nin bütün faydası gider. Report-Only bu geçiş için var.

İkincisi, base-uri 'none''ı Angular'a kopyalamak. Lab'da iç içe route'larda uygulama açılmadı; Angular'da 'self' kullan.

Üçüncüsü, frame-ancestors'ı meta etiketine yazmak. Tarayıcı onu orada yok sayar; autoCsp kullanırken bile bu direktif hosting'in header'ında olmalı.

Dördüncüsü, ngCspNonce'ı yalnız script'ler için sanmak. Attribute, Angular'ın runtime'da eklediği component style'larının da nonce'u; o olmadan style-src ihlali sessizce görünümü bozar.

Beşincisi, nonce'lu HTML'i cache'lemek. Statik bir nonce, nonce değildir.

Küçük Bir Deneme

★☆☆ Lab'daki ASP.NET Core host'unda Cache-Control: no-store satırını kaldır, aynı sayfayı iki kez curl -i ile iste ve nonce'ların hâlâ farklı olduğunu doğrula. Sonra bir reverse proxy cache'i önüne koyduğunda ne olacağını düşün.

★★☆ autoCsp build'ine optimization.styles.inlineCritical: false ekle, üretilen index.html'i ve hash listesini karşılaştır. Critical CSS loader'ı kalktığında policy'de kaç hash kaldı?

★★★ Report-Only policy'ye 'report-sample' ekle ve zafiyetli sayfayı tekrar aç. Raporlardaki script-sample alanının neyi gösterdiğine bak; bu alanın loglara kişisel veri taşıyıp taşımayacağını değerlendir.

CSP, hangi script'in çalışacağını sınırlıyor ama innerHTML'e yazılan string'i ya da script.src'ye verilen URL'i sorgulamıyor. Bir sonraki yazıda bu boşluğu Trusted Types ile kapatıyoruz: Angular'ın policy adları, üçüncü parti library ihlalleri, default policy'nin tehlikesi ve Baseline 2026 ile değişen tarayıcı tablosu.