fix(builder): sanitize HtmlBlock/Countdown/Gallery JS contexts
Entity-escaping alone doesn't protect JS-string or raw-HTML sinks:
- HtmlBlock.toHtml exported props.code raw; now runs it through the
same purifyHtml (DOMPurify) config already used for the live editor
preview, so <script>/on*= payloads can't survive export either.
- Countdown.toHtml interpolated targetDate directly into
`new Date("${targetDate}")` inside an inline <script> -- a value
like `2026-01-01");alert(1)//` broke out of the string literal. Now
validated against a strict date/datetime shape and JSON.stringify'd
before embedding, falling back to `new Date()` for anything invalid.
- Gallery.toHtml's lightbox used
`onclick="${id}_open('${esc(img.src)}')"`, which a single quote in
img.src could break out of. Replaced with a `data-lb-src` attribute
per thumbnail and one delegated click listener on the grid
(`e.target.closest('[data-lb-src]')`) instead of a per-item inline
handler string.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,28 @@
|
||||
import { describe, test, expect } from 'vitest';
|
||||
import { Countdown } from './Countdown';
|
||||
|
||||
const toHtml = (Countdown as any).toHtml;
|
||||
|
||||
describe('Countdown.toHtml validates targetDate before inline-script injection (A4.2)', () => {
|
||||
test('malicious targetDate cannot break out of the new Date(...) call', () => {
|
||||
const { html } = toHtml({ targetDate: '2026-01-01");alert(1)//' }, '');
|
||||
expect(html).not.toContain('alert(');
|
||||
expect(html).not.toContain('");');
|
||||
});
|
||||
|
||||
test('valid date is JSON-encoded into the script', () => {
|
||||
const { html } = toHtml({ targetDate: '2026-01-01' }, '');
|
||||
expect(html).toContain('new Date("2026-01-01")');
|
||||
});
|
||||
|
||||
test('valid date+time is preserved', () => {
|
||||
const { html } = toHtml({ targetDate: '2026-01-01T12:30:00' }, '');
|
||||
expect(html).toContain('new Date("2026-01-01T12:30:00")');
|
||||
});
|
||||
|
||||
test('invalid/empty targetDate falls back safely (no injected literal)', () => {
|
||||
const { html } = toHtml({ targetDate: 'not-a-date' }, '');
|
||||
expect(html).not.toContain('not-a-date');
|
||||
expect(html).toMatch(/new Date\(\)\.getTime\(\)|new Date\(Date\.now\(\)\)\.getTime\(\)/);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user