# Unescaped metadata in HTML sinks

Attacker-controlled metadata pasted into SSR, websocket, email or import output without context-aware encoding

_Area: Web injection and code execution. Seen in 3 disclosed reports; 2 confirmed bounties: $150 and $13,337; latest 2026-10-07. Updated 2026-11-03._

## How it works
A value the attacker controls — dataset metadata, a display name, a post's embed fields, a page title — is treated as trusted text and pasted into an HTML-producing sink. The broken assumption is that metadata is "just data" and therefore safe. It is not: the sink (server-rendered HTML, a websocket broadcast, an import renderer) does string interpolation, so any markup or URL inside the value becomes markup or a link in the output. The failure is usually a parallel path: one renderer escapes correctly, another that produces the same output does not [E2](https://bugbountylibrary.com/r/ghsa%3AGHSA-pq96-jpmf-w254.md)[E3](https://bugbountylibrary.com/r/h1%3A2541027.md).

## Where it shows up
- Separate client and SSR implementations of the same renderer; the SSR one builds tags with template literals [E2](https://bugbountylibrary.com/r/ghsa%3AGHSA-pq96-jpmf-w254.md)
- XML/import formats whose metadata is rendered into pages, especially CDATA sections that bypass XML escaping [E1](https://bugbountylibrary.com/r/blog%3As1gnalcha0s-github-io-dspl-2018-03-07-stored-xss-and-ssrf-google-html.md)
- Two write paths: DB persistence sanitizes, websocket/API broadcast does not [E3](https://bugbountylibrary.com/r/h1%3A2541027.md)
- Transactional email templates that insert a partner or user display name into a sentence (non-HTML variant) [E4]
- Name fields that accept sentences, URLs, or punctuation rather than a bounded token [E4]

## How to review
Find every sink that concatenates attacker-influenced metadata into HTML or a socket payload. The invariant: each value must be encoded for its exact context (HTML body, attribute, URL, JS) at the point of output, or set via an auto-escaping DOM API. Diff the SSR/broadcast path against the client or DB path line by line [E2](https://bugbountylibrary.com/r/ghsa%3AGHSA-pq96-jpmf-w254.md)[E3](https://bugbountylibrary.com/r/h1%3A2541027.md). For email, check whether display names are bounded and reject URLs or sentence structure (recommendation, not evidenced) [E4].

## How finders spotted it
- Read a sample import format, saw metadata and remote-source fields consumed without encoding or validation, then tested CDATA-wrapped script and an ftp:// URL to a local port [E1](https://bugbountylibrary.com/r/blog%3As1gnalcha0s-github-io-dspl-2018-03-07-stored-xss-and-ssrf-google-html.md)
- Compared the SSR-only serializer against the client path that used createElement/setAttribute, and noticed the SSR version was a parallel implementation that never got escaping [E2](https://bugbountylibrary.com/r/ghsa%3AGHSA-pq96-jpmf-w254.md)
- Diffed how posts are sanitized on DB save versus how they are broadcast over websockets, then injected arbitrary metadata fields into the create-post request [E3](https://bugbountylibrary.com/r/h1%3A2541027.md)
- Saw an awkward injected sentence inside a legitimate email, which pointed to the partner-name field as the injection point [E4]

## Variants seen
- Stored XSS via XML metadata, with a separate SSRF via remote file source in the same feature [E1](https://bugbountylibrary.com/r/blog%3As1gnalcha0s-github-io-dspl-2018-03-07-stored-xss-and-ssrf-google-html.md)
- SSR-only meta tag injection executing before hydration [E2](https://bugbountylibrary.com/r/ghsa%3AGHSA-pq96-jpmf-w254.md)
- Unsanitized post metadata over websockets causing client crashes and forged embeds [E3](https://bugbountylibrary.com/r/h1%3A2541027.md)
- Phishing link injected through a display name into a trusted email [E4]

## Why fixes fail
Escaping one path while a parallel path (SSR, websocket) stays raw leaves the hole open [E2](https://bugbountylibrary.com/r/ghsa%3AGHSA-pq96-jpmf-w254.md)[E3](https://bugbountylibrary.com/r/h1%3A2541027.md).

## Impact and severity
Ranged from stored XSS on a major origin chainable to account actions, plus SSRF reaching internal hosts [E1](https://bugbountylibrary.com/r/blog%3As1gnalcha0s-github-io-dspl-2018-03-07-stored-xss-and-ssrf-google-html.md), to pre-hydration script execution [E2](https://bugbountylibrary.com/r/ghsa%3AGHSA-pq96-jpmf-w254.md), cross-channel client crashes for all users [E3](https://bugbountylibrary.com/r/h1%3A2541027.md), and credential/MFA theft via trusted-domain phishing [E4]. Impact rose with origin trust, reach across channels, and chaining to account takeover.

## Seen in real software
Open-source packages and projects, and products, where this pattern was found. If the target runs one, compare the version it runs with the fix.
- **quasar (npm)** < 2.22.0, fixed in 2.22.0: Quasar Framework: Stored/Reflected XSS via unescaped SSR meta tag rendering in getHead() (CVE-2026-106102). Report: https://bugbountylibrary.com/r/ghsa%3AGHSA-pq96-jpmf-w254.md. Fix: https://github.com/quasarframework/quasar/commit/11505afe5b5218f2c468f130181815b898fd1e40
- **Mattermost**: Posts sent via websockets aren't sanitized properly (CVE-2024-47003). Report: https://bugbountylibrary.com/r/h1%3A2541027.md

## Evidence
- E1: https://bugbountylibrary.com/r/blog%3As1gnalcha0s-github-io-dspl-2018-03-07-stored-xss-and-ssrf-google-html.md
- E2: https://bugbountylibrary.com/r/ghsa%3AGHSA-pq96-jpmf-w254.md
- E3: https://bugbountylibrary.com/r/h1%3A2541027.md
