1+ let safemap = new WeakMap ( )
2+ let globals = new Set ( )
3+
4+ Object . getOwnPropertyNames ( globalThis ) . forEach ( key => {
5+ // Prevent Safari deprecation warning...
6+ if ( key === 'styleMedia' ) return
7+
8+ globals . add ( globalThis [ key ] )
9+ } )
10+
111class Token {
212 constructor ( type , value , start , end ) {
313 this . type = type ;
@@ -642,47 +652,47 @@ class Parser {
642652}
643653
644654class Evaluator {
645- evaluate ( { node, scope = { } , context = null , allowGlobal = false , forceBindingRootScopeToFunctions = true } ) {
655+ evaluate ( { node, scope = { } , context = null , forceBindingRootScopeToFunctions = true } ) {
646656 switch ( node . type ) {
647657 case 'Literal' :
648658 return node . value ;
649659
650660 case 'Identifier' :
651661 if ( node . name in scope ) {
652662 const value = scope [ node . name ] ;
663+
664+ this . checkForDangerousValues ( value )
665+
653666 // If it's a function and we're accessing it directly (not calling it),
654667 // bind it to scope to preserve 'this' context for later calls
655668 if ( typeof value === 'function' ) {
656669 return value . bind ( scope ) ;
657670 }
658- return value ;
659- }
660671
661- // Fallback to globals - let CSP catch dangerous ones at runtime
662- if ( allowGlobal && typeof globalThis [ node . name ] !== 'undefined' ) {
663- const value = globalThis [ node . name ] ;
664- if ( typeof value === 'function' ) {
665- return value . bind ( globalThis ) ;
666- }
667672 return value ;
668673 }
669674
670675 throw new Error ( `Undefined variable: ${ node . name } ` ) ;
671676
672677 case 'MemberExpression' :
673- const object = this . evaluate ( { node : node . object , scope, context, allowGlobal , forceBindingRootScopeToFunctions } ) ;
678+ const object = this . evaluate ( { node : node . object , scope, context, forceBindingRootScopeToFunctions } ) ;
674679 if ( object == null ) {
675680 throw new Error ( 'Cannot read property of null or undefined' ) ;
676681 }
677682
678- let memberValue ;
683+ let property ;
679684 if ( node . computed ) {
680- const property = this . evaluate ( { node : node . property , scope, context, allowGlobal, forceBindingRootScopeToFunctions } ) ;
681- memberValue = object [ property ] ;
685+ property = this . evaluate ( { node : node . property , scope, context, forceBindingRootScopeToFunctions } ) ;
682686 } else {
683- memberValue = object [ node . property . name ] ;
687+ property = node . property . name ;
684688 }
685689
690+ this . checkForDangerousKeywords ( property )
691+
692+ let memberValue = object [ property ] ;
693+
694+ this . checkForDangerousValues ( memberValue )
695+
686696 // If the accessed value is a function, bind it based on forceBindingRootScopeToFunctions flag
687697 if ( typeof memberValue === 'function' ) {
688698 if ( forceBindingRootScopeToFunctions ) {
@@ -695,34 +705,38 @@ class Evaluator {
695705 return memberValue ;
696706
697707 case 'CallExpression' :
698- const args = node . arguments . map ( arg => this . evaluate ( { node : arg , scope, context, allowGlobal, forceBindingRootScopeToFunctions } ) ) ;
708+ const args = node . arguments . map ( arg => this . evaluate ( { node : arg , scope, context, forceBindingRootScopeToFunctions } ) ) ;
709+
710+ let returnValue ;
699711
700712 if ( node . callee . type === 'MemberExpression' ) {
701713 // For member expressions, get the object and function separately to preserve context
702- const obj = this . evaluate ( { node : node . callee . object , scope, context, allowGlobal, forceBindingRootScopeToFunctions } ) ;
703- let func ;
714+ const obj = this . evaluate ( { node : node . callee . object , scope, context, forceBindingRootScopeToFunctions } ) ;
715+
716+ let prop ;
704717 if ( node . callee . computed ) {
705- const prop = this . evaluate ( { node : node . callee . property , scope, context, allowGlobal, forceBindingRootScopeToFunctions } ) ;
706- func = obj [ prop ] ;
718+ prop = this . evaluate ( { node : node . callee . property , scope, context, forceBindingRootScopeToFunctions } ) ;
707719 } else {
708- func = obj [ node . callee . property . name ] ;
720+ prop = node . callee . property . name
709721 }
710722
723+ this . checkForDangerousKeywords ( prop )
724+
725+ let func = obj [ prop ] ;
711726 if ( typeof func !== 'function' ) {
712727 throw new Error ( 'Value is not a function' ) ;
713728 }
714729
715730 // For member expressions, always use the object as the 'this' context
716- return func . apply ( obj , args ) ;
731+ returnValue = func . apply ( obj , args ) ;
717732 } else {
718733 // For direct function calls (identifiers), get the original function and apply context
719734 if ( node . callee . type === 'Identifier' ) {
720735 const name = node . callee . name ;
736+
721737 let func ;
722738 if ( name in scope ) {
723739 func = scope [ name ] ;
724- } else if ( allowGlobal && typeof globalThis [ name ] !== 'undefined' ) {
725- func = globalThis [ name ] ;
726740 } else {
727741 throw new Error ( `Undefined variable: ${ name } ` ) ;
728742 }
@@ -733,21 +747,25 @@ class Evaluator {
733747
734748 // For direct calls, use provided context or the scope
735749 const thisContext = context !== null ? context : scope ;
736- return func . apply ( thisContext , args ) ;
750+ returnValue = func . apply ( thisContext , args ) ;
737751 } else {
738752 // For other expressions
739- const callee = this . evaluate ( { node : node . callee , scope, context, allowGlobal , forceBindingRootScopeToFunctions } ) ;
753+ const callee = this . evaluate ( { node : node . callee , scope, context, forceBindingRootScopeToFunctions } ) ;
740754 if ( typeof callee !== 'function' ) {
741755 throw new Error ( 'Value is not a function' ) ;
742756 }
743757
744758 // For other expressions, use provided context
745- return callee . apply ( context , args ) ;
759+ returnValue = callee . apply ( context , args ) ;
746760 }
747761 }
748762
763+ this . checkForDangerousValues ( returnValue )
764+
765+ return returnValue
766+
749767 case 'UnaryExpression' :
750- const argument = this . evaluate ( { node : node . argument , scope, context, allowGlobal , forceBindingRootScopeToFunctions } ) ;
768+ const argument = this . evaluate ( { node : node . argument , scope, context, forceBindingRootScopeToFunctions } ) ;
751769 switch ( node . operator ) {
752770 case '!' : return ! argument ;
753771 case '-' : return - argument ;
@@ -772,9 +790,9 @@ class Evaluator {
772790
773791 return node . prefix ? scope [ name ] : oldValue ;
774792 } else if ( node . argument . type === 'MemberExpression' ) {
775- const obj = this . evaluate ( { node : node . argument . object , scope, context, allowGlobal , forceBindingRootScopeToFunctions } ) ;
793+ const obj = this . evaluate ( { node : node . argument . object , scope, context, forceBindingRootScopeToFunctions } ) ;
776794 const prop = node . argument . computed
777- ? this . evaluate ( { node : node . argument . property , scope, context, allowGlobal , forceBindingRootScopeToFunctions } )
795+ ? this . evaluate ( { node : node . argument . property , scope, context, forceBindingRootScopeToFunctions } )
778796 : node . argument . property . name ;
779797
780798 const oldValue = obj [ prop ] ;
@@ -789,8 +807,8 @@ class Evaluator {
789807 throw new Error ( 'Invalid update expression target' ) ;
790808
791809 case 'BinaryExpression' :
792- const left = this . evaluate ( { node : node . left , scope, context, allowGlobal , forceBindingRootScopeToFunctions } ) ;
793- const right = this . evaluate ( { node : node . right , scope, context, allowGlobal , forceBindingRootScopeToFunctions } ) ;
810+ const left = this . evaluate ( { node : node . left , scope, context, forceBindingRootScopeToFunctions } ) ;
811+ const right = this . evaluate ( { node : node . right , scope, context, forceBindingRootScopeToFunctions } ) ;
794812
795813 switch ( node . operator ) {
796814 case '+' : return left + right ;
@@ -813,41 +831,34 @@ class Evaluator {
813831 }
814832
815833 case 'ConditionalExpression' :
816- const test = this . evaluate ( { node : node . test , scope, context, allowGlobal , forceBindingRootScopeToFunctions } ) ;
834+ const test = this . evaluate ( { node : node . test , scope, context, forceBindingRootScopeToFunctions } ) ;
817835 return test
818- ? this . evaluate ( { node : node . consequent , scope, context, allowGlobal , forceBindingRootScopeToFunctions } )
819- : this . evaluate ( { node : node . alternate , scope, context, allowGlobal , forceBindingRootScopeToFunctions } ) ;
836+ ? this . evaluate ( { node : node . consequent , scope, context, forceBindingRootScopeToFunctions } )
837+ : this . evaluate ( { node : node . alternate , scope, context, forceBindingRootScopeToFunctions } ) ;
820838
821839 case 'AssignmentExpression' :
822- const value = this . evaluate ( { node : node . right , scope, context, allowGlobal , forceBindingRootScopeToFunctions } ) ;
840+ const value = this . evaluate ( { node : node . right , scope, context, forceBindingRootScopeToFunctions } ) ;
823841
824842 if ( node . left . type === 'Identifier' ) {
825843 scope [ node . left . name ] = value ;
826844 return value ;
827845 } else if ( node . left . type === 'MemberExpression' ) {
828- const obj = this . evaluate ( { node : node . left . object , scope, context, allowGlobal, forceBindingRootScopeToFunctions } ) ;
829- if ( node . left . computed ) {
830- const prop = this . evaluate ( { node : node . left . property , scope, context, allowGlobal, forceBindingRootScopeToFunctions } ) ;
831- obj [ prop ] = value ;
832- } else {
833- obj [ node . left . property . name ] = value ;
834- }
835- return value ;
846+ throw new Error ( 'Property assignments are prohibited in the CSP build' )
836847 }
837848 throw new Error ( 'Invalid assignment target' ) ;
838849
839850 case 'ArrayExpression' :
840- return node . elements . map ( el => this . evaluate ( { node : el , scope, context, allowGlobal , forceBindingRootScopeToFunctions } ) ) ;
851+ return node . elements . map ( el => this . evaluate ( { node : el , scope, context, forceBindingRootScopeToFunctions } ) ) ;
841852
842853 case 'ObjectExpression' :
843854 const result = { } ;
844855 for ( const prop of node . properties ) {
845856 const key = prop . computed
846- ? this . evaluate ( { node : prop . key , scope, context, allowGlobal , forceBindingRootScopeToFunctions } )
857+ ? this . evaluate ( { node : prop . key , scope, context, forceBindingRootScopeToFunctions } )
847858 : prop . key . type === 'Identifier'
848859 ? prop . key . name
849- : this . evaluate ( { node : prop . key , scope, context, allowGlobal , forceBindingRootScopeToFunctions } ) ;
850- const value = this . evaluate ( { node : prop . value , scope, context, allowGlobal , forceBindingRootScopeToFunctions } ) ;
860+ : this . evaluate ( { node : prop . key , scope, context, forceBindingRootScopeToFunctions } ) ;
861+ const value = this . evaluate ( { node : prop . value , scope, context, forceBindingRootScopeToFunctions } ) ;
851862 result [ key ] = value ;
852863 }
853864 return result ;
@@ -856,6 +867,44 @@ class Evaluator {
856867 throw new Error ( `Unknown node type: ${ node . type } ` ) ;
857868 }
858869 }
870+
871+ checkForDangerousKeywords ( keyword ) {
872+ let blacklist = [
873+ 'constructor' , 'prototype' , '__proto__' ,
874+ '__defineGetter__' , '__defineSetter__' ,
875+ 'insertAdjacentHTML' ,
876+ ]
877+
878+ if ( blacklist . includes ( keyword ) ) {
879+ throw new Error ( `Accessing "${ keyword } " is prohibited in the CSP build` )
880+ }
881+ }
882+
883+ checkForDangerousValues ( prop ) {
884+ if ( prop === null ) {
885+ return
886+ }
887+
888+ if ( typeof prop !== 'object' && typeof prop !== 'function' ) {
889+ return
890+ }
891+
892+ if ( safemap . has ( prop ) ) {
893+ return
894+ }
895+
896+ if ( prop instanceof HTMLIFrameElement || prop instanceof HTMLScriptElement ) {
897+ throw new Error ( 'Accessing iframes and scripts is prohibited in the CSP build' )
898+ }
899+
900+ if ( globals . has ( prop ) ) {
901+ throw new Error ( 'Accessing global variables is prohibited in the CSP build' )
902+ }
903+
904+ safemap . set ( prop , true )
905+
906+ return true
907+ }
859908}
860909
861910export function generateRuntimeFunction ( expression ) {
@@ -867,9 +916,9 @@ export function generateRuntimeFunction(expression) {
867916 const evaluator = new Evaluator ( ) ;
868917
869918 return function ( options = { } ) {
870- const { scope = { } , context = null , allowGlobal = false , forceBindingRootScopeToFunctions = false } = options ;
919+ const { scope = { } , context = null , forceBindingRootScopeToFunctions = false } = options ;
871920 // Use the scope directly - mutations are expected for assignments
872- return evaluator . evaluate ( { node : ast , scope, context, allowGlobal , forceBindingRootScopeToFunctions } ) ;
921+ return evaluator . evaluate ( { node : ast , scope, context, forceBindingRootScopeToFunctions } ) ;
873922 } ;
874923 } catch ( error ) {
875924 throw new Error ( `CSP Parser Error: ${ error . message } ` ) ;
0 commit comments