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

Angular'da DOM XSS Sink Envanteri: ElementRef, Renderer2, Dinamik URL'ler, HTML Üreten Kütüphaneler ve Code Review Listesi

Mert Özen 8 Kas 2026 13 dk 3 görüntülenme
Angular'da DOM XSS Sink Envanteri: ElementRef, Renderer2, Dinamik URL'ler, HTML Üreten Kütüphaneler ve Code Review Listesi

Angular yalnız kendi template binding'lerini sanitize eder; ElementRef.nativeElement, Renderer2.setProperty/setAttribute, insertAdjacentHTML, dinamik script ve iframe src'leri ile HTML üreten library'ler korumasızdır. Bu yazıda bu sink'leri headless Chrome'da gösteriyor, template ve DomSanitizer ile düzeltiyor, marked, DOMPurify ve setHTML'i karşılaştırıyor ve grep ile ESLint tabanlı bir code review kontrol listesi kuruyoruz.

Sürüm notu: Bu yazıdaki örnekler Angular 22.2, Node.js 24, Chrome 156, marked 18.1, DOMPurify 3.4 ve ESLint 10.14 ile denenmiştir.

Önce Tahmin Et

Bookstore'un kitap sayfasında okur yorumları gösteriliyor. Yorumlar API'den (Application Programming Interface) JSON (JavaScript Object Notation) olarak geliyor ve aralarında Mallory'nin şu metni var: Great book <img src=x onerror="...">. Aynı değer bir component'te dört farklı yoldan DOM'a (Document Object Model) yazılıyor: template'te [innerHTML] binding'i, ElementRef.nativeElement.innerHTML ataması, insertAdjacentHTML çağrısı ve Renderer2.setProperty(el, 'innerHTML', ...). Angular "varsayılan olarak güvenli" bir framework. Bu dört yoldan kaçında onerror çalışır?

Cevap üç. Angular yalnız kendi template binding'lerini sanitize eder; geri kalan üç yol tarayıcının ham DOM API'lerine gider ve Angular o yazmayı hiç görmez. Bu yazıda bu yolların envanterini çıkarıyoruz: Angular'ın korumadığı sink'ler, HTML (HyperText Markup Language) üreten bağımlılıklar, sunucuda template üretme riski ve ekipte her code review'da kullanılabilecek grep ve ESLint tabanlı bir kontrol listesi. XSS'in (Cross-Site Scripting) üç türü, innerHTML tuzağı ve bypassSecurityTrust* method'larının temeli Web Güvenliği (OWASP Top 10) serisinin "OWASP A05: Injection (2)" yazısında anlatıldı; burada onları tekrar etmiyor, Angular koduna özel kör noktalara bakıyoruz. OWASP, Open Worldwide Application Security Project'in kısaltması.

Source'tan Sink'e Düşünmek

DOM XSS'i anlamanın en kısa yolu iki soruyu birlikte sormak: değer nereden geliyor (source) ve nereye yazılıyor (sink)? Source; API yanıtı, location.hash, query string, postMessage ile gelen veri ya da localStorage olabilir. Sink ise değeri HTML ya da script olarak yorumlayan her API'dir: innerHTML, outerHTML, insertAdjacentHTML, document.write, script.src, iframe.srcdoc, eval, setTimeout'a verilen string. OWASP'ın DOM based XSS Prevention Cheat Sheet'i de aynı ayrımla başlar: güvenilmeyen veri bir sink'e ulaşmadan önce o sink'in context'ine uygun biçimde işlenmelidir.

Angular'da bu soruya verilecek cevap çoğu zaman "template" olur. {{ }} interpolation'ı text node üretir, [innerHTML], [href], [src] gibi property binding'leri Angular'ın sanitizer'ından geçer. Sorun, kodun template'in dışına çıktığı anlarda başlar. Lab'daki component tam olarak bunu yapıyor:

