# Angular SSR: XSS via Unescaped Processing Instruction (<?...?>) Nodes in Fallback Raw-Content Elements

Angular SSR serialized processing-instruction data without escaping ancestor fallback closing tags, letting `</noscript` in PI data break out of `<noscript>` and execute injected HTML/JS.

- Payout: Research
- Severity: high
- Program: Angular
- Found by: VenkatKwest
- Disclosed: 2026-09-28
- CVE: CVE-2026-88058
- Write-up quality: 7/10
- Original: https://github.com/advisories/GHSA-j3r3-mxqp-r2p4
- Library page: https://bugbountylibrary.com/r/ghsa%3AGHSA-j3r3-mxqp-r2p4
- Tags: area:web domain:web impact:integrity kind:research platform:ghsa sev:high tech:nodejs technique:parser-differential technique:source-review vuln:xss

**Target:** @angular/platform-server SSR HTML serializer (ProcessingInstruction nodes in fallback raw-content elements)
**Root cause:** PI serialization escaped `>` to `>` but left `<` untouched and never checked ancestor fallback raw-content tag names, so a matching end-tag sequence in PI data terminated the container in RAWTEXT mode.
**Steps:**
1. Create component with template `<noscript id="host"></noscript>`.
2. In ngAfterViewInit, call `inject(DOCUMENT).createProcessingInstruction('x', '</noscript ')` with attacker-controlled data.
3. Append the PI to the noscript host via `Renderer2.appendChild`, then append a sibling `<img src=x onerror=alert(...)>`.
4. SSR emits `<noscript><?x </noscript ?><img src="x" onerror=...></noscript>`; browser closes noscript at the unescaped end tag and executes the img onerror.
**How found:** Noticed PI serialization only escaped `>` (bogus-comment breakout) but not `<`, then reasoned about HTML5 RAWTEXT tokenizer behavior in fallback raw-content elements where only a matching end tag terminates the container.
**Exploitation:** Requires app/library code passing untrusted input into createProcessingInstruction (or Renderer2 DOM insertion) inside a fallback raw-content element during SSR; the emitted unescaped `</noscript` closes the container so sibling markup renders as live HTML and runs attacker JS in victims' browsers.
**Insight:** When auditing HTML serializers, check each node type against every tokenizer state its ancestor can impose (RAWTEXT/RCDATA/script-data), not just the default data state; end-tag-name matching is the real escape boundary, not `>` or `?>`.

