|
| 1 | +import { assert, test, testGroup } from "test/test_helper" |
| 2 | + |
| 3 | +import { delay } from "../test_helpers/timing_helpers" |
| 4 | + |
| 5 | +// getEditorElement is installed as a global test helper (see trix/core/helpers/global), |
| 6 | +// mirroring how the other system tests reach the live editor. |
| 7 | + |
| 8 | +// Exercises the real re-inflation entry point rather than the sanitizer in isolation: |
| 9 | +// editor.loadHTML re-parses stored HTML under DOMPurify's mXSS-safe mode |
| 10 | +// (SAFE_FOR_XML: true) and renders it into the live editor, including any attachment |
| 11 | +// content re-parsed by AttachmentView. These assertions target the security invariant — |
| 12 | +// no executable handler and no <script> reach the live DOM, and nothing executes. |
| 13 | +// Handler stripping is a browser-independent DOMPurify guarantee, so unlike element |
| 14 | +// shape the assertions are parser-agnostic. |
| 15 | +testGroup("Re-inflation security (editor.loadHTML)", { template: "editor_empty" }, () => { |
| 16 | + const loadAndAssertInert = async (html) => { |
| 17 | + window.reinflationXSS = 0 |
| 18 | + getEditorElement().editor.loadHTML(html) |
| 19 | + await delay(20) |
| 20 | + |
| 21 | + const element = getEditorElement() |
| 22 | + assert.equal( |
| 23 | + element.querySelectorAll("[onerror], [onload], [onclick]").length, 0, |
| 24 | + `live event handler survived re-inflation: ${element.innerHTML}` |
| 25 | + ) |
| 26 | + assert.notOk(element.querySelector("script"), `script survived re-inflation: ${element.innerHTML}`) |
| 27 | + assert.equal(window.reinflationXSS, 0, "re-inflated payload executed") |
| 28 | + |
| 29 | + delete window.reinflationXSS |
| 30 | + } |
| 31 | + |
| 32 | + test("neutralizes a mutation-XSS payload loaded through the editor", async () => { |
| 33 | + await loadAndAssertInert( |
| 34 | + "<noscript><p title=\"</noscript><img src=x onerror=window.reinflationXSS=(window.reinflationXSS||0)+1>\">" |
| 35 | + ) |
| 36 | + }) |
| 37 | + |
| 38 | + test("sanitizes attacker-controlled attachment content on re-inflation", async () => { |
| 39 | + const attachment = { |
| 40 | + contentType: "text/html5", |
| 41 | + content: "</style><img src=x onerror=window.reinflationXSS=(window.reinflationXSS||0)+1>HELLO", |
| 42 | + } |
| 43 | + const html = `<div data-trix-attachment='${JSON.stringify(attachment).replace(/'/g, "'")}'></div>` |
| 44 | + await loadAndAssertInert(html) |
| 45 | + }) |
| 46 | +}) |
0 commit comments