[
  { "author": "Ada", "text": "Clear examples, <b>short</b> chapters.", "website": "https://example.com/ada" },
  { "author": "Mallory", "text": "Great book <img src=x onerror=\"console.log('XSS fired in #' + this.parentElement.id)\">", "website": "javascript:console.log('XSS fired via href')" }
]

Payload zararsız: çalışırsa yalnız hangi container'da çalıştığını konsola yazıyor. Component:

@Component({
  selector: 'app-review-sinks',
  template: `
    @for (review of reviews(); track review.author) {
      <article>
        <p>{{ review.text }}</p>
        <p [id]="'binding-' + review.author" [innerHTML]="review.text"></p>
        <a [id]="'href-' + review.author" [href]="review.website">Website</a>
      </article>
    }
    <div id="native" #native></div>
    <div id="adjacent" #adjacent></div>
    <div id="renderer" #rendered></div>
    <a id="renderer-link" #link>Author site</a>
  `,
})
export class ReviewSinks {
  private readonly http = inject(HttpClient);
  private readonly renderer = inject(Renderer2);
  protected readonly reviews = toSignal(this.http.get<Review[]>('/api/reviews.json'), { initialValue: [] });

  private readonly native = viewChild.required<ElementRef<HTMLElement>>('native');
  private readonly adjacent = viewChild.required<ElementRef<HTMLElement>>('adjacent');
  private readonly rendered = viewChild.required<ElementRef<HTMLElement>>('rendered');
  private readonly link = viewChild.required<ElementRef<HTMLAnchorElement>>('link');

  constructor() {
    effect(() => {
      const mallory = this.reviews().find(r => r.author === 'Mallory');
      if (!mallory) return;
      // GÜVENSİZ: Angular bu yazmaları görmez, hiçbir şey sanitize edilmez
      this.native().nativeElement.innerHTML = mallory.text;
      this.adjacent().nativeElement.insertAdjacentHTML('beforeend', mallory.text);
      this.renderer.setProperty(this.rendered().nativeElement, 'innerHTML', mallory.text);
      this.renderer.setAttribute(this.link().nativeElement, 'href', mallory.website);
    });
  }
}

Sayfayı headless Chrome'da açıp konsolu okumak için Playwright'ın playwright-core paketini sistemdeki Chrome ile kullanan küçük bir script yazdım. İşin özü iki listener: konsol mesajlarını ve CSP (Content Security Policy) ihlali event'lerini yazdırmak.

const browser = await chromium.launch({ executablePath: process.env.CHROME, headless: true });
const page = await browser.newPage();
page.on('console', m => console.log(`[console.${m.type()}] ${m.text()}`));
page.on('pageerror', e => console.log(`[pageerror] ${e.message}`));
await page.addInitScript(() => {
  document.addEventListener('securitypolicyviolation', e => {
    console.log(`[csp-violation] directive=${e.violatedDirective} blocked=${e.blockedURI} disposition=${e.disposition} sample=${e.sample}`);
  });
});

Development build'e karşı çıktı:

