1
1
import { spawn , SpawnOptions , spawnSync } from 'child_process' ;
2
- import { quoteAll } from 'shescape/stateless' ;
2
+ import { escapeAll , quoteAll } from 'shescape/stateless' ;
3
+ import * as os from 'node:os' ;
3
4
4
5
interface ProcessOptions {
5
6
cwd ?: string ;
6
7
env ?: { [ name : string ] : string } ;
7
8
}
8
9
9
- function makeSpawnOptions ( options ?: ProcessOptions ) {
10
+ function processArguments (
11
+ args : string [ ] ,
12
+ spawnOptions : SpawnOptions
13
+ ) : string [ ] {
14
+ if ( ! args ) {
15
+ return args ;
16
+ }
17
+
18
+ // Best practices, also security-wise, is to not invoke processes in a shell, but as a stand-alone command.
19
+ // However, on Windows, we need to invoke the command in a shell, due to internal NodeJS problems with this approach
20
+ // see: https://nodejs.org/docs/latest-v24.x/api/child_process.html#spawning-bat-and-cmd-files-on-windows
21
+ const isWinLocal = / ^ w i n / . test ( os . platform ( ) ) ;
22
+ if ( isWinLocal ) {
23
+ spawnOptions . shell = true ;
24
+ // Further, we distinguish between quoting and escaping arguments since quoteAll does not support quoting without
25
+ // supplying a shell, but escapeAll does.
26
+ // See this (very long) discussion for more details: https://github.com/ericcornelissen/shescape/issues/2009
27
+ return quoteAll ( args , { ...spawnOptions , flagProtection : false } ) ;
28
+ } else {
29
+ return escapeAll ( args , { ...spawnOptions , flagProtection : false } ) ;
30
+ }
31
+ }
32
+
33
+ function makeSpawnOptions ( options ?: ProcessOptions ) : SpawnOptions {
10
34
const spawnOptions : SpawnOptions = {
11
- shell : true ,
35
+ shell : false ,
12
36
env : { ...process . env } ,
13
37
} ;
38
+
14
39
if ( options && options . cwd ) {
15
40
spawnOptions . cwd = options . cwd ;
16
41
}
@@ -19,7 +44,7 @@ function makeSpawnOptions(options?: ProcessOptions) {
19
44
}
20
45
21
46
// Before spawning an external process, we look if we need to restore the system proxy configuration,
22
- // which overides the cli internal proxy configuration.
47
+ // which overrides the cli internal proxy configuration.
23
48
if ( process . env . SNYK_SYSTEM_HTTP_PROXY !== undefined ) {
24
49
spawnOptions . env . HTTP_PROXY = process . env . SNYK_SYSTEM_HTTP_PROXY ;
25
50
}
@@ -39,19 +64,26 @@ export function execute(
39
64
options ?: ProcessOptions
40
65
) : Promise < string > {
41
66
const spawnOptions = makeSpawnOptions ( options ) ;
42
- args = quoteAll ( args , { flagProtection : false } ) ;
67
+ const processedArgs = processArguments ( args , spawnOptions ) ;
68
+
43
69
return new Promise ( ( resolve , reject ) => {
44
70
let stdout = '' ;
45
71
let stderr = '' ;
46
72
47
- const proc = spawn ( command , args , spawnOptions ) ;
73
+ const proc = spawn ( command , processedArgs , spawnOptions ) ;
48
74
proc . stdout . on ( 'data' , ( data ) => {
49
75
stdout = stdout + data ;
50
76
} ) ;
77
+
51
78
proc . stderr . on ( 'data' , ( data ) => {
52
79
stderr = stderr + data ;
53
80
} ) ;
54
81
82
+ proc . on ( 'error' , ( err ) => {
83
+ stderr = err . message ;
84
+ return reject ( { stdout, stderr } ) ;
85
+ } ) ;
86
+
55
87
proc . on ( 'close' , ( code ) => {
56
88
if ( code !== 0 ) {
57
89
return reject ( stdout || stderr ) ;
@@ -67,7 +99,7 @@ export function executeSync(
67
99
options ?: ProcessOptions
68
100
) {
69
101
const spawnOptions = makeSpawnOptions ( options ) ;
70
- args = quoteAll ( args , { flagProtection : false } ) ;
102
+ const processedArgs = processArguments ( args , spawnOptions ) ;
71
103
72
- return spawnSync ( command , args , spawnOptions ) ;
104
+ return spawnSync ( command , processedArgs , spawnOptions ) ;
73
105
}
0 commit comments