diff --git a/src/__tests__/core/sanitize.test.ts b/src/__tests__/core/sanitize.test.ts index 2d9e1091f..b013fa759 100644 --- a/src/__tests__/core/sanitize.test.ts +++ b/src/__tests__/core/sanitize.test.ts @@ -15,7 +15,7 @@ */ import { describe, it, expect } from 'bun:test' -import { sanitizeRichtext, isRichtextPropKey, PLAIN_TEXT_CONFIG } from '@core/sanitize' +import { sanitizeRichtext, sanitizePostBody, isRichtextPropKey, PLAIN_TEXT_CONFIG } from '@core/sanitize' // --------------------------------------------------------------------------- // XSS prevention — the core contract @@ -230,6 +230,131 @@ describe('sanitizeRichtext() in server runtime', () => { }) }) +// --------------------------------------------------------------------------- +// sanitizePostBody() — 2026-08-13 blog round-2 fix. Post/page body content +// (base.outlet's markdown-rendered html) needs a wider allowlist than +// sanitizeRichtext(), including iframe embeds scoped to a trusted-host +// allowlist. See @core/sanitize POST_BODY_CONFIG. +// --------------------------------------------------------------------------- + +describe('sanitizePostBody() — trusted-host iframe embeds', () => { + it('keeps an iframe embed from youtube.com', () => { + const result = sanitizePostBody( + '', + ) + expect(result).toContain(' { + const result = sanitizePostBody( + '', + ) + expect(result).toContain(' { + const result = sanitizePostBody('') + expect(result).toContain(' { + const result = sanitizePostBody('') + expect(result).not.toContain(' { + const result = sanitizePostBody('') + expect(result).not.toContain(' { + // e.g. "youtube.com.evil.com" or "evilyoutube.com" must NOT pass a naive + // substring check — isTrustedIframeHost requires exact match or a + // genuine `.` + trusted-host suffix. + const lookalikes = [ + 'https://youtube.com.evil.com/embed/x', + 'https://evilyoutube.com/embed/x', + 'https://notyoutube.com/embed/x', + ] + for (const src of lookalikes) { + const result = sanitizePostBody(``) + expect(result).not.toContain(' { + expect(sanitizePostBody('')).not.toContain('')).not.toContain(' tags and event-handler attributes (iframe allowlisting is not a blanket HTML-open)', () => { + const result = sanitizePostBody('

hi

') + expect(result).not.toContain('onclick') + expect(result).not.toContain(' { + const result = sanitizePostBody( + 'a
x
', + ) + expect(result).toContain(' with a child — the form real editors/importers emit', () => { + // A bare `