# Sanitizer attribute allowlist bypass

Attributes kept by a sanitizer that downstream code re-parses into HTML or handlers

_Area: Web injection and code execution. Seen in 3 disclosed reports; 2 confirmed bounties: $337 and $3,000; latest 2026-07-14. Updated 2026-10-23._

## How it works
A sanitizer decides which attributes survive, assuming the ones it keeps are inert. That assumption breaks when a downstream consumer reads an attribute value and rebuilds HTML or a handler from it without re-checking. The gap is between sanitize time and re-parse time: the value was safe as an attribute but is treated as markup or a handler name later.

## Where it shows up
- Frontend code that interpolates an attribute value into an HTML string, e.g. gl-emoji building an img tag from an unescaped `name` attribute, bypassing v-safe-html [E1](https://bugbountylibrary.com/r/h1%3A1398305.md)
- Markdown filters that string-interpolate node attributes (`data-sourcepos`, `lang`) into generated HTML [E1](https://bugbountylibrary.com/r/h1%3A1398305.md)
- Sanitizer hooks that allowlist a whole prefix (`/^data-trix-/`) instead of named attributes [E2](https://bugbountylibrary.com/r/h1%3A3581911.md)
- Serialize/deserialize round-trips that parse JSON from a data attribute and `setAttribute` each entry [E2](https://bugbountylibrary.com/r/h1%3A3581911.md)
- Denylists that omit one attribute the framework still consumes, e.g. rails-ujs `data-disable-with` rendered as raw HTML on click [E3](https://bugbountylibrary.com/r/h1%3A1579645.md)

## How to review
Enumerate every attribute the sanitizer keeps, then trace each downstream consumer that parses it. The invariant: no attribute value surviving sanitization may be re-interpreted as HTML, a handler name, or a URL base without passing the sanitizer again. For prefix allowlists, confirm the consumer validates attribute names against an explicit safe set. For denylists, diff against the framework's full attribute-reading surface. For string-built HTML, confirm every interpolated attribute is escaped for its quoting context. For data-* JSON, confirm parsed keys are checked against a safe set before `setAttribute`, not applied blindly.

## Variants seen
- Frontend custom element interpolating an unescaped `name` attribute into an img tag [E1](https://bugbountylibrary.com/r/h1%3A1398305.md)
- Markdown filter string-interpolating node attributes into HTML [E1](https://bugbountylibrary.com/r/h1%3A1398305.md)
- Prefix allowlist plus serializer re-applying JSON via `setAttribute`, keeping on* handlers [E2](https://bugbountylibrary.com/r/h1%3A3581911.md)
- Denylist missing a framework attribute rendered as HTML on click [E3](https://bugbountylibrary.com/r/h1%3A1579645.md)

## Impact and severity
Stored XSS against users viewing shared content: issue comments and snippets [E1](https://bugbountylibrary.com/r/h1%3A1398305.md), editor content [E2](https://bugbountylibrary.com/r/h1%3A3581911.md), CI job lists [E3](https://bugbountylibrary.com/r/h1%3A1579645.md). Severity rises when the payload persists in content seen by many users, and when the surviving attribute yields a handler or markup that fires without further interaction [E2](https://bugbountylibrary.com/r/h1%3A3581911.md).

## Evidence
- E1: https://bugbountylibrary.com/r/h1%3A1398305.md
- E2: https://bugbountylibrary.com/r/h1%3A3581911.md
- E3: https://bugbountylibrary.com/r/h1%3A1579645.md
