@@ -40,6 +40,7 @@ const {
4040 RegExpPrototypeExec,
4141 SafeSet,
4242 StringPrototypeIncludes,
43+ StringPrototypeIndexOf,
4344 StringPrototypeSlice,
4445 StringPrototypeToUpperCase,
4546 SymbolDispose,
@@ -93,6 +94,8 @@ const {
9394 stdioStringToArray,
9495} = child_process ;
9596
97+ const { getEnvPairs } = internalBinding ( 'process_wrap' ) ;
98+
9699const MAX_BUFFER = 1024 * 1024 ;
97100
98101const permission = require ( 'internal/process/permission' ) ;
@@ -697,60 +700,74 @@ function normalizeSpawnArguments(file, args, options) {
697700 ArrayPrototypeUnshift ( args , file ) ;
698701 }
699702
700- // Shallow copy to guarantee changes won't impact process.env
701- const env = options . env || { ...process . env } ;
702- const envPairs = [ ] ;
703-
704- // process.env.NODE_V8_COVERAGE always propagates, making it possible to
705- // collect coverage for programs that spawn with white-listed environment.
706- copyProcessEnvToEnv ( env , 'NODE_V8_COVERAGE' , options . env ) ;
707-
708- if ( isZOS ) {
709- // The following environment variables must always propagate if set.
710- copyProcessEnvToEnv ( env , '_BPXK_AUTOCVT' , options . env ) ;
711- copyProcessEnvToEnv ( env , '_CEE_RUNOPTS' , options . env ) ;
712- copyProcessEnvToEnv ( env , '_TAG_REDIR_ERR' , options . env ) ;
713- copyProcessEnvToEnv ( env , '_TAG_REDIR_IN' , options . env ) ;
714- copyProcessEnvToEnv ( env , '_TAG_REDIR_OUT' , options . env ) ;
715- copyProcessEnvToEnv ( env , 'STEPLIB' , options . env ) ;
716- copyProcessEnvToEnv ( env , 'LIBPATH' , options . env ) ;
717- copyProcessEnvToEnv ( env , '_EDC_SIG_DFLT' , options . env ) ;
718- copyProcessEnvToEnv ( env , '_EDC_SUSV3' , options . env ) ;
719- }
703+ let envPairs ;
704+ if ( ! options . env && ! permission . isEnabled ( ) ) {
705+ // Default: the child inherits this process's environment. Take it as
706+ // 'KEY=value' strings in one native pass over the environment block
707+ // instead of copying process.env (one interceptor round trip and one
708+ // getenv() scan per variable). Everything copyProcessEnvToEnv() would
709+ // propagate below is part of it by definition, and entries of the real
710+ // environment block cannot contain null bytes.
711+ envPairs = getEnvPairs ( ) ;
712+ if ( process . platform === 'win32' ) {
713+ envPairs = dedupeWindowsEnvPairs ( envPairs ) ;
714+ }
715+ } else {
716+ // Shallow copy to guarantee changes won't impact process.env
717+ const env = options . env || { ...process . env } ;
718+ envPairs = [ ] ;
719+
720+ // process.env.NODE_V8_COVERAGE always propagates, making it possible to
721+ // collect coverage for programs that spawn with white-listed environment.
722+ copyProcessEnvToEnv ( env , 'NODE_V8_COVERAGE' , options . env ) ;
723+
724+ if ( isZOS ) {
725+ // The following environment variables must always propagate if set.
726+ copyProcessEnvToEnv ( env , '_BPXK_AUTOCVT' , options . env ) ;
727+ copyProcessEnvToEnv ( env , '_CEE_RUNOPTS' , options . env ) ;
728+ copyProcessEnvToEnv ( env , '_TAG_REDIR_ERR' , options . env ) ;
729+ copyProcessEnvToEnv ( env , '_TAG_REDIR_IN' , options . env ) ;
730+ copyProcessEnvToEnv ( env , '_TAG_REDIR_OUT' , options . env ) ;
731+ copyProcessEnvToEnv ( env , 'STEPLIB' , options . env ) ;
732+ copyProcessEnvToEnv ( env , 'LIBPATH' , options . env ) ;
733+ copyProcessEnvToEnv ( env , '_EDC_SIG_DFLT' , options . env ) ;
734+ copyProcessEnvToEnv ( env , '_EDC_SUSV3' , options . env ) ;
735+ }
720736
721- if ( permission . isEnabled ( ) ) {
722- copyPermissionModelFlagsToEnv ( env , 'NODE_OPTIONS' , args ) ;
723- }
737+ if ( permission . isEnabled ( ) ) {
738+ copyPermissionModelFlagsToEnv ( env , 'NODE_OPTIONS' , args ) ;
739+ }
724740
725- let envKeys = [ ] ;
726- // Prototype values are intentionally included.
727- for ( const key in env ) {
728- ArrayPrototypePush ( envKeys , key ) ;
729- }
741+ let envKeys = [ ] ;
742+ // Prototype values are intentionally included.
743+ for ( const key in env ) {
744+ ArrayPrototypePush ( envKeys , key ) ;
745+ }
730746
731- if ( process . platform === 'win32' ) {
732- // On Windows env keys are case insensitive. Filter out duplicates,
733- // keeping only the first one (in lexicographic order)
734- const sawKey = new SafeSet ( ) ;
735- envKeys = ArrayPrototypeFilter (
736- ArrayPrototypeSort ( envKeys ) ,
737- ( key ) => {
738- const uppercaseKey = StringPrototypeToUpperCase ( key ) ;
739- if ( sawKey . has ( uppercaseKey ) ) {
740- return false ;
741- }
742- sawKey . add ( uppercaseKey ) ;
743- return true ;
744- } ,
745- ) ;
746- }
747+ if ( process . platform === 'win32' ) {
748+ // On Windows env keys are case insensitive. Filter out duplicates,
749+ // keeping only the first one (in lexicographic order)
750+ const sawKey = new SafeSet ( ) ;
751+ envKeys = ArrayPrototypeFilter (
752+ ArrayPrototypeSort ( envKeys ) ,
753+ ( key ) => {
754+ const uppercaseKey = StringPrototypeToUpperCase ( key ) ;
755+ if ( sawKey . has ( uppercaseKey ) ) {
756+ return false ;
757+ }
758+ sawKey . add ( uppercaseKey ) ;
759+ return true ;
760+ } ,
761+ ) ;
762+ }
747763
748- for ( const key of envKeys ) {
749- const value = env [ key ] ;
750- if ( value !== undefined ) {
751- validateArgumentNullCheck ( key , `options.env['${ key } ']` ) ;
752- validateArgumentNullCheck ( value , `options.env['${ key } ']` ) ;
753- ArrayPrototypePush ( envPairs , `${ key } =${ value } ` ) ;
764+ for ( const key of envKeys ) {
765+ const value = env [ key ] ;
766+ if ( value !== undefined ) {
767+ validateArgumentNullCheck ( key , `options.env['${ key } ']` ) ;
768+ validateArgumentNullCheck ( value , `options.env['${ key } ']` ) ;
769+ ArrayPrototypePush ( envPairs , `${ key } =${ value } ` ) ;
770+ }
754771 }
755772 }
756773
@@ -768,6 +785,34 @@ function normalizeSpawnArguments(file, args, options) {
768785 } ;
769786}
770787
788+ /**
789+ * Windows environment variable names are case-insensitive: keep only the
790+ * first entry for each name in lexicographic order of the names, exactly like
791+ * the key filtering applied to a user-supplied `options.env`.
792+ * @param {string[] } envPairs 'KEY=value' strings
793+ * @returns {string[] }
794+ */
795+ function dedupeWindowsEnvPairs ( envPairs ) {
796+ const keyed = [ ] ;
797+ for ( let i = 0 ; i < envPairs . length ; i ++ ) {
798+ const pair = envPairs [ i ] ;
799+ // Names never start with '=' here (hidden variables are not enumerated),
800+ // so the first '=' ends the name.
801+ const eq = StringPrototypeIndexOf ( pair , '=' ) ;
802+ ArrayPrototypePush ( keyed , { key : eq === - 1 ? pair : StringPrototypeSlice ( pair , 0 , eq ) , pair } ) ;
803+ }
804+ ArrayPrototypeSort ( keyed , ( a , b ) => ( a . key < b . key ? - 1 : a . key > b . key ? 1 : 0 ) ) ;
805+ const sawKey = new SafeSet ( ) ;
806+ const result = [ ] ;
807+ for ( let i = 0 ; i < keyed . length ; i ++ ) {
808+ const uppercaseKey = StringPrototypeToUpperCase ( keyed [ i ] . key ) ;
809+ if ( sawKey . has ( uppercaseKey ) ) continue ;
810+ sawKey . add ( uppercaseKey ) ;
811+ ArrayPrototypePush ( result , keyed [ i ] . pair ) ;
812+ }
813+ return result ;
814+ }
815+
771816function abortChildProcess ( child , killSignal , reason ) {
772817 if ( ! child )
773818 return ;
0 commit comments