@@ -478,6 +478,76 @@ describe('render to string', () => {
478478 } )
479479 } )
480480
481+ describe ( 'XSS prevention for attribute keys' , ( ) => {
482+ it ( 'Should skip attribute keys containing double quotes' , ( ) => {
483+ const props : Record < string , string > = { [ '" onfocus="alert(1)' ] : 'x' }
484+ const template = < div { ...props } > Hello</ div >
485+ expect ( template . toString ( ) ) . toBe ( '<div>Hello</div>' )
486+ } )
487+
488+ it ( 'Should skip attribute keys containing >' , ( ) => {
489+ const props : Record < string , string > = { [ '"><script>alert(1)</script><x x="' ] : 'x' }
490+ const template = < div { ...props } > Hello</ div >
491+ expect ( template . toString ( ) ) . toBe ( '<div>Hello</div>' )
492+ } )
493+
494+ it ( 'Should skip attribute keys containing <' , ( ) => {
495+ const props : Record < string , string > = { [ 'foo<bar' ] : 'x' }
496+ const template = < div { ...props } > Hello</ div >
497+ expect ( template . toString ( ) ) . toBe ( '<div>Hello</div>' )
498+ } )
499+
500+ it ( 'Should skip attribute keys containing backslashes' , ( ) => {
501+ const props : Record < string , string > = { [ 'foo\\bar' ] : 'x' }
502+ const template = < div { ...props } > Hello</ div >
503+ expect ( template . toString ( ) ) . toBe ( '<div>Hello</div>' )
504+ } )
505+
506+ it ( 'Should skip attribute keys containing backticks' , ( ) => {
507+ const props : Record < string , string > = { [ 'foo`bar' ] : 'x' }
508+ const template = < div { ...props } > Hello</ div >
509+ expect ( template . toString ( ) ) . toBe ( '<div>Hello</div>' )
510+ } )
511+
512+ it ( 'Should skip attribute keys containing spaces' , ( ) => {
513+ const props : Record < string , string > = { [ 'foo bar' ] : 'x' }
514+ const template = < div { ...props } > Hello</ div >
515+ expect ( template . toString ( ) ) . toBe ( '<div>Hello</div>' )
516+ } )
517+
518+ it ( 'Should still render valid attributes alongside invalid ones' , ( ) => {
519+ const props : Record < string , string > = {
520+ id : 'safe' ,
521+ [ '" onfocus="alert(1)' ] : 'x' ,
522+ class : 'test' ,
523+ }
524+ const template = < div { ...props } > Hello</ div >
525+ expect ( template . toString ( ) ) . toBe ( '<div id="safe" class="test">Hello</div>' )
526+ } )
527+
528+ it ( 'Should skip invalid attribute keys before style serialization' , ( ) => {
529+ const template = < div { ...{ [ '" onfocus="alert(1)' ] : { fontSize : 10 } } } > Hello</ div >
530+ expect ( template . toString ( ) ) . toBe ( '<div>Hello</div>' )
531+ } )
532+
533+ it ( 'Should skip invalid attribute keys before function prop validation' , ( ) => {
534+ const template = < div { ...{ [ '" onfocus="alert(1)' ] : ( ) => 'x' } } > Hello</ div >
535+ expect ( template . toString ( ) ) . toBe ( '<div>Hello</div>' )
536+ } )
537+
538+ it ( 'Should skip invalid attribute keys before dangerouslySetInnerHTML handling' , ( ) => {
539+ const template = (
540+ < div { ...{ [ '" onfocus="alert(1)' ] : { __html : '<strong>Injected</strong>' } } } > Hello</ div >
541+ )
542+ expect ( template . toString ( ) ) . toBe ( '<div>Hello</div>' )
543+ } )
544+
545+ it ( 'Should skip invalid attribute keys with Promise values' , async ( ) => {
546+ const template = < div { ...{ [ '" onfocus="alert(1)' ] : Promise . resolve ( 'x' ) } } > Hello</ div >
547+ expect ( await template . toString ( ) ) . toBe ( '<div>Hello</div>' )
548+ } )
549+ } )
550+
481551 describe ( 'head' , ( ) => {
482552 it ( 'Simple head elements should be rendered as is' , ( ) => {
483553 const template = (
@@ -667,6 +737,15 @@ describe('SVG', () => {
667737 )
668738 } )
669739
740+ it ( 'should skip invalid attribute keys in SVG while preserving valid ones' , ( ) => {
741+ const template = (
742+ < svg >
743+ < g { ...{ [ '" onload="alert(1)' ] : 'x' , viewBox : '0 0 10 10' } } />
744+ </ svg >
745+ )
746+ expect ( template . toString ( ) ) . toBe ( '<svg><g viewBox="0 0 10 10"></g></svg>' )
747+ } )
748+
670749 describe ( 'attribute' , ( ) => {
671750 describe ( 'camelCase' , ( ) => {
672751 test . each `
0 commit comments