[console.log] Angular is running in development mode.
[console.warning] WARNING: sanitizing HTML stripped some content, see https://angular.dev/best-practices/security#preventing-cross-site-scripting-xss
[console.warning] WARNING: sanitizing unsafe URL value javascript:console.log('XSS fired via href') (see https://angular.dev/best-practices/security#preventing-cross-site-scripting-xss)
[console.log] XSS fired in #renderer
[console.log] XSS fired in #native
[console.log] XSS fired in #adjacent

Template'teki iki binding Angular'ın sanitizer'ından geçti: [innerHTML] sonrası DOM'da Great book <img src="x"> kaldı, [href] değeri unsafe:javascript:... oldu. Diğer üç yol olduğu gibi çalıştı. Renderer2.setAttribute ile yazılan link de korumasız: href attribute'u javascript:console.log('XSS fired via href') olarak kaldı ve linke tıkladığımda konsola XSS fired via href düştü.

Angular'ın Korumadığı Yollar

Angular'ın sanitization'ı compiler'ın template'te gördüğü binding'lere bağlı. Compiler [innerHTML] binding'ini görünce değeri HTML context'inde, [href]'i URL (Uniform Resource Locator) context'inde işleyen bir çağrı üretir. ElementRef.nativeElement ise sana gerçek DOM element'ini verir; onun üzerinde yaptığın her şey tarayıcının kendi API'si. angular.dev'deki güvenlik rehberi bunu açıkça yazıyor: Trusted Types zorunlu değilse tarayıcının yerleşik DOM API'leri seni güvenlik açıklarına karşı kendiliğinden korumaz.

Renderer2 çoğu ekipte "Angular'ın güvenli yolu" diye bilinir; bu bir yanılgı. Renderer2 platformdan bağımsız DOM işlemleri için bir soyutlama; setProperty ve setAttribute değeri doğrudan element'e yazar, sanitizer çağırmaz. Lab'da setProperty(el, 'innerHTML', ...) satırı tıpkı nativeElement.innerHTML gibi çalıştı.

Envanterdeki diğer yollar:

  • document.write ve document.writeln: Angular uygulamasında meşru kullanımı neredeyse yok; görürsen sor.
  • Dinamik script: document.createElement('script') ile oluşturulan element'in src'sine kullanıcı verisi yazmak, saldırgana kendi script'ini yükletmek demek.
  • Dinamik iframe: iframe.src ve özellikle iframe.srcdoc, ikincisi doğrudan HTML alır.
  • eval, new Function, setTimeout('...'): string'i kod olarak çalıştıran her şey.
  • Kullanıcı URL'i taşıyan attribute'lar: href, src, action, formaction; javascript: şeması burada çalışır.

Angular'ın template tarafında da bir sınır var: resource URL context'i. <iframe [src]> ya da <script [src]> gibi kod yükleyen bir binding'e düz string verirsen Angular sanitize etmeye çalışmaz, hata fırlatır. Lab'da previewUrl = 'https://example.com/preview/42' değerini <iframe [src]>'ye bağladığımda:

[console.error] ERROR RuntimeError: NG0904: unsafe value used in a resource URL context (see https://angular.dev/best-practices/security#preventing-cross-site-scripting-xss). Find more at https://v22.angular.dev/errors/NG0904
    at ɵɵsanitizeResourceUrl (http://localhost:19420/main.js:8289:9)

Bu davranış doğru ama bir yan etkisi var: geliştirici hatayı susturmak için ilk aklına gelen bypassSecurityTrustResourceUrl ile değeri sarmak oluyor. Resource URL kullanıcıdan geliyorsa bu, korumayı bilerek kapatmak demek. Doğru yol, URL'i sabit bir allowlist'ten seçmek ve bypass'ı yalnız o sabit değer için, kodun tek bir yerinde yapmak.

Düzeltilmiş Hâli: Önce Template, Sonra Sanitize

İlk kural basit: değeri DOM'a template üzerinden yaz. Metin gerekiyorsa interpolation, HTML gerekiyorsa [innerHTML] binding'i, URL gerekiyorsa [href]. Direct DOM erişimi gerçekten kaçınılmazsa (üçüncü parti bir widget'ın container'ı, canvas, ölçüm) angular.dev'in önerisi değeri DomSanitizer.sanitize ile uygun SecurityContext için kendin sanitize etmek. Lab'daki düzeltilmiş effect:

effect(() => {
  const mallory = this.reviews().find(r => r.author === 'Mallory');
  if (!mallory) return;
  // GÜVENLİ: HTML gerekiyor, o yüzden Angular'ın sanitizer'ını açıkça çağır
  // eslint-disable-next-line no-unsanitized/property -- incelendi: DomSanitizer çıktısı
  this.native().nativeElement.innerHTML = this.sanitizer.sanitize(SecurityContext.HTML, mallory.text) ?? '';
  // GÜVENLİ: düz metin HTML parser'a hiç ulaşmaz
  this.renderer.setProperty(this.rendered().nativeElement, 'textContent', mallory.text);
  // GÜVENLİ: yalnız https: ve mailto: URL'leri yazılır
  const url = safeUrl(mallory.website);
  // eslint-disable-next-line no-restricted-syntax -- incelendi: safeUrl allowlist'i
  if (url) this.renderer.setAttribute(this.link().nativeElement, 'href', url);
});

URL kontrolü bir allowlist; javascript: gibi şemaları tek tek yasaklamak yerine izin verdiklerini sayıyor:

const allowedProtocols = new Set(['https:', 'mailto:']);

export function safeUrl(value: string): string | null {
  try {
    const url = new URL(value, document.baseURI);
    return allowedProtocols.has(url.protocol) ? url.href : null;
  } catch {
    return null;
  }
}

Aynı payload ile sonuç:

[console.warning] WARNING: sanitizing HTML stripped some content, see https://angular.dev/best-practices/security#preventing-cross-site-scripting-xss
[status] 200
[eval] {"native":"Great book <img src=\"x\">","renderer":"Great book &lt;img src=x onerror=\"console.log('XSS fired in #' + this.parentElement.id)\"&gt;","link":null}

onerror ilk container'dan atıldı, ikincisinde payload metin olarak göründü, link'e hiç href yazılmadı. XSS fired satırı yok. Bir not: sanitize bir string döndürüyor. Trusted Types açık bir sayfada bu atama yine ihlal sayılır; bunu serinin 04. yazısında göreceğiz.

HTML Üreten Bağımlılıklar

Kendi kodunu temizlemek işin yarısı. Markdown parser'ları, rich text editörleri ve tooltip ya da label formatter'ı HTML string döndüren chart library'leri HTML üretir ve bu HTML'i çoğu zaman kendileri DOM'a yazar. Angular bu yazmayı görmez. Değerlendirirken üç soru sor: library çıktısını sanitize ediyor mu, etmiyorsa dokümanında bunu söylüyor mu ve HTML'i DOM'a kendisi mi yazıyor yoksa sana mı veriyor?

marked bunun iyi bir örneği: çıktıyı sanitize etmiyor ve dokümanı bunu açıkça söylüyor. Lab'da Markdown'ı önce çıplak marked'dan, sonra DOMPurify ile geçirdim:

@Component({
  selector: 'app-review-markdown',
  template: `<div id="markdown" [innerHTML]="html()"></div>`,
})
export class ReviewMarkdown {
  readonly source = input.required<string>();

  // marked sanitize etmez: çıktısı güvenilmeyen HTML'dir
  protected readonly html = computed(() =>
    DOMPurify.sanitize(marked.parse(this.source(), { async: false })));
}

Kaynak: **Great** book <img src=x onerror="..."> [site](javascript:alert(1)). Çıktı:

[eval] {"md":"<p><strong>Great</strong> book <img src=\"x\"> <a>site</a></p>\n","raw":"<p><strong>Great</strong> book <img src=x onerror=\"console.log('XSS fired in markdown')\"> <a href=\"javascript:alert(1)\">site</a></p>\n"}

raw, marked'ın ham çıktısı: onerror da javascript: link'i de duruyor. DOMPurify ikisini de attı. Burada iki katman var: DOMPurify, ardından [innerHTML] binding'i üzerinden Angular'ın kendi sanitizer'ı. Library çıktısını nativeElement.innerHTML'e yazsaydın tek katman DOMPurify kalırdı.

Tarayıcının kendi sanitizer'ı da artık var. HTML Sanitizer API'nin Element.setHTML() method'u, MDN'in (MDN Web Docs) browser-compat-data paketinin 8.1.8 sürümüne göre Chrome ve Edge 146 ile Firefox 148'de destekleniyor; Safari'de yok ve özellik Baseline değil. Chrome 156'da aynı Markdown çıktısını setHTML ile yazdım:

[eval] {"hasSetHTML":true,"out":"<p><strong>Great</strong> book  <a>site</a></p>"}

Dikkat çeken ayrıntı: <img> tamamen gitti. Chrome 156'nın varsayılan sanitizer configuration'ı img element'ine hiç izin vermiyor (new Sanitizer().get() listesinde yok); kapak görseli gösteren bir yorum alanı için configuration'ı kendin vermen gerekir. Kasım 2026 itibarıyla setHTML'i destek kontrolüyle kullanıp desteklemeyen tarayıcıda DOMPurify'a düşmek mantıklı. TypeScript 6.0'ın DOM tiplerinde setHTMLUnsafe var ama setHTML yok; kullanacaksan küçük bir tip tanımı eklemen gerekir. setHTMLUnsafe ise adının söylediği gibi: sanitizer vermezsen her şeyi yazar.

Template Injection: Template'i Sunucuda Üretme

Angular'ın template'i kod demek: compiler onu JavaScript'e çevirir. Bu yüzden angular.dev iki kuralı kesin yazıyor: Angular template'lerini sunucu tarafında bir templating dili ile üretme ve template'i kullanıcı girdisini template syntax'ı ile birleştirerek oluşturma. Kullanıcı verisiyle JIT (Just-in-Time) compilation yapmayı da açıkça anti-pattern olarak niteliyor. Örnek senaryo: ASP.NET Core tarafında Razor ile index.html içine <app-banner title="@Model.Title"> gibi bir parça basıp kullanıcının yazdığı başlıkta {{ }} bırakmak. Angular o başlığı template olarak yorumlarsa veri koda dönüşür. Varsayılan AOT (Ahead-of-Time) compilation ve build anında üretilen template'lerle bu sorun ortadan kalkar; veriyi template'e değil, binding'e ver.

SSR'da (Server-Side Rendering) aynı ilke geçerli: render edilen HTML Angular'ın kendi escape kurallarından geçer, ama sunucu kodu HTML'i string birleştirerek üretiyorsa ya da kullanıcı verisini TransferState'e koyup başka bir yerde HTML olarak yazıyorsa sink yine açılır. SSR'a özgü riskleri serinin 07. yazısında ayrıca ele alacağız.

Code Review İçin Sink Kontrol Listesi

Envanteri kalıcı hâle getirmenin yolu, onu her pull request'te otomatik çalışan bir kontrole çevirmek. İki katman kullanıyorum. Birincisi kaba ama hızlı bir grep; amacı "bu satıra bir insan baksın" demek:

#!/bin/sh
# Angular projesindeki DOM XSS sink adaylarını code review için listeler.
grep -rnE \
  -e '\.(innerHTML|outerHTML)[[:space:]]*=' \
  -e 'insertAdjacentHTML|document\.write|setHTMLUnsafe' \
  -e "setProperty\([^,]+,[[:space:]]*'(innerHTML|outerHTML)'" \
  -e 'bypassSecurityTrust[A-Za-z]+' \
  -e "createElement\('(script|iframe)'\)" \
  -e '\[(innerHTML|outerHTML|srcdoc)\]=' \
  -e 'new Function\(|[^.]eval\(' \
  --include='*.ts' --include='*.html' "${1:-src}"

Lab projesindeki çıktısı:

src/app/reviews/review-markdown.ts:7:  template: `<div id="markdown" [innerHTML]="html()"></div>`,
src/app/reviews/review-safe.ts:31:      this.native().nativeElement.innerHTML = this.sanitizer.sanitize(SecurityContext.HTML, mallory.text) ?? '';
src/app/reviews/review-sinks.ts:12:        <p [id]="'binding-' + review.author" [innerHTML]="review.text"></p>
src/app/reviews/review-sinks.ts:37:      this.native().nativeElement.innerHTML = mallory.text;
src/app/reviews/review-sinks.ts:38:      this.adjacent().nativeElement.insertAdjacentHTML('beforeend', mallory.text);
src/app/reviews/review-sinks.ts:39:      this.renderer.setProperty(this.rendered().nativeElement, 'innerHTML', mallory.text);

Grep, güvenli ve güvensiz satırı ayırt edemez; [innerHTML] binding'leri de listeye girer çünkü onlar da "HTML yazıyoruz" demek ve kaynağını bilmek istersin. İkinci katman ESLint: Mozilla'nın eslint-plugin-no-unsanitized paketi innerHTML ataması ve insertAdjacentHTML gibi çağrıları yakalar, no-restricted-syntax ile de Angular'a özel kalıpları AST (Abstract Syntax Tree) selector'larıyla tanımlarız:

import { defineConfig } from 'eslint/config';
import tseslint from 'typescript-eslint';
import noUnsanitized from 'eslint-plugin-no-unsanitized';

export default defineConfig({
  files: ['src/**/*.ts'],
  ignores: ['src/**/*.spec.ts'],
  languageOptions: { parser: tseslint.parser },
  plugins: { 'no-unsanitized': noUnsanitized },
  rules: {
    'no-unsanitized/property': 'error',
    'no-unsanitized/method': 'error',
    'no-restricted-syntax': ['error',
      {
        selector: 'CallExpression[callee.property.name=/^bypassSecurityTrust/]',
        message: 'bypassSecurityTrust* disables Angular sanitization; needs security review.',
      },
      {
        selector: "CallExpression[callee.property.name='setProperty'][arguments.1.value=/^(innerHTML|outerHTML)$/]",
        message: 'Renderer2.setProperty with innerHTML/outerHTML is not sanitized.',
      },
      {
        selector: "CallExpression[callee.property.name='setAttribute'][arguments.1.value=/^(href|src|srcdoc|action|formaction)$/]",
        message: 'URL attribute written outside Angular bindings; validate the scheme.',
      },
    ],
  },
});

npx eslint src çıktısı:

src/app/reviews/review-sinks.ts
  37:7  error  Unsafe assignment to innerHTML                                                  no-unsanitized/property
  38:7  error  Unsafe call to this.adjacent().nativeElement.insertAdjacentHTML for argument 1  no-unsanitized/method
  39:7  error  Renderer2.setProperty with innerHTML/outerHTML is not sanitized                 no-restricted-syntax
  40:7  error  URL attribute written outside Angular bindings; validate the scheme             no-restricted-syntax

✖ 4 problems (4 errors, 0 warnings)

Güvensiz component'teki dört satırın dördü de yakalandı. Düzeltilmiş component listede yok, çünkü oradaki iki satırı inceledikten sonra gerekçeli bir eslint-disable-next-line yorumuyla işaretledim. İlk koşuda o iki satır da hata vermişti; bu iyi bir şey: her istisna, kodun içinde gerekçesiyle görünür kalıyor ve review'da sorgulanabiliyor.

Kontrol listesinin kendisi:

SinkAngular korur mu?Ne yapmalı
{{ }} interpolationEvet, text nodeVarsayılan yol
[innerHTML], [href], [src] bindingEvet, sanitizerKaynağını bil, bypass'tan kaçın
nativeElement.innerHTML, insertAdjacentHTMLHayırTemplate'e taşı ya da DomSanitizer.sanitize
Renderer2.setProperty / setAttributeHayırtextContent, URL allowlist
document.write, eval, new FunctionHayırKaldır
script.src, iframe.src, srcdocHayırSabit allowlist
bypassSecurityTrust*Korumayı kapatırYalnız sabit değerler, security review
HTML üreten libraryHayırDOMPurify ya da setHTML, sonra binding

Üretimde Dikkat

ESLint kuralını yalnız yeni kodda değil, CI (Continuous Integration) pipeline'ında hata olarak çalıştır; uyarı seviyesinde bırakılan güvenlik kuralları birkaç sprint sonra kimsenin okumadığı yüzlerce satıra dönüşür. Mevcut büyük bir projede ilk koşu muhtemelen onlarca bulgu çıkaracak. Bunları tek bir pull request'te susturmak yerine dosya dosya eslint-disable gerekçeleri yazarak ilerle; gerekçesi olmayan istisna kalmasın.

Production build'de Angular'ın "sanitizing HTML stripped some content" uyarısı yok, o yalnız development mode'da çıkıyor. Yani sanitizer'ın sessizce bir şeyi kırptığını production'da göremezsin. Kullanıcıların beklediği HTML'in kırpıldığını fark etmek için bu uyarıları development ortamında ve uçtan uca testlerde takip et.

Bağımlılıklar değişir. Bugün çıktısını sanitize eden bir editör, yeni bir major sürümde bunu configuration'a bırakabilir. HTML üreten her paketi bir liste hâlinde tut ve sürüm yükseltirken changelog'unda sanitize, XSS ve HTML kelimelerini ara. Bir sonraki adım, bu hataların sonucunu tarayıcıda sınırlamak: strict CSP ve Trusted Types. İkisi, gözden kaçan bir sink'i production'da yakalayan ikinci savunma hattı.

Sık Düşülen Tuzaklar

Birincisi, Renderer2'yi güvenli sanmak. Renderer2, sunucu ve tarayıcı platformları arasında taşınabilirlik için var; sanitization katmanı değil. Lab'da setProperty(el, 'innerHTML', ...) ve setAttribute(a, 'href', ...) satırları çıplak DOM API'leri kadar açıktı.

İkincisi, NG0904 hatasını bypass ile susturmak. Hata, Angular'ın kod yükleyen bir context'te düz string'e güvenmediğini söylüyor. bypassSecurityTrustResourceUrl'ü kullanıcıdan gelen bir değere uygulamak, uyarıyı değil korumayı kaldırır.

Üçüncüsü, "API'den gelen veri bizim veritabanımızdan geliyor, güvenli" demek. Stored XSS tam olarak budur: dün bir kullanıcının yazdığı yorum bugün başka bir kullanıcının ekranında çalışır.

Dördüncüsü, HTML üreten bir library'nin varsayılanını doğrulamadan kullanmak. marked'ın ham çıktısında onerror ve javascript: link'i aynen duruyordu. Library HTML'i DOM'a kendisi yazıyorsa Angular'ın sanitizer'ı devreye bile girmez.

Beşincisi, grep'i tek kontrol saymak. Grep regex ile çalışır; el['inner' + 'HTML'] gibi bir yazımı ya da bir helper fonksiyonun içine gizlenmiş atamayı kaçırır. Grep insan gözünü yönlendirir, ESLint kuralı ise AST üzerinde çalıştığı için daha zor kandırılır; ikisi de tarayıcıdaki Trusted Types kadar kesin değildir.

Küçük Bir Deneme

★☆☆ Lab component'ine this.native().nativeElement.outerHTML = mallory.text satırını ekle. Grep ve ESLint'in ikisinin de yakaladığını doğrula, sonra headless Chrome'da konsolu oku.

★★☆ Markdown component'inde DOMPurify'ı setHTML ile değiştir: setHTML destekleniyorsa onu kullan, desteklenmiyorsa DOMPurify'a düş. Chrome'da kapak görselinin (<img src="cover.png">) görünmesi için Sanitizer configuration'ına img element'ini ve src, alt attribute'larını ekle ve çıktıyı karşılaştır.

★★★ no-restricted-syntax kurallarına document.createElement('script') sonrası .src ataması yapan kodu yakalayan bir selector ekle. Kuralı lab'a eklediğin küçük bir loader fonksiyonuyla test et ve false positive üretip üretmediğine bak.

Envanter hazır; sıradaki soru, gözden kaçan bir sink'in sonucunu tarayıcıda nasıl sınırlayacağımız. Bir sonraki yazıda Angular'a strict CSP uyguluyoruz: nonce ve 'strict-dynamic', autoCsp ile hash tabanlı policy, ASP.NET Core'dan nonce'lu servis ve Report-Only ile kırmadan geçiş.