@@ -6,6 +6,7 @@ import { motion } from 'framer-motion';
66import { ControlsPanel } from './components/ControlsPanel' ;
77import { ExportPanel } from './components/ExportPanel' ;
88import InteractiveViewer from '@/components/InteractiveViewer' ;
9+ import DOMPurify from 'dompurify' ;
910import type {
1011 ExportFormat ,
1112 Font ,
@@ -142,11 +143,18 @@ export default function CustomizePage(): ReactElement {
142143 } )
143144 . then ( ( text ) => {
144145 if ( ! text ) return ;
145- // Basic SVG sanitization to prevent XSS (strip scripts and inline event handlers)
146- const sanitized = text
147- . replace ( / < s c r i p t [ \s \S ] * ?> [ \s \S ] * ?< \/ s c r i p t > / gi, '' )
148- . replace ( / o n \w + \s * = \s * ( " [ ^ " ] * " | ' [ ^ ' ] * ' ) / gi, '' ) ;
149- setSvgContent ( sanitized ) ;
146+ // Sanitize SVG using DOMPurify with the SVG profile.
147+ // - Forbid risky tags like foreignObject and embedded content
148+ // - Forbid xlink:href to avoid external references
149+ // - Use a conservative URI whitelist to prevent javascript: URIs
150+ const sanitized = DOMPurify . sanitize ( text , {
151+ USE_PROFILES : { svg : true } ,
152+ FORBID_TAGS : [ 'foreignObject' , 'iframe' , 'object' , 'embed' , 'script' ] ,
153+ FORBID_ATTR : [ 'xlink:href' ] ,
154+ ALLOWED_URI_REGEXP : / ^ (?: (?: h t t p s ? | m a i l t o | d a t a ) : | # ) / i,
155+ } ) ;
156+
157+ setSvgContent ( sanitized as string ) ;
150158 setSvgState ( 'loaded' ) ;
151159 setErrorMessage ( null ) ;
152160 } )
0 commit comments