import { describe, test, expect } from 'vitest'; import { ButtonLink } from './basic/ButtonLink'; import { Icon } from './basic/Icon'; import { SocialLinks } from './basic/SocialLinks'; import { Logo } from './basic/Logo'; import { Menu } from './basic/Menu'; import { Navbar } from './basic/Navbar'; import { ContentSlider } from './sections/ContentSlider'; import { FeaturesGrid } from './sections/FeaturesGrid'; import { PricingTable } from './sections/PricingTable'; import { ImageBlock } from './media/ImageBlock'; import { VideoBlock } from './media/VideoBlock'; import { Gallery } from './sections/Gallery'; import { BackgroundSection } from './layout/BackgroundSection'; import { HeroSimple } from './sections/HeroSimple'; import { CallToAction } from './sections/CallToAction'; import { MapEmbed } from './media/MapEmbed'; import { FormContainer } from './forms/FormContainer'; const XSS = 'javascript:alert(1)'; const QUOTE_BREAKOUT = 'x" onerror="alert(1)"'; function toHtmlOf(Component: any) { return Component.toHtml as (props: any, childrenHtml: string) => { html: string }; } describe('A3: exported URLs are wrapped in safeUrl + escapeAttr', () => { test('ButtonLink href', () => { const html = toHtmlOf(ButtonLink)({ href: XSS, text: 'Go' }, '').html; expect(html).not.toContain('javascript:'); const html2 = toHtmlOf(ButtonLink)({ href: QUOTE_BREAKOUT, text: 'Go' }, '').html; expect(html2).not.toContain('onerror="alert(1)"'); }); test('Icon href (link), class is escaped but NOT safeUrl-filtered', () => { const html = toHtmlOf(Icon)({ link: XSS, icon: 'fa-star' }, '').html; expect(html).not.toContain('javascript:'); const html2 = toHtmlOf(Icon)({ link: QUOTE_BREAKOUT, icon: 'fa-star' }, '').html; expect(html2).not.toContain('onerror="alert(1)"'); // class attribute is a CSS class, not a URL -- still escaped for attr safety const html3 = toHtmlOf(Icon)({ icon: 'fa-star" onerror="alert(1)' }, '').html; expect(html3).not.toContain('onerror="alert(1)"'); }); test('SocialLinks href', () => { const html = toHtmlOf(SocialLinks)({ links: [{ platform: 'facebook', url: XSS }] }, '').html; expect(html).not.toContain('javascript:'); const html2 = toHtmlOf(SocialLinks)({ links: [{ platform: 'facebook', url: QUOTE_BREAKOUT }] }, '').html; expect(html2).not.toContain('onerror="alert(1)"'); }); test('Logo href + image src', () => { const html = toHtmlOf(Logo)({ href: XSS, type: 'text', text: 'Site' }, '').html; expect(html).not.toContain('javascript:'); const html2 = toHtmlOf(Logo)({ type: 'image', imageSrc: XSS }, '').html; expect(html2).not.toContain('javascript:'); const html3 = toHtmlOf(Logo)({ type: 'image', imageSrc: QUOTE_BREAKOUT }, '').html; expect(html3).not.toContain('onerror="alert(1)"'); }); test('Menu link href', () => { const html = toHtmlOf(Menu)({ links: [{ text: 'x', href: XSS }] }, '').html; expect(html).not.toContain('javascript:'); const html2 = toHtmlOf(Menu)({ links: [{ text: 'x', href: QUOTE_BREAKOUT }] }, '').html; expect(html2).not.toContain('onerror="alert(1)"'); }); test('Navbar logo href, logo image src, link hrefs', () => { const html = toHtmlOf(Navbar)({ logoUrl: XSS, logoType: 'text', logoText: 'Site', links: [{ text: 'x', href: XSS }] }, '').html; expect(html).not.toContain('javascript:'); const html2 = toHtmlOf(Navbar)({ logoType: 'image', logoImage: XSS }, '').html; expect(html2).not.toContain('javascript:'); const html3 = toHtmlOf(Navbar)({ logoUrl: QUOTE_BREAKOUT, links: [{ text: 'x', href: QUOTE_BREAKOUT }] }, '').html; expect(html3).not.toContain('onerror="alert(1)"'); }); test('ContentSlider button href + slide image src (background url)', () => { const html = toHtmlOf(ContentSlider)({ slides: [{ buttonText: 'Go', buttonHref: XSS, imageSrc: '' }] }, '').html; expect(html).not.toContain('javascript:'); const html2 = toHtmlOf(ContentSlider)({ slides: [{ imageSrc: XSS }] }, '').html; expect(html2).not.toContain('javascript:'); const html3 = toHtmlOf(ContentSlider)({ slides: [{ buttonText: 'Go', buttonHref: QUOTE_BREAKOUT }] }, '').html; expect(html3).not.toContain('onerror="alert(1)"'); }); test('FeaturesGrid image src + button url', () => { const html = toHtmlOf(FeaturesGrid)({ features: [{ title: 't', description: 'd', image: XSS }] }, '').html; expect(html).not.toContain('javascript:'); const html2 = toHtmlOf(FeaturesGrid)({ features: [{ title: 't', description: 'd', buttonText: 'Go', buttonUrl: XSS }] }, '').html; expect(html2).not.toContain('javascript:'); }); test('PricingTable button href', () => { const html = toHtmlOf(PricingTable)({ plans: [{ name: 'p', price: '$1', period: '/mo', features: [], buttonText: 'Buy', buttonHref: XSS }] }, '').html; expect(html).not.toContain('javascript:'); const html2 = toHtmlOf(PricingTable)({ plans: [{ name: 'p', price: '$1', period: '/mo', features: [], buttonText: 'Buy', buttonHref: QUOTE_BREAKOUT }] }, '').html; expect(html2).not.toContain('onerror="alert(1)"'); }); test('ImageBlock img src', () => { const html = toHtmlOf(ImageBlock)({ src: XSS }, '').html; expect(html).not.toContain('javascript:'); const html2 = toHtmlOf(ImageBlock)({ src: QUOTE_BREAKOUT }, '').html; expect(html2).not.toContain('onerror="alert(1)"'); }); test('VideoBlock direct-file src is safeUrl-filtered on final emitted src', () => { // matches the .mp4 extension sniff in detectVideoType but carries a javascript: scheme const html = toHtmlOf(VideoBlock)({ videoUrl: 'javascript:alert(1)//x.mp4' }, '').html; expect(html).not.toContain('javascript:'); const html2 = toHtmlOf(VideoBlock)({ videoUrl: '">.mp4' }, '').html; expect(html2).not.toContain(''); }); test('VideoBlock youtube embed still works after safeUrl pass', () => { const html = toHtmlOf(VideoBlock)({ videoUrl: 'https://www.youtube.com/watch?v=abc123' }, '').html; expect(html).toContain('https://www.youtube.com/embed/abc123'); }); test('Gallery img src', () => { const html = toHtmlOf(Gallery)({ images: [{ src: XSS, alt: 'a' }] }, '').html; expect(html).not.toContain('javascript:'); const html2 = toHtmlOf(Gallery)({ images: [{ src: QUOTE_BREAKOUT, alt: 'a' }] }, '').html; expect(html2).not.toContain('onerror="alert(1)"'); }); test('BackgroundSection bg image url()', () => { const html = toHtmlOf(BackgroundSection)({ bgImage: XSS }, '').html; expect(html).not.toContain('javascript:'); }); test('HeroSimple bg image url() and bg video src', () => { const html = toHtmlOf(HeroSimple)({ bgType: 'image', bgImage: XSS }, '').html; expect(html).not.toContain('javascript:'); const html2 = toHtmlOf(HeroSimple)({ bgType: 'video', bgVideo: XSS }, '').html; expect(html2).not.toContain('javascript:'); const html3 = toHtmlOf(HeroSimple)({ bgType: 'video', bgVideo: QUOTE_BREAKOUT }, '').html; expect(html3).not.toContain('onerror="alert(1)"'); }); test('CallToAction bg image url()', () => { const html = toHtmlOf(CallToAction)({ bgType: 'image', bgValue: XSS }, '').html; expect(html).not.toContain('javascript:'); }); test('MapEmbed iframe src (found beyond the brief-listed sites via grep sweep)', () => { const html = toHtmlOf(MapEmbed)({ address: 'New York, NY', zoom: 14 }, '').html; expect(html).toContain('maps.google.com'); expect(html).not.toContain('javascript:'); }); test('FormContainer legacy form action is safeUrl-filtered (found via grep sweep)', () => { const html = toHtmlOf(FormContainer)({ action: XSS, method: 'GET' }, '').html; expect(html).not.toContain('javascript:'); // non-relay legacy path (no recipientEmail) still works normally const html2 = toHtmlOf(FormContainer)({ action: '/legacy', method: 'POST' }, '').html; expect(html2).toContain('action="/legacy"'); }); });