# Imported HTML bypasses sanitization cache

Derived *_html cache fields mass-assignable from import JSON, and unescaped template rendering of imported content

_Area: Web injection and code execution. Seen in 2 disclosed reports; 2 confirmed bounties: $4,500 and $6,267; latest 2020-04-30. Updated 2026-11-01._

## How it works
A field that is normally *derived* (rendered HTML, a cached markdown version, a template body) is treated as trusted input on an import or cache path. The sanitizer or cache-invalidation guard only runs when the source field changes through normal editing, so content arriving via import, upload, or a client-side template fetch is stored and later rendered unescaped. The broken assumption: "anything in the *_html column was produced by our sanitizer."

## Where it shows up
- Import/export (project.json, archive restore) that mass-assigns model attributes including `*_html`, `cached_markdown_version`, or template bodies [E2](https://bugbountylibrary.com/r/h1%3A508184.md)
- Cache-invalidation code that compares `changed_attributes` against `author`/`project` while the actual keys are `author_id`/`project_id` [E2](https://bugbountylibrary.com/r/h1%3A508184.md)
- Invalidation logic that *removes* the source field from the dirty set when the derived field is present [E2](https://bugbountylibrary.com/r/h1%3A508184.md)
- Version checks satisfied by a constant (e.g. a fixed `latest_cached_markdown_version`) [E2](https://bugbountylibrary.com/r/h1%3A508184.md)
- Client-side templates fetched via XHR from `location.hash` and rendered unescaped [E1](https://bugbountylibrary.com/r/blog%3Ablog-doyensec-com-2020-04-30-polymorphic-images-for-xss-html.md)
- User uploads served same-origin, especially after re-encoding that preserves attacker bytes [E1](https://bugbountylibrary.com/r/blog%3Ablog-doyensec-com-2020-04-30-polymorphic-images-for-xss-html.md)
- User-facing CSS/Less/Sass editors compiled with `javascriptEnabled` or `@plugin` support [E3](https://bugbountylibrary.com/r/h1%3A858874.md)

## How to review
Trace every path that writes a derived/HTML field and confirm the invariant: **a rendered-HTML field is only ever written by the sanitizer, and its cache version is recomputed from the current source content, never accepted from input.**
- Enumerate mass-assignable attributes on import; flag any `*_html`, `*_cache*`, or version field.
- Read the invalidation guard: do the `changed_attributes` keys match the real attribute names? Does any branch delete the source field from the dirty set?
- Check whether the version comparison can be satisfied by a constant or attacker-supplied value.
- For templates: confirm escaping happens at render, not at fetch.
- For CSS preprocessors: confirm JS evaluation and remote plugin loading are disabled.

## How finders spotted it
- Read `cache_markdown_field.rb`, noticed `changed_attributes` held `author_id`/`project_id` not `author`/`project`, and that `note` was dropped from invalidations when `note_html` changed; then saw the version constant made the second check trivially pass [E2](https://bugbountylibrary.com/r/h1%3A508184.md)
- Noticed the hash+XHR templating rendered unescaped and images were same-origin; when doubted, built a differential harness across image libraries to find bytes surviving re-encoding [E1](https://bugbountylibrary.com/r/blog%3Ablog-doyensec-com-2020-04-30-polymorphic-images-for-xss-html.md)
- Noticed the Markdown panel accepted Less, then tested backtick JS and `@plugin` imports in the CSS editor [E3](https://bugbountylibrary.com/r/h1%3A858874.md)

## Variants seen
- Import path setting `note_html` + `cached_markdown_version` to skip cache invalidation [E2](https://bugbountylibrary.com/r/h1%3A508184.md)
- Polymorphic image surviving re-encode, rendered via unescaped hash template [E1](https://bugbountylibrary.com/r/blog%3Ablog-doyensec-com-2020-04-30-polymorphic-images-for-xss-html.md)
- Less compiler with `javascriptEnabled` executing inline JS in a visualization editor [E3](https://bugbountylibrary.com/r/h1%3A858874.md)

## Impact and severity
Stored XSS against other users: MR discussions [E2](https://bugbountylibrary.com/r/h1%3A508184.md), Scholar pages [E1](https://bugbountylibrary.com/r/blog%3Ablog-doyensec-com-2020-04-30-polymorphic-images-for-xss-html.md), Kibana dashboard viewers [E3](https://bugbountylibrary.com/r/h1%3A858874.md). Impact rose with reach (shared dashboards, baited users [E3](https://bugbountylibrary.com/r/h1%3A858874.md)) and with a second endpoint reusing the same technique, paid separately [E1](https://bugbountylibrary.com/r/blog%3Ablog-doyensec-com-2020-04-30-polymorphic-images-for-xss-html.md). Median bounty ~$5.4k.

## Evidence
- E1: https://bugbountylibrary.com/r/blog%3Ablog-doyensec-com-2020-04-30-polymorphic-images-for-xss-html.md
- E2: https://bugbountylibrary.com/r/h1%3A508184.md
- E3: https://bugbountylibrary.com/r/h1%3A858874.md
