From 621bb21d521387ebc2420507e8ab7aeb9cb405dc Mon Sep 17 00:00:00 2001 From: Josh Knapp Date: Sun, 12 Jul 2026 19:53:02 -0700 Subject: [PATCH] fix(builder): safeImageUrl for FeaturesGrid/ContentSlider image sinks + tighten data:image allowlist FeaturesGrid's and ContentSlider's CSS background-image url() were still on safeUrl, which blocks data:image/svg+xml -- inconsistent with other image sinks already swapped to safeImageUrl and a latent regression for those two components. Swapped both to safeImageUrl; left their navigation sinks (buttonUrl/buttonHref) on safeUrl. Also tightened safeImageUrl's data:image allowlist check to require the slash (dataimage/ not dataimage), so a bogus MIME like data:imagehtml/... can no longer slip past the prefix check. --- .../sections/ContentSlider.toHtml.test.ts | 9 ++++++++ .../src/components/sections/ContentSlider.tsx | 4 ++-- .../sections/FeaturesGrid.toHtml.test.ts | 23 +++++++++++++++++++ .../src/components/sections/FeaturesGrid.tsx | 4 ++-- craft/src/utils/escape.test.ts | 9 ++++++++ craft/src/utils/escape.ts | 2 +- 6 files changed, 46 insertions(+), 5 deletions(-) create mode 100644 craft/src/components/sections/FeaturesGrid.toHtml.test.ts diff --git a/craft/src/components/sections/ContentSlider.toHtml.test.ts b/craft/src/components/sections/ContentSlider.toHtml.test.ts index f1b1a15..d4b147d 100644 --- a/craft/src/components/sections/ContentSlider.toHtml.test.ts +++ b/craft/src/components/sections/ContentSlider.toHtml.test.ts @@ -109,6 +109,15 @@ describe('ContentSlider.toHtml renders slide.imageSrc as a background-image (INT expect(html).not.toContain('background-image:url('); expect(html).toContain('background-color:#123456'); }); + + test('a slide with a data:image/svg+xml imageSrc exports a non-empty background-image url (safeImageUrl, not safeUrl)', () => { + const svgDataUri = 'data:image/svg+xml,%3Csvg%2F%3E'; + const slidesWithSvg = [ + { type: 'image' as const, imageSrc: svgDataUri, heading: 'One' }, + ]; + const { html } = toHtml({ slides: slidesWithSvg }, ''); + expect(html).toContain(`background-image:url('${svgDataUri}')`); + }); }); describe('ContentSlider.toHtml interval is NOT runtime-type-checked -- must be coerced before it reaches the inline