@@ -779,6 +779,42 @@ describe('Style#addImport', () => {
779779 'streets-v2'
780780 ] ) ;
781781 } ) ;
782+
783+ test ( 'rejects "__proto__" import id to prevent prototype pollution' , async ( ) => {
784+ const { style} = newStubStyle ( ) ;
785+ // eslint-disable-next-line @typescript-eslint/no-unsafe-argument
786+ style . loadJSON ( createStyleJSON ( ) ) ;
787+ await waitFor ( style , 'style.load' ) ;
788+
789+ const errorSpy = vi . fn ( ) ;
790+ style . on ( 'error' , errorSpy ) ;
791+
792+ // addImport fires an error event and returns early without mutating state
793+ style . addImport ( { id : '__proto__' , url : '/style.json' } ) ;
794+
795+ expect ( errorSpy ) . toHaveBeenCalledOnce ( ) ;
796+ // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
797+ expect ( errorSpy . mock . calls [ 0 ] [ 0 ] . error . message ) . toMatch ( '__proto__' ) ;
798+ // Must not have added the import and must not have polluted Object.prototype
799+ expect ( style . stylesheet . imports ?? [ ] ) . toHaveLength ( 0 ) ;
800+ expect ( Object . hasOwn ( { } , 0 ) ) . toBe ( false ) ;
801+ } ) ;
802+
803+ test ( 'rejects "constructor" and "prototype" import ids to prevent prototype pollution' , async ( ) => {
804+ const { style} = newStubStyle ( ) ;
805+ // eslint-disable-next-line @typescript-eslint/no-unsafe-argument
806+ style . loadJSON ( createStyleJSON ( ) ) ;
807+ await waitFor ( style , 'style.load' ) ;
808+
809+ const errorSpy = vi . fn ( ) ;
810+ style . on ( 'error' , errorSpy ) ;
811+
812+ style . addImport ( { id : 'constructor' , url : '/style.json' } ) ;
813+ style . addImport ( { id : 'prototype' , url : '/style.json' } ) ;
814+
815+ expect ( errorSpy ) . toHaveBeenCalledTimes ( 2 ) ;
816+ expect ( style . stylesheet . imports ?? [ ] ) . toHaveLength ( 0 ) ;
817+ } ) ;
782818} ) ;
783819
784820describe ( 'Style#updateImport' , ( ) => {
0 commit comments