@@ -384,11 +384,63 @@ export const createConcurrentThemeFromObject = (base: any, options?: any): Concu
384384 return createTheme ( theme ) as ConcurrentTheme
385385}
386386
387+ export function sanitizeTheme < T > ( input : T ) : T {
388+ const forbiddenKeys = new Set ( [ 'href' , 'xlinkHref' , 'dangerouslySetInnerHTML' ] )
389+ const normalizeForSchemeCheck = ( s : string ) =>
390+ s . trim ( ) . replace ( / [ \u0000 - \u001F \u007F - \u009F \u200B - \u200D \uFEFF ] / g, '' )
391+
392+ const isDangerousStringValue = ( v : string ) : boolean => {
393+ const s = normalizeForSchemeCheck ( v )
394+ if ( / ^ (?: j a v a s c r i p t | v b s c r i p t | f i l e ) \s * : / i. test ( s ) ) return true
395+ if ( / ^ d a t a \s * : / i. test ( s ) ) return true
396+ if ( / [ < > ] / . test ( s ) ) return true
397+ if ( / l i n e a r - g r a d i e n t \s * \( / i. test ( s ) ) return true
398+ return false
399+ }
400+
401+ const seen = new WeakMap < object , any > ( )
402+
403+ const cloneAndStrip = ( value : any ) : any => {
404+ if ( value === null || typeof value === undefined ) return value
405+
406+ if ( typeof value === 'string' ) {
407+ if ( isDangerousStringValue ( value ) ) return undefined
408+ return value
409+ }
410+
411+ if ( seen . has ( value ) ) return seen . get ( value )
412+
413+ if ( typeof value !== 'object' ) return value
414+
415+ if ( Array . isArray ( value ) ) {
416+ const arr : any [ ] = [ ]
417+ seen . set ( value , arr )
418+ for ( const item of value ) arr . push ( cloneAndStrip ( item ) )
419+ return arr
420+ }
421+
422+ const out : Record < string , any > = { }
423+ seen . set ( value , out )
424+
425+ for ( const [ k , v ] of Object . entries ( value ) ) {
426+ if ( forbiddenKeys . has ( k ) ) continue
427+ const sanitized = cloneAndStrip ( v )
428+ if ( sanitized === undefined ) continue
429+ out [ k ] = sanitized
430+ }
431+
432+ return out
433+ }
434+
435+ return cloneAndStrip ( input ) as T
436+ }
437+
387438export const loadConcurrentTheme = (
388439 name : string ,
389440 customs : Record < string , DeepPartial < ConcurrentTheme > > = { } ,
390441 options ?: { fontSize ?: number }
391442) : ConcurrentTheme => {
392443 const base = customs [ name ] ?? Themes [ name ] ?? Themes . blue
393- return createConcurrentThemeFromObject ( base , options )
444+ const generated = createConcurrentThemeFromObject ( base , options )
445+ return sanitizeTheme < ConcurrentTheme > ( generated )
394446}
0 commit comments