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:
2026-07-12 12:06:07 -07:00
co-authored by Claude Opus 4.8
parent 48d0441be3
commit 7179287087
6 changed files with 106 additions and 9 deletions
+9 -1
View File
@@ -284,6 +284,14 @@ Countdown.craft = {
// Generate a unique ID for this countdown instance
const uid = 'cd_' + Math.random().toString(36).slice(2, 8);
// Only accept a strict date/datetime shape before it's embedded in the
// inline <script>; anything else falls back to "now" instead of letting
// arbitrary text (e.g. `");alert(1)//`) break out of the new Date(...) call.
const VALID_DATE_RE = /^\d{4}-\d{2}-\d{2}([T ][0-9:.\-+Z]*)?$/;
const dateExpr = typeof targetDate === 'string' && VALID_DATE_RE.test(targetDate)
? `new Date(${JSON.stringify(targetDate)})`
: 'new Date()';
return {
html: `<section${idAttr}${sectionStyle ? ` style="${sectionStyle}"` : ''}>
${headingHtml}
@@ -295,7 +303,7 @@ Countdown.craft = {
</div>
<script>
(function(){
var target = new Date("${targetDate}").getTime();
var target = ${dateExpr}.getTime();
function pad(n){ return String(n).padStart(2,'0'); }
function update(){
var diff = target - Date.now();