# Denylist bypass via parser normalization mismatch

Filter checks raw input; the consumer normalizes it (case, prefixes, aliases, quotes) into a dangerous form

_Area: Web injection and code execution. Seen in 4 disclosed reports; latest 2026-09-17. Updated 2026-10-25._

## How it works
A denylist compares user input against a fixed set of forbidden names or patterns, but the component that actually consumes the input (an HTML tokenizer, a third-party tool, a parser) normalizes it differently. The filter checks the raw string; the consumer resolves a different, equivalent form. Any input the consumer treats as dangerous but the filter does not recognize slips through. The broken assumption is that "the string I see equals the thing that gets executed." The same mismatch appears when a sanitizer guards one dangerous form but not an equivalent one the consumer also honors.

## Where it shows up
- Regexes emulating an HTML tokenizer, especially quote handling in attribute values [E1](https://bugbountylibrary.com/r/ghsa%3AGHSA-vfmf-q6x9-cw96.md)
- Blocklists of keys/tags passed to a third-party tool that has its own aliasing, group prefixes, or case folding [E2](https://bugbountylibrary.com/r/ghsa%3AGHSA-7v3r-m9c8-r855.md)[E3](https://bugbountylibrary.com/r/ghsa%3AGHSA-62p3-hvxx-fxg4.md)[E4](https://bugbountylibrary.com/r/ghsa%3AGHSA-qmwh-9m9c-h36m.md)
- Key-validation regexes that permit separators (colons) the downstream tool interprets as namespaces [E2](https://bugbountylibrary.com/r/ghsa%3AGHSA-7v3r-m9c8-r855.md)[E3](https://bugbountylibrary.com/r/ghsa%3AGHSA-62p3-hvxx-fxg4.md)
- Fixes using host-language comparison (`delete()`, `strings.EqualFold`) against a tool documented as case-insensitive [E4](https://bugbountylibrary.com/r/ghsa%3AGHSA-qmwh-9m9c-h36m.md)
- Adjacent variant: sanitizers that check static attributes but miss indirect setters (e.g. SVG `animate` setting `xlink:href`) [E1](https://bugbountylibrary.com/r/ghsa%3AGHSA-vfmf-q6x9-cw96.md)

## How to review
The invariant: the filter must match the input in the same normalized form the consumer resolves it to. Concretely:
- Identify the real consumer and enumerate its normalization rules: case folding, group prefixes, aliases, pseudo-tags, alternate namespaces [E2](https://bugbountylibrary.com/r/ghsa%3AGHSA-7v3r-m9c8-r855.md)[E3](https://bugbountylibrary.com/r/ghsa%3AGHSA-62p3-hvxx-fxg4.md)[E4](https://bugbountylibrary.com/r/ghsa%3AGHSA-qmwh-9m9c-h36m.md)
- Diff the filter's normalization against the consumer's documented parsing rules; confirm the filter operates on the post-normalization form, not raw input
- For HTML filters, diff the regex state machine against the tokenizer spec: when does a quote start a quoted value? [E1](https://bugbountylibrary.com/r/ghsa%3AGHSA-vfmf-q6x9-cw96.md)
- Enumerate the full dangerous set, not just names seen in prior reports (missing `HardLink`/`SymLink`, `FilePermissions`) [E4](https://bugbountylibrary.com/r/ghsa%3AGHSA-qmwh-9m9c-h36m.md)[E2](https://bugbountylibrary.com/r/ghsa%3AGHSA-7v3r-m9c8-r855.md)
- For sanitizers, test indirect attribute setters, not just static ones [E1](https://bugbountylibrary.com/r/ghsa%3AGHSA-vfmf-q6x9-cw96.md)

## Variants seen
- Unpaired quote in an unquoted attribute value hides a following event handler from the regex [E1](https://bugbountylibrary.com/r/ghsa%3AGHSA-vfmf-q6x9-cw96.md)
- Group-prefix syntax (`File:`/`System:`) stripped by the tool before matching, evading bare-name blocklist [E2](https://bugbountylibrary.com/r/ghsa%3AGHSA-7v3r-m9c8-r855.md)[E3](https://bugbountylibrary.com/r/ghsa%3AGHSA-62p3-hvxx-fxg4.md)
- Case-insensitive tool vs case-sensitive host comparison [E4](https://bugbountylibrary.com/r/ghsa%3AGHSA-qmwh-9m9c-h36m.md)
- Missing pseudo-tags/keys entirely absent from the blocklist [E4](https://bugbountylibrary.com/r/ghsa%3AGHSA-qmwh-9m9c-h36m.md)
- Sanitizer misses an indirect attribute setter (SVG animation smuggling a `javascript:` URI) [E1](https://bugbountylibrary.com/r/ghsa%3AGHSA-vfmf-q6x9-cw96.md)

## Why fixes fail
Fixes added quote-awareness for paired quotes but left unpaired quotes unhandled [E1](https://bugbountylibrary.com/r/ghsa%3AGHSA-vfmf-q6x9-cw96.md). A fix used case-sensitive `delete()` against a case-insensitive tool and omitted `HardLink`/`SymLink` [E4](https://bugbountylibrary.com/r/ghsa%3AGHSA-qmwh-9m9c-h36m.md). Each fix closed the reported shape while leaving adjacent syntax variants open.

## Impact and severity
Stored XSS executing in the app origin [E1](https://bugbountylibrary.com/r/ghsa%3AGHSA-vfmf-q6x9-cw96.md); pre-auth arbitrary file rename, move, symlink, chmod in a container [E2](https://bugbountylibrary.com/r/ghsa%3AGHSA-7v3r-m9c8-r855.md)[E3](https://bugbountylibrary.com/r/ghsa%3AGHSA-62p3-hvxx-fxg4.md)[E4](https://bugbountylibrary.com/r/ghsa%3AGHSA-qmwh-9m9c-h36m.md). Severity is high because the bypass needs no special access and the filter gives false assurance. Worse when the consumer is a powerful tool (file operations) or output is stored and served to other users. Note E2/E3/E4 are near-duplicate reports on one codebase (Gotenberg); weigh accordingly.

## Evidence
- E1: https://bugbountylibrary.com/r/ghsa%3AGHSA-vfmf-q6x9-cw96.md
- E2: https://bugbountylibrary.com/r/ghsa%3AGHSA-7v3r-m9c8-r855.md
- E3: https://bugbountylibrary.com/r/ghsa%3AGHSA-62p3-hvxx-fxg4.md
- E4: https://bugbountylibrary.com/r/ghsa%3AGHSA-qmwh-9m9c-h36m.md
