// The Journal HTML sanitizer mirrors the Journal plugin's bluemonday policy // (classes/format_html.go). The unsafe cases are the six of // TestFormatHTMLRejectsUnsafeHTML plus every bypass from the Phase 16.1 code // review (CR-01), as rendered HTML. Each one must come out without the // dangerous part while the safe text around it stays. import { test } from 'node:test' import assert from 'node:assert/strict' import { gateJournalHtml, isSafeJournalHtml, sanitizeJournalHtml } from '../app/utils/htmlGate.ts' // Signs that something executable or navigating survived. const DANGER = [ /'], ['iframe', '

keep

'], ['event', '

keep

'], ['javascript', 'keep'], ['vbscript', 'keep'], ['data', 'keep'], ] // CR-01: payloads the three-regex gate let through, and their neighbours. const REVIEW_BYPASSES: Array<[string, string]> = [ ['attribute without a space', '

keep

'], ['svg with slash separator', '
\n\n

keep

'], ['numeric entity colon', 'keep'], ['named entity colon', 'keep'], ['entity in the scheme', 'keep'], ['tab in the scheme', 'keep'], ['newline in the scheme', 'keep'], ['leading control characters', 'keep'], ['upper case scheme', 'keep'], ['MathML xlink:href', '
\nkeep\n
'], ['base', '

keep

'], ['meta refresh', '

keep

'], ['form phish', '

keep

'], ['embed', '

keep

'], ['object', '

keep

'], ['data image src', '

keep

'], ['style element', '

keep

'], ['css url in style attribute', '

keep

'], ['css expression in colour', '

keep

'], ['noscript title breakout', '

keep

'], ['handler in upper case', '

keep

'], ] for (const [name, html] of [...GO_CASES, ...REVIEW_BYPASSES]) { test(`${name} is neutralized and the rest is kept`, () => { const out = assertNeutralized(html, name) assert.match(out, /keep/, `${name} lost the safe text: ${out}`) }) } test('the Go cases in upper case are neutralized', () => { for (const [name, html] of GO_CASES) assertNeutralized(html.toUpperCase(), `${name} upper`) }) test('comments are dropped; a comment is not content, so the input still counts as safe', () => { const html = '

keep

' assert.equal(sanitizeJournalHtml(html), '

keep

') assert.deepEqual(gateJournalHtml(html), { safe: true, html: '

keep

' }) }) test('unsafe parts are removed, not the whole value', () => { assert.equal(sanitizeJournalHtml('

Before

After

'), '

Before

After

') assert.equal(sanitizeJournalHtml('

tekst

'), '

tekst

') assert.equal(sanitizeJournalHtml('link'), 'link') }) test('text that names a scheme is ordinary text (WR-01)', () => { const texts = [ '

Data: 12 maja 2024

', '

Data publikacji: 3 czerwca 2025

', '

The metadata: title, author and date.

', '

Big data: what it means. JavaScript: a language. vbscript: legacy.

', ] for (const html of texts) { assert.equal(sanitizeJournalHtml(html), html) assert.equal(isSafeJournalHtml(html), true, html) assert.deepEqual(gateJournalHtml(html), { safe: true, html }) } }) // The author footer of an imported post (post 64), without its \n
` const out = sanitizeJournalHtml(html) assert.equal(isSafeJournalHtml(html), false) assert.doesNotMatch(out, /`) }) test('inline styles keep only the four allowed properties with valid values', () => { assert.equal( sanitizeJournalHtml('

x

'), '

x

', ) assert.equal(sanitizeJournalHtml('

x

'), '

x

') assert.equal(sanitizeJournalHtml('

x

'), '

x

') assert.equal(sanitizeJournalHtml('

x

'), '

x

') }) test('goldmark markup passes whole: headings, tables, footnotes, links and images', () => { const safe = [ '

Hello

', '
ab
12
', '

See footnote.1

', '

  1. Note. ↩︎

', '

Artykuły las

', '

mail cdn page

', '

Quote

', '

one of the onions

', ] for (const html of safe) { assert.equal(sanitizeJournalHtml(html), html) assert.deepEqual(gateJournalHtml(html), { safe: true, html }) } }) test('pasted chat transcripts keep dir="auto" and the two data attributes only', () => { const html = '
Hi
' assert.equal(sanitizeJournalHtml(html), html) assert.equal( sanitizeJournalHtml('
Hi
'), '
Hi
', ) }) test('attribute values must match their patterns', () => { assert.equal(sanitizeJournalHtml('

x

'), '

x

') assert.equal(sanitizeJournalHtml('

x

'), '

x

') assert.equal(sanitizeJournalHtml('x'), 'x') assert.equal(sanitizeJournalHtml('x'), 'x') assert.equal(sanitizeJournalHtml('x'), 'x') assert.equal(sanitizeJournalHtml('x'), 'x') }) test('attribute and tag names from Object.prototype find no rule', () => { assert.equal( sanitizeJournalHtml('xt'), 'xt', ) }) test('sanitizing is idempotent, so server and client agree on a sanitized body', () => { const inputs = [AUTHOR_FOOTER, ...REVIEW_BYPASSES.map(([, html]) => html), ...GO_CASES.map(([, html]) => html)] for (const html of inputs) { const once = sanitizeJournalHtml(html) assert.equal(sanitizeJournalHtml(once), once, html) } }) test('empty input is safe and empty', () => { for (const html of [null, undefined, '']) { assert.equal(sanitizeJournalHtml(html), '') assert.equal(isSafeJournalHtml(html), true) assert.deepEqual(gateJournalHtml(html), { safe: true, html: '' }) } })