Files
site-builder/craft/src/components/sections/Countdown.toHtml.test.ts
T
shadowdao 7179287087 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>
2026-07-12 12:06:07 -07:00

29 lines
1.1 KiB
TypeScript

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\(\)/);
});
});