security: add safeImageUrl, un-break M-5's over-blocking of image-context SVG data URIs
M-5 made safeUrl() block data:image/svg+xml everywhere, including the image-only sinks (<img src>, CSS url()) that Gallery's default images and other SVG placeholders rely on. Loaded as an image, an SVG is rasterized and never executes an inline <script>/onload= -- that only happens when it's navigated to or loaded as an <iframe> document -- so M-5 over-blocked the safe contexts and broke every published Gallery (and other components using an SVG placeholder) using safeUrl's default images in prod. Adds safeImageUrl(): identical javascript:/vbscript: handling to safeUrl, but treats data: as an allowlist of image/* subtypes instead of a blocklist -- allows all data:image/* (including svg+xml, with or without base64), still blocks data:text/html and any other non-image data: type. Swapped to safeImageUrl at IMAGE-src / CSS-image url() sinks only: - Gallery.tsx img src + lightbox data-lb-src - ImageBlock.tsx img src (toHtml) - Logo.tsx / Navbar.tsx logo <img> src (their href/link targets keep safeUrl) - style-helpers.ts sanitizeCssValue's url(...) handling (background-image for HeroSimple/BackgroundSection/Section/CallToAction) Left on safeUrl (href/iframe/form-action/navigation sinks, where data:image/svg+xml must stay blocked): ButtonLink, Icon link, SocialLinks, Menu/Navbar link hrefs, PricingTable buttonHref, _cta-helpers, ContentSlider buttonHref, FeaturesGrid buttonUrl, FormContainer action (via form-relay-wiring), MapEmbed/VideoBlock iframe src. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -94,6 +94,32 @@ describe('Gallery.toHtml deterministic + unique scope ids (thread node id, no Ma
|
||||
});
|
||||
});
|
||||
|
||||
describe('Gallery.toHtml default SVG placeholder images survive export (Bug 2 regression)', () => {
|
||||
test('a default data:image/svg+xml image emits a non-empty img src, not src=""', () => {
|
||||
const { html } = toHtml({}, ''); // no images prop -> component default SVG placeholders
|
||||
expect(html).not.toContain('src=""');
|
||||
expect(html).toMatch(/src="data:image\/svg\+xml[^"]*"/);
|
||||
});
|
||||
|
||||
test('an explicit data:image/svg+xml gallery image src is preserved (not stripped to empty)', () => {
|
||||
const svg = 'data:image/svg+xml,%3Csvg%2F%3E';
|
||||
const { html } = toHtml({ images: [{ src: svg, alt: 'a' }] }, '');
|
||||
expect(html).toContain(`src="${svg}"`);
|
||||
});
|
||||
|
||||
test('lightbox data-lb-src also preserves data:image/svg+xml (still an image context)', () => {
|
||||
const svg = 'data:image/svg+xml,%3Csvg%2F%3E';
|
||||
const { html } = toHtml({ images: [{ src: svg, alt: 'a' }], lightbox: true }, '');
|
||||
expect(html).toContain(`data-lb-src="${svg}"`);
|
||||
});
|
||||
|
||||
test('a javascript: gallery image src still yields an empty src (safeImageUrl still blocks it)', () => {
|
||||
const { html } = toHtml({ images: [{ src: 'javascript:alert(1)', alt: 'a' }] }, '');
|
||||
expect(html).toContain('src=""');
|
||||
expect(html).not.toContain('javascript:');
|
||||
});
|
||||
});
|
||||
|
||||
describe('Gallery.toHtml lightbox focus management (M-2)', () => {
|
||||
const props = { images: [{ src: '/a.jpg', alt: 'a' }], lightbox: true };
|
||||
|
||||
|
||||
Reference in New Issue
Block a user