@vibe-agent-toolkit/utils 0.1.41-rc.1 → 0.1.41-rc.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -0
- package/dist/index.js.map +1 -1
- package/dist/safe-exec.d.ts.map +1 -1
- package/dist/safe-exec.js +2 -75
- package/dist/safe-exec.js.map +1 -1
- package/dist/skill-test/spawn-claude.d.ts +1 -1
- package/dist/skill-test/spawn-claude.d.ts.map +1 -1
- package/dist/skill-test/spawn-claude.js +15 -6
- package/dist/skill-test/spawn-claude.js.map +1 -1
- package/dist/spawn-hardened.d.ts +22 -0
- package/dist/spawn-hardened.d.ts.map +1 -0
- package/dist/spawn-hardened.js +47 -0
- package/dist/spawn-hardened.js.map +1 -0
- package/dist/windows-shell.d.ts +75 -0
- package/dist/windows-shell.d.ts.map +1 -0
- package/dist/windows-shell.js +97 -0
- package/dist/windows-shell.js.map +1 -0
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -5,6 +5,8 @@
|
|
|
5
5
|
* Utilities are added as needed by other packages, not speculatively.
|
|
6
6
|
*/
|
|
7
7
|
export * from './safe-exec.js';
|
|
8
|
+
export * from './windows-shell.js';
|
|
9
|
+
export * from './spawn-hardened.js';
|
|
8
10
|
export * from './path-utils.js';
|
|
9
11
|
export * from './asset-reference.js';
|
|
10
12
|
export * from './fs-utils.js';
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAGH,cAAc,gBAAgB,CAAC;AAG/B,cAAc,iBAAiB,CAAC;AAGhC,cAAc,sBAAsB,CAAC;AAGrC,cAAc,eAAe,CAAC;AAG9B,cAAc,mBAAmB,CAAC;AAGlC,cAAc,wBAAwB,CAAC;AAGvC,cAAc,cAAc,CAAC;AAG7B,cAAc,gBAAgB,CAAC;AAI/B,cAAc,oBAAoB,CAAC;AAGnC,cAAc,kBAAkB,CAAC;AAGjC,cAAc,mBAAmB,CAAC;AAGlC,cAAc,wBAAwB,CAAC;AAGvC,cAAc,eAAe,CAAC;AAG9B,cAAc,oBAAoB,CAAC;AAInC,OAAO,EACL,KAAK,cAAc,EACnB,KAAK,QAAQ,EACb,KAAK,YAAY,EACjB,KAAK,aAAa,EAClB,uBAAuB,EACvB,KAAK,cAAc,GACpB,MAAM,wBAAwB,CAAC;AAChC,YAAY,EAAE,aAAa,EAAE,MAAM,gCAAgC,CAAC;AACpE,YAAY,EAAE,WAAW,EAAE,MAAM,wBAAwB,CAAC;AAC1D,OAAO,EAAE,iBAAiB,EAAE,KAAK,WAAW,EAAE,MAAM,8BAA8B,CAAC;AACnF,OAAO,EAAE,WAAW,EAAE,iBAAiB,EAAE,MAAM,6BAA6B,CAAC;AAG7E,cAAc,uBAAuB,CAAC;AAGtC,cAAc,wBAAwB,CAAC;AAGvC,cAAc,mBAAmB,CAAC;AAGlC,cAAc,yBAAyB,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAGH,cAAc,gBAAgB,CAAC;AAG/B,cAAc,oBAAoB,CAAC;AAGnC,cAAc,qBAAqB,CAAC;AAGpC,cAAc,iBAAiB,CAAC;AAGhC,cAAc,sBAAsB,CAAC;AAGrC,cAAc,eAAe,CAAC;AAG9B,cAAc,mBAAmB,CAAC;AAGlC,cAAc,wBAAwB,CAAC;AAGvC,cAAc,cAAc,CAAC;AAG7B,cAAc,gBAAgB,CAAC;AAI/B,cAAc,oBAAoB,CAAC;AAGnC,cAAc,kBAAkB,CAAC;AAGjC,cAAc,mBAAmB,CAAC;AAGlC,cAAc,wBAAwB,CAAC;AAGvC,cAAc,eAAe,CAAC;AAG9B,cAAc,oBAAoB,CAAC;AAInC,OAAO,EACL,KAAK,cAAc,EACnB,KAAK,QAAQ,EACb,KAAK,YAAY,EACjB,KAAK,aAAa,EAClB,uBAAuB,EACvB,KAAK,cAAc,GACpB,MAAM,wBAAwB,CAAC;AAChC,YAAY,EAAE,aAAa,EAAE,MAAM,gCAAgC,CAAC;AACpE,YAAY,EAAE,WAAW,EAAE,MAAM,wBAAwB,CAAC;AAC1D,OAAO,EAAE,iBAAiB,EAAE,KAAK,WAAW,EAAE,MAAM,8BAA8B,CAAC;AACnF,OAAO,EAAE,WAAW,EAAE,iBAAiB,EAAE,MAAM,6BAA6B,CAAC;AAG7E,cAAc,uBAAuB,CAAC;AAGtC,cAAc,wBAAwB,CAAC;AAGvC,cAAc,mBAAmB,CAAC;AAGlC,cAAc,yBAAyB,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -6,6 +6,10 @@
|
|
|
6
6
|
*/
|
|
7
7
|
// Safe command execution (cross-platform, no shell injection)
|
|
8
8
|
export * from './safe-exec.js';
|
|
9
|
+
// Windows shell-invocation helpers (.cmd/.bat/.ps1 handling), shared by every spawn wrapper
|
|
10
|
+
export * from './windows-shell.js';
|
|
11
|
+
// Hardened async spawn (streaming stdio + correct Windows .cmd/.bat launching)
|
|
12
|
+
export * from './spawn-hardened.js';
|
|
9
13
|
// Cross-platform path utilities
|
|
10
14
|
export * from './path-utils.js';
|
|
11
15
|
// Asset reference resolution (paths + npm bare specifiers)
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,8DAA8D;AAC9D,cAAc,gBAAgB,CAAC;AAE/B,gCAAgC;AAChC,cAAc,iBAAiB,CAAC;AAEhC,2DAA2D;AAC3D,cAAc,sBAAsB,CAAC;AAErC,uBAAuB;AACvB,cAAc,eAAe,CAAC;AAE9B,wCAAwC;AACxC,cAAc,mBAAmB,CAAC;AAElC,sBAAsB;AACtB,cAAc,wBAAwB,CAAC;AAEvC,uEAAuE;AACvE,cAAc,cAAc,CAAC;AAE7B,8CAA8C;AAC9C,cAAc,gBAAgB,CAAC;AAE/B,2DAA2D;AAC3D,iEAAiE;AACjE,cAAc,oBAAoB,CAAC;AAEnC,yDAAyD;AACzD,cAAc,kBAAkB,CAAC;AAEjC,oDAAoD;AACpD,cAAc,mBAAmB,CAAC;AAElC,4CAA4C;AAC5C,cAAc,wBAAwB,CAAC;AAEvC,2DAA2D;AAC3D,cAAc,eAAe,CAAC;AAE9B,oEAAoE;AACpE,cAAc,oBAAoB,CAAC;AAEnC,uDAAuD;AACvD,uEAAuE;AACvE,OAAO,EAKL,uBAAuB,GAExB,MAAM,wBAAwB,CAAC;AAGhC,OAAO,EAAE,iBAAiB,EAAoB,MAAM,8BAA8B,CAAC;AACnF,OAAO,EAAE,WAAW,EAAE,iBAAiB,EAAE,MAAM,6BAA6B,CAAC;AAE7E,2EAA2E;AAC3E,cAAc,uBAAuB,CAAC;AAEtC,oEAAoE;AACpE,cAAc,wBAAwB,CAAC;AAEvC,gDAAgD;AAChD,cAAc,mBAAmB,CAAC;AAElC,8EAA8E;AAC9E,cAAc,yBAAyB,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,8DAA8D;AAC9D,cAAc,gBAAgB,CAAC;AAE/B,4FAA4F;AAC5F,cAAc,oBAAoB,CAAC;AAEnC,+EAA+E;AAC/E,cAAc,qBAAqB,CAAC;AAEpC,gCAAgC;AAChC,cAAc,iBAAiB,CAAC;AAEhC,2DAA2D;AAC3D,cAAc,sBAAsB,CAAC;AAErC,uBAAuB;AACvB,cAAc,eAAe,CAAC;AAE9B,wCAAwC;AACxC,cAAc,mBAAmB,CAAC;AAElC,sBAAsB;AACtB,cAAc,wBAAwB,CAAC;AAEvC,uEAAuE;AACvE,cAAc,cAAc,CAAC;AAE7B,8CAA8C;AAC9C,cAAc,gBAAgB,CAAC;AAE/B,2DAA2D;AAC3D,iEAAiE;AACjE,cAAc,oBAAoB,CAAC;AAEnC,yDAAyD;AACzD,cAAc,kBAAkB,CAAC;AAEjC,oDAAoD;AACpD,cAAc,mBAAmB,CAAC;AAElC,4CAA4C;AAC5C,cAAc,wBAAwB,CAAC;AAEvC,2DAA2D;AAC3D,cAAc,eAAe,CAAC;AAE9B,oEAAoE;AACpE,cAAc,oBAAoB,CAAC;AAEnC,uDAAuD;AACvD,uEAAuE;AACvE,OAAO,EAKL,uBAAuB,GAExB,MAAM,wBAAwB,CAAC;AAGhC,OAAO,EAAE,iBAAiB,EAAoB,MAAM,8BAA8B,CAAC;AACnF,OAAO,EAAE,WAAW,EAAE,iBAAiB,EAAE,MAAM,6BAA6B,CAAC;AAE7E,2EAA2E;AAC3E,cAAc,uBAAuB,CAAC;AAEtC,oEAAoE;AACpE,cAAc,wBAAwB,CAAC;AAEvC,gDAAgD;AAChD,cAAc,mBAAmB,CAAC;AAElC,8EAA8E;AAC9E,cAAc,yBAAyB,CAAC"}
|
package/dist/safe-exec.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"safe-exec.d.ts","sourceRoot":"","sources":["../src/safe-exec.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"safe-exec.d.ts","sourceRoot":"","sources":["../src/safe-exec.ts"],"names":[],"mappings":"AAMA;;GAEG;AACH,MAAM,WAAW,eAAe;IAC9B,kEAAkE;IAClE,QAAQ,CAAC,EAAE,cAAc,CAAC;IAC1B,iCAAiC;IACjC,KAAK,CAAC,EAAE,MAAM,GAAG,QAAQ,GAAG,SAAS,GAAG,KAAK,CAAC,MAAM,GAAG,QAAQ,GAAG,SAAS,CAAC,CAAC;IAC7E,6EAA6E;IAC7E,GAAG,CAAC,EAAE,MAAM,CAAC,UAAU,CAAC;IACxB,wBAAwB;IACxB,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,0CAA0C;IAC1C,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,8BAA8B;IAC9B,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED;;GAEG;AACH,MAAM,WAAW,cAAc;IAC7B,6DAA6D;IAC7D,OAAO,EAAE,OAAO,CAAC;IACjB,8BAA8B;IAC9B,MAAM,EAAE,MAAM,CAAC;IACf,sBAAsB;IACtB,MAAM,EAAE,MAAM,GAAG,MAAM,CAAC;IACxB,qBAAqB;IACrB,MAAM,EAAE,MAAM,GAAG,MAAM,CAAC;IACxB,8CAA8C;IAC9C,KAAK,CAAC,EAAE,KAAK,CAAC;CACf;AAED;;GAEG;AACH,qBAAa,qBAAsB,SAAQ,KAAK;IAC9C,SAAgB,MAAM,EAAE,MAAM,CAAC;IAC/B,SAAgB,MAAM,EAAE,MAAM,GAAG,MAAM,CAAC;IACxC,SAAgB,MAAM,EAAE,MAAM,GAAG,MAAM,CAAC;gBAGtC,OAAO,EAAE,MAAM,EACf,MAAM,EAAE,MAAM,EACd,MAAM,EAAE,MAAM,GAAG,MAAM,EACvB,MAAM,EAAE,MAAM,GAAG,MAAM;CAQ1B;AAuCD;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AACH,wBAAgB,YAAY,CAC1B,OAAO,EAAE,MAAM,EACf,IAAI,GAAE,MAAM,EAAO,EACnB,OAAO,GAAE,eAAoB,GAC5B,MAAM,GAAG,MAAM,CAmBjB;AAED;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,cAAc,CAC5B,OAAO,EAAE,MAAM,EACf,IAAI,GAAE,MAAM,EAAO,EACnB,OAAO,GAAE,eAAoB,GAC5B,cAAc,CA4BhB;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,eAAe,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAOzD;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,cAAc,CAC5B,QAAQ,EAAE,MAAM,EAChB,UAAU,GAAE,MAAoB,GAC/B,MAAM,GAAG,IAAI,CAUf;AAuBD;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AACH,wBAAgB,cAAc,CAAC,aAAa,EAAE,MAAM,GAAG;IACrD,cAAc,EAAE,OAAO,CAAC;IACxB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB,CA0CA;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAoDG;AACH,wBAAgB,kBAAkB,CAChC,aAAa,EAAE,MAAM,EACrB,OAAO,GAAE,eAAoB,GAC5B,MAAM,GAAG,MAAM,CAyBjB"}
|
package/dist/safe-exec.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { spawnSync } from 'node:child_process';
|
|
2
2
|
import which from 'which';
|
|
3
|
+
import { buildWindowsShellLine, shouldUseShell } from './windows-shell.js';
|
|
3
4
|
/**
|
|
4
5
|
* Error thrown when command execution fails
|
|
5
6
|
*/
|
|
@@ -15,80 +16,6 @@ export class CommandExecutionError extends Error {
|
|
|
15
16
|
this.stderr = stderr;
|
|
16
17
|
}
|
|
17
18
|
}
|
|
18
|
-
/**
|
|
19
|
-
* Determine if shell should be used for command execution on Windows
|
|
20
|
-
*
|
|
21
|
-
* ## Security Context
|
|
22
|
-
*
|
|
23
|
-
* This package's primary security model is `shell: false` to prevent command injection.
|
|
24
|
-
* Windows requires `shell: true` only for shell scripts (.cmd/.bat/.ps1) which require
|
|
25
|
-
* a shell interpreter by design (not executable binaries).
|
|
26
|
-
*
|
|
27
|
-
* ## Node.js on Windows - NO SHELL REQUIRED
|
|
28
|
-
*
|
|
29
|
-
* **Previous behavior (REMOVED):** Used shell:true for 'node' command
|
|
30
|
-
* **Problem discovered:** Node.js DEP0190 deprecation warning - passing args array with
|
|
31
|
-
* shell:true leads to incorrect command execution. Exit codes are ignored (always returns 0).
|
|
32
|
-
*
|
|
33
|
-
* **Testing shows:**
|
|
34
|
-
* - ✅ `shell: false` + absolute path from `which.sync('node')` → Works correctly
|
|
35
|
-
* - ❌ `shell: true` + args array → Exit codes ignored, security warning
|
|
36
|
-
*
|
|
37
|
-
* **Root Cause of Previous ENOENT Issues:** Likely resolved in newer Node.js versions.
|
|
38
|
-
* Current testing (Node 20+) shows shell:false works correctly with absolute path.
|
|
39
|
-
*
|
|
40
|
-
* ## Why This Is Secure
|
|
41
|
-
*
|
|
42
|
-
* 1. **Minimal Shell Usage:** Shell only used for .cmd/.bat/.ps1 files (required)
|
|
43
|
-
* 2. **Path Validation:** Command paths resolved via `which.sync()` before execution
|
|
44
|
-
* 3. **Array-Based Arguments:** Arguments passed as array, preventing injection
|
|
45
|
-
* 4. **Controlled Environment:** Commands from trusted configuration, not user input
|
|
46
|
-
* 5. **No String Interpolation:** Never concatenate user input into command strings
|
|
47
|
-
*
|
|
48
|
-
* ## References
|
|
49
|
-
*
|
|
50
|
-
* - Node.js deprecation: https://nodejs.org/api/deprecations.html#DEP0190
|
|
51
|
-
* - Security tests: `packages/utils/test/safe-exec.test.ts`
|
|
52
|
-
* - Windows fix: PR #94 (fix/windows-shell-independence-v2)
|
|
53
|
-
*
|
|
54
|
-
* @param commandPath - Resolved absolute path to command
|
|
55
|
-
* @returns true if shell should be used, false otherwise
|
|
56
|
-
*/
|
|
57
|
-
function shouldUseShell(commandPath) {
|
|
58
|
-
if (process.platform !== 'win32') {
|
|
59
|
-
return false;
|
|
60
|
-
}
|
|
61
|
-
// Node.js deprecation warning (DEP0190): Passing args with shell:true leads to incorrect
|
|
62
|
-
// command execution and security vulnerabilities. Testing shows shell:false works correctly
|
|
63
|
-
// with absolute path from which.sync('node') on Windows.
|
|
64
|
-
// Previous ENOENT issues may have been resolved in newer Node.js versions.
|
|
65
|
-
//
|
|
66
|
-
// REMOVED: if (command === 'node') return true;
|
|
67
|
-
// Reason: shell:true causes exit codes to be ignored (always returns 0)
|
|
68
|
-
// Fix: Use shell:false with absolute path - works correctly
|
|
69
|
-
// Windows shell scripts require shell by design (case-insensitive check)
|
|
70
|
-
const lowerPath = commandPath.toLowerCase();
|
|
71
|
-
return lowerPath.endsWith('.cmd') || lowerPath.endsWith('.bat') || lowerPath.endsWith('.ps1');
|
|
72
|
-
}
|
|
73
|
-
/**
|
|
74
|
-
* Quote a single argument for a Windows `cmd.exe` command line.
|
|
75
|
-
*
|
|
76
|
-
* Rules:
|
|
77
|
-
* - An arg that's empty, or contains whitespace, quotes, or any of `& | < > ^ ( ) % !`,
|
|
78
|
-
* gets wrapped in double quotes.
|
|
79
|
-
* - Embedded double quotes become `""` (cmd.exe's escape form inside quoted strings).
|
|
80
|
-
*
|
|
81
|
-
* This is narrow on purpose: it's only used when we're forced to assemble a shell string
|
|
82
|
-
* for `.cmd` / `.bat` wrappers on Windows (DEP0190 path). Callers still control what's
|
|
83
|
-
* in `args`, but this keeps a stray space, glob metachar, or paren from getting
|
|
84
|
-
* re-interpreted by the shell.
|
|
85
|
-
*/
|
|
86
|
-
function windowsShellQuote(arg) {
|
|
87
|
-
if (arg === '' || /["\s&|<>^()%!]/.test(arg)) {
|
|
88
|
-
return `"${arg.replaceAll('"', '""')}"`;
|
|
89
|
-
}
|
|
90
|
-
return arg;
|
|
91
|
-
}
|
|
92
19
|
/**
|
|
93
20
|
* Resolve command, build spawn options, and execute via spawnSync.
|
|
94
21
|
*
|
|
@@ -114,7 +41,7 @@ function resolveAndSpawn(command, args, options) {
|
|
|
114
41
|
if (!useShell) {
|
|
115
42
|
return spawnSync(execCommand, args, spawnOptions);
|
|
116
43
|
}
|
|
117
|
-
const shellLine =
|
|
44
|
+
const shellLine = buildWindowsShellLine(execCommand, args);
|
|
118
45
|
// eslint-disable-next-line sonarjs/os-command -- Windows DEP0190 workaround: command resolved via which.sync(); args are per-arg shell-quoted via windowsShellQuote()
|
|
119
46
|
return spawnSync(shellLine, { ...spawnOptions, shell: true });
|
|
120
47
|
}
|
package/dist/safe-exec.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"safe-exec.js","sourceRoot":"","sources":["../src/safe-exec.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAyB,MAAM,oBAAoB,CAAC;AAEtE,OAAO,KAAK,MAAM,OAAO,CAAC;
|
|
1
|
+
{"version":3,"file":"safe-exec.js","sourceRoot":"","sources":["../src/safe-exec.ts"],"names":[],"mappings":"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"}
|
|
@@ -41,7 +41,7 @@ export declare function killProcessTree(child: {
|
|
|
41
41
|
export declare function killAllActiveClaudeChildren(): void;
|
|
42
42
|
export interface SpawnHeadlessOptions extends ClaudeSpawnArgs {
|
|
43
43
|
/**
|
|
44
|
-
* The
|
|
44
|
+
* The executor prompt, held IN MEMORY and streamed to the child's stdin
|
|
45
45
|
* (claude 2.x reads the `-p` prompt from stdin; there is no `--prompt-file`
|
|
46
46
|
* flag). Kept off argv so the prompt is never inlined (Windows cmd-quoting
|
|
47
47
|
* safety) AND, deliberately, off disk: the harness stamps a per-run integrity
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"spawn-claude.d.ts","sourceRoot":"","sources":["../../src/skill-test/spawn-claude.ts"],"names":[],"mappings":"AAKA,MAAM,WAAW,eAAe;IAC9B,UAAU,EAAE,MAAM,EAAE,CAAC;IACrB,UAAU,EAAE,MAAM,CAAC;IACnB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AAED;;;;;;GAMG;AACH,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,eAAe,GAAG,MAAM,EAAE,CAmBlE;AAED,MAAM,WAAW,WAAW;IAC1B,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,OAAO,CAAC;IAClB,OAAO,EAAE,OAAO,CAAC;CAClB;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,eAAe,CAAC,KAAK,EAAE;IAAE,GAAG,CAAC,EAAE,MAAM,GAAG,SAAS,CAAA;CAAE,GAAG,IAAI,CAczE;AAoBD;;;;;GAKG;AACH,wBAAgB,2BAA2B,IAAI,IAAI,CAKlD;AAED,MAAM,WAAW,oBAAqB,SAAQ,eAAe;IAC3D;;;;;;;;;OASG;IACH,MAAM,EAAE,MAAM,CAAC;IACf,GAAG,EAAE,MAAM,CAAC;IACZ,GAAG,EAAE,MAAM,CAAC,UAAU,CAAC;IACvB,SAAS,EAAE,MAAM,CAAC;IAClB,yFAAyF;IACzF,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,QAAQ,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IACnC,QAAQ,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IACnC;;;;;OAKG;IACH,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED;;;;;;GAMG;AACH,wBAAsB,mBAAmB,CAAC,IAAI,EAAE,oBAAoB,GAAG,OAAO,CAAC,WAAW,CAAC,
|
|
1
|
+
{"version":3,"file":"spawn-claude.d.ts","sourceRoot":"","sources":["../../src/skill-test/spawn-claude.ts"],"names":[],"mappings":"AAKA,MAAM,WAAW,eAAe;IAC9B,UAAU,EAAE,MAAM,EAAE,CAAC;IACrB,UAAU,EAAE,MAAM,CAAC;IACnB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AAED;;;;;;GAMG;AACH,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,eAAe,GAAG,MAAM,EAAE,CAmBlE;AAED,MAAM,WAAW,WAAW;IAC1B,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,OAAO,CAAC;IAClB,OAAO,EAAE,OAAO,CAAC;CAClB;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,eAAe,CAAC,KAAK,EAAE;IAAE,GAAG,CAAC,EAAE,MAAM,GAAG,SAAS,CAAA;CAAE,GAAG,IAAI,CAczE;AAoBD;;;;;GAKG;AACH,wBAAgB,2BAA2B,IAAI,IAAI,CAKlD;AAED,MAAM,WAAW,oBAAqB,SAAQ,eAAe;IAC3D;;;;;;;;;OASG;IACH,MAAM,EAAE,MAAM,CAAC;IACf,GAAG,EAAE,MAAM,CAAC;IACZ,GAAG,EAAE,MAAM,CAAC,UAAU,CAAC;IACvB,SAAS,EAAE,MAAM,CAAC;IAClB,yFAAyF;IACzF,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,QAAQ,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IACnC,QAAQ,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IACnC;;;;;OAKG;IACH,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED;;;;;;GAMG;AACH,wBAAsB,mBAAmB,CAAC,IAAI,EAAE,oBAAoB,GAAG,OAAO,CAAC,WAAW,CAAC,CAwF1F"}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { spawnSync } from 'node:child_process';
|
|
2
2
|
import { Readable } from 'node:stream';
|
|
3
|
-
import
|
|
3
|
+
import { spawnHardened } from '../spawn-hardened.js';
|
|
4
4
|
/**
|
|
5
5
|
* Pure assembly of the headless `claude -p` argv (spec §9). The prompt is never
|
|
6
6
|
* inlined as an argv string (Windows cmd-quoting safety) and claude 2.x has no
|
|
@@ -98,11 +98,16 @@ export function killAllActiveClaudeChildren() {
|
|
|
98
98
|
*/
|
|
99
99
|
export async function spawnHeadlessClaude(opts) {
|
|
100
100
|
// opts.binPath is an internal test seam (see SpawnHeadlessOptions); production
|
|
101
|
-
// callers leave it unset and `claude` is resolved on PATH
|
|
102
|
-
|
|
101
|
+
// callers leave it unset and the bare `claude` name is resolved on PATH by
|
|
102
|
+
// spawnHardened. spawnHardened is REQUIRED here (not a bare spawn): on Windows
|
|
103
|
+
// `claude` resolves to a `claude.cmd` shim, and since CVE-2024-27980 a bare
|
|
104
|
+
// spawn of a `.cmd` throws EINVAL — spawnHardened routes it through the shell.
|
|
103
105
|
const args = assembleClaudeArgs(opts);
|
|
104
106
|
return await new Promise((resolve, reject) => {
|
|
105
|
-
|
|
107
|
+
// `stdio: ['pipe','pipe','pipe']` guarantees all three streams are non-null;
|
|
108
|
+
// spawnHardened is typed to return the generic (nullable-stream) ChildProcess,
|
|
109
|
+
// so narrow to the piped shape here (mirrors Node's own stdio-tuple overload).
|
|
110
|
+
const child = spawnHardened(opts.binPath ?? 'claude', args, {
|
|
106
111
|
cwd: opts.cwd,
|
|
107
112
|
env: opts.env,
|
|
108
113
|
stdio: ['pipe', 'pipe', 'pipe'],
|
|
@@ -167,7 +172,11 @@ export async function spawnHeadlessClaude(opts) {
|
|
|
167
172
|
promptStream.pipe(child.stdin);
|
|
168
173
|
child.stdout.on('data', (d) => { opts.onStdout?.(d.toString()); });
|
|
169
174
|
child.stderr.on('data', (d) => { opts.onStderr?.(d.toString()); });
|
|
170
|
-
|
|
175
|
+
// Reap on 'error' too: clearAllTimers() unregisters the child, so if 'error'
|
|
176
|
+
// ever fires while the process is actually alive we would otherwise drop it
|
|
177
|
+
// from the reap set without killing it (the exact orphan this file guards
|
|
178
|
+
// against). killProcessTree no-ops on an undefined pid (never-spawned case).
|
|
179
|
+
child.on('error', err => { clearAllTimers(); killProcessTree(child); reject(err); });
|
|
171
180
|
child.on('close', code => { clearAllTimers(); resolve({ status: code ?? -1, timedOut, stalled }); });
|
|
172
181
|
});
|
|
173
182
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"spawn-claude.js","sourceRoot":"","sources":["../../src/skill-test/spawn-claude.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,
|
|
1
|
+
{"version":3,"file":"spawn-claude.js","sourceRoot":"","sources":["../../src/skill-test/spawn-claude.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { type ChildProcess, type SpawnOptions } from 'node:child_process';
|
|
2
|
+
/**
|
|
3
|
+
* Cross-platform hardened async `spawn`, returning a live {@link ChildProcess} with
|
|
4
|
+
* streaming stdio intact (unlike the sync, buffered {@link ./safe-exec.ts} path).
|
|
5
|
+
*
|
|
6
|
+
* The one thing it does that a bare `child_process.spawn` does not: correctly launch
|
|
7
|
+
* Windows `.cmd`/`.bat`/`.ps1` wrappers. Since the CVE-2024-27980 fix, `spawn` throws
|
|
8
|
+
* `EINVAL` on such a wrapper unless `shell: true` — and npm-installed CLIs (`claude`,
|
|
9
|
+
* `npm`, …) resolve to exactly that on Windows. When shell mode is required we join the
|
|
10
|
+
* command + args into a SINGLE string with per-arg {@link windowsShellQuote} (Node's
|
|
11
|
+
* DEP0190 rejects `shell: true` with a separate args array); everywhere else we spawn
|
|
12
|
+
* `shell: false` with the resolved absolute path, keeping the no-injection guarantee.
|
|
13
|
+
*
|
|
14
|
+
* POSIX is a pure passthrough to `child_process.spawn` (`shouldUseShell` is always
|
|
15
|
+
* false off-Windows), so process-group / `detached` semantics are unchanged there.
|
|
16
|
+
*
|
|
17
|
+
* @param command - Bare command name (PATH-resolved) or an explicit path (used as-is).
|
|
18
|
+
* @param args - Arguments, passed as an array (never string-concatenated on POSIX).
|
|
19
|
+
* @param options - Standard `SpawnOptions` (cwd, env, stdio, detached, …), passed through.
|
|
20
|
+
*/
|
|
21
|
+
export declare function spawnHardened(command: string, args: string[], options?: SpawnOptions): ChildProcess;
|
|
22
|
+
//# sourceMappingURL=spawn-hardened.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"spawn-hardened.d.ts","sourceRoot":"","sources":["../src/spawn-hardened.ts"],"names":[],"mappings":"AAAA,OAAO,EAAS,KAAK,YAAY,EAAE,KAAK,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAmBjF;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,aAAa,CAC3B,OAAO,EAAE,MAAM,EACf,IAAI,EAAE,MAAM,EAAE,EACd,OAAO,GAAE,YAAiB,GACzB,YAAY,CAad"}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { spawn } from 'node:child_process';
|
|
2
|
+
import which from 'which';
|
|
3
|
+
import { isAbsoluteAnyPlatform } from './path-utils.js';
|
|
4
|
+
import { buildWindowsShellLine, shouldUseShell, windowsShellQuote } from './windows-shell.js';
|
|
5
|
+
/**
|
|
6
|
+
* A `command` is treated as an explicit path (used verbatim, never PATH-resolved)
|
|
7
|
+
* when it is absolute or contains a path separator. A bare name (`claude`, `npm`,
|
|
8
|
+
* `git`) is resolved on PATH via `which.sync`. This mirrors — and preserves — the
|
|
9
|
+
* behaviour callers relied on before hardening: an explicit `binPath` is spawned
|
|
10
|
+
* as-given (so a nonexistent path still surfaces as an async `'error'` event, not a
|
|
11
|
+
* synchronous `which` throw), while a bare command is looked up.
|
|
12
|
+
*/
|
|
13
|
+
function isPathLike(command) {
|
|
14
|
+
return isAbsoluteAnyPlatform(command) || command.includes('/') || command.includes('\\');
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Cross-platform hardened async `spawn`, returning a live {@link ChildProcess} with
|
|
18
|
+
* streaming stdio intact (unlike the sync, buffered {@link ./safe-exec.ts} path).
|
|
19
|
+
*
|
|
20
|
+
* The one thing it does that a bare `child_process.spawn` does not: correctly launch
|
|
21
|
+
* Windows `.cmd`/`.bat`/`.ps1` wrappers. Since the CVE-2024-27980 fix, `spawn` throws
|
|
22
|
+
* `EINVAL` on such a wrapper unless `shell: true` — and npm-installed CLIs (`claude`,
|
|
23
|
+
* `npm`, …) resolve to exactly that on Windows. When shell mode is required we join the
|
|
24
|
+
* command + args into a SINGLE string with per-arg {@link windowsShellQuote} (Node's
|
|
25
|
+
* DEP0190 rejects `shell: true` with a separate args array); everywhere else we spawn
|
|
26
|
+
* `shell: false` with the resolved absolute path, keeping the no-injection guarantee.
|
|
27
|
+
*
|
|
28
|
+
* POSIX is a pure passthrough to `child_process.spawn` (`shouldUseShell` is always
|
|
29
|
+
* false off-Windows), so process-group / `detached` semantics are unchanged there.
|
|
30
|
+
*
|
|
31
|
+
* @param command - Bare command name (PATH-resolved) or an explicit path (used as-is).
|
|
32
|
+
* @param args - Arguments, passed as an array (never string-concatenated on POSIX).
|
|
33
|
+
* @param options - Standard `SpawnOptions` (cwd, env, stdio, detached, …), passed through.
|
|
34
|
+
*/
|
|
35
|
+
export function spawnHardened(command, args, options = {}) {
|
|
36
|
+
const resolved = isPathLike(command) ? command : which.sync(command);
|
|
37
|
+
if (!shouldUseShell(resolved)) {
|
|
38
|
+
return spawn(resolved, args, { ...options, shell: false });
|
|
39
|
+
}
|
|
40
|
+
// Windows shell path (.cmd/.bat/.ps1). Use the bare command for a PATH lookup (cmd.exe
|
|
41
|
+
// re-resolves it via PATHEXT); use the quoted resolved path when given an explicit path.
|
|
42
|
+
const shellCommand = isPathLike(command) ? windowsShellQuote(resolved) : command;
|
|
43
|
+
const shellLine = buildWindowsShellLine(shellCommand, args);
|
|
44
|
+
// eslint-disable-next-line sonarjs/os-command -- Windows DEP0190 workaround: command is a PATH-resolved bare name or an explicit path; args are per-arg shell-quoted via windowsShellQuote()
|
|
45
|
+
return spawn(shellLine, { ...options, shell: true });
|
|
46
|
+
}
|
|
47
|
+
//# sourceMappingURL=spawn-hardened.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"spawn-hardened.js","sourceRoot":"","sources":["../src/spawn-hardened.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAwC,MAAM,oBAAoB,CAAC;AAEjF,OAAO,KAAK,MAAM,OAAO,CAAC;AAE1B,OAAO,EAAE,qBAAqB,EAAE,MAAM,iBAAiB,CAAC;AACxD,OAAO,EAAE,qBAAqB,EAAE,cAAc,EAAE,iBAAiB,EAAE,MAAM,oBAAoB,CAAC;AAE9F;;;;;;;GAOG;AACH,SAAS,UAAU,CAAC,OAAe;IACjC,OAAO,qBAAqB,CAAC,OAAO,CAAC,IAAI,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAC,IAAI,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC;AAC3F,CAAC;AAED;;;;;;;;;;;;;;;;;;GAkBG;AACH,MAAM,UAAU,aAAa,CAC3B,OAAe,EACf,IAAc,EACd,UAAwB,EAAE;IAE1B,MAAM,QAAQ,GAAG,UAAU,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;IAErE,IAAI,CAAC,cAAc,CAAC,QAAQ,CAAC,EAAE,CAAC;QAC9B,OAAO,KAAK,CAAC,QAAQ,EAAE,IAAI,EAAE,EAAE,GAAG,OAAO,EAAE,KAAK,EAAE,KAAK,EAAE,CAAC,CAAC;IAC7D,CAAC;IAED,uFAAuF;IACvF,yFAAyF;IACzF,MAAM,YAAY,GAAG,UAAU,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,iBAAiB,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC;IACjF,MAAM,SAAS,GAAG,qBAAqB,CAAC,YAAY,EAAE,IAAI,CAAC,CAAC;IAC5D,6LAA6L;IAC7L,OAAO,KAAK,CAAC,SAAS,EAAE,EAAE,GAAG,OAAO,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC;AACvD,CAAC"}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Windows shell-invocation helpers, shared by every spawn wrapper in this package
|
|
3
|
+
* ({@link ./safe-exec.ts} for the sync `spawnSync` path, {@link ./spawn-hardened.ts}
|
|
4
|
+
* for the async streaming `spawn` path). Extracted so there is exactly ONE Windows
|
|
5
|
+
* `.cmd`/`.bat`/`.ps1` convention across the toolkit — never two subtly-different
|
|
6
|
+
* copies.
|
|
7
|
+
*
|
|
8
|
+
* Background: since the CVE-2024-27980 fix, Node's `child_process.spawn`/`spawnSync`
|
|
9
|
+
* throw `EINVAL` when asked to launch a `.cmd`/`.bat` wrapper without `shell: true`.
|
|
10
|
+
* npm-installed CLIs (`claude`, `npm`, `git` shims, …) resolve to exactly such a
|
|
11
|
+
* wrapper on Windows, so any spawn that omits this handling crashes on Windows only.
|
|
12
|
+
*/
|
|
13
|
+
/**
|
|
14
|
+
* Determine if shell should be used for command execution on Windows.
|
|
15
|
+
*
|
|
16
|
+
* ## Security Context
|
|
17
|
+
*
|
|
18
|
+
* This package's primary security model is `shell: false` to prevent command injection.
|
|
19
|
+
* Windows requires `shell: true` only for shell scripts (.cmd/.bat/.ps1) which require
|
|
20
|
+
* a shell interpreter by design (not executable binaries).
|
|
21
|
+
*
|
|
22
|
+
* ## Node.js on Windows - NO SHELL REQUIRED
|
|
23
|
+
*
|
|
24
|
+
* **Previous behavior (REMOVED):** Used shell:true for 'node' command
|
|
25
|
+
* **Problem discovered:** Node.js DEP0190 deprecation warning - passing args array with
|
|
26
|
+
* shell:true leads to incorrect command execution. Exit codes are ignored (always returns 0).
|
|
27
|
+
*
|
|
28
|
+
* **Testing shows:**
|
|
29
|
+
* - ✅ `shell: false` + absolute path from `which.sync('node')` → Works correctly
|
|
30
|
+
* - ❌ `shell: true` + args array → Exit codes ignored, security warning
|
|
31
|
+
*
|
|
32
|
+
* **Root Cause of Previous ENOENT Issues:** Likely resolved in newer Node.js versions.
|
|
33
|
+
* Current testing (Node 20+) shows shell:false works correctly with absolute path.
|
|
34
|
+
*
|
|
35
|
+
* ## Why This Is Secure
|
|
36
|
+
*
|
|
37
|
+
* 1. **Minimal Shell Usage:** Shell only used for .cmd/.bat/.ps1 files (required)
|
|
38
|
+
* 2. **Path Validation:** Command paths resolved via `which.sync()` before execution
|
|
39
|
+
* 3. **Array-Based Arguments:** Arguments passed as array, preventing injection
|
|
40
|
+
* 4. **Controlled Environment:** Commands from trusted configuration, not user input
|
|
41
|
+
* 5. **No String Interpolation:** Never concatenate user input into command strings
|
|
42
|
+
*
|
|
43
|
+
* ## References
|
|
44
|
+
*
|
|
45
|
+
* - Node.js deprecation: https://nodejs.org/api/deprecations.html#DEP0190
|
|
46
|
+
* - Security tests: `packages/utils/test/safe-exec.test.ts`
|
|
47
|
+
* - Windows fix: PR #94 (fix/windows-shell-independence-v2)
|
|
48
|
+
*
|
|
49
|
+
* @param commandPath - Resolved absolute path to command
|
|
50
|
+
* @returns true if shell should be used, false otherwise
|
|
51
|
+
*/
|
|
52
|
+
export declare function shouldUseShell(commandPath: string): boolean;
|
|
53
|
+
/**
|
|
54
|
+
* Quote a single argument for a Windows `cmd.exe` command line.
|
|
55
|
+
*
|
|
56
|
+
* Rules:
|
|
57
|
+
* - An arg that's empty, or contains whitespace, quotes, or any of `& | < > ^ ( ) % !`,
|
|
58
|
+
* gets wrapped in double quotes.
|
|
59
|
+
* - Embedded double quotes become `""` (cmd.exe's escape form inside quoted strings).
|
|
60
|
+
*
|
|
61
|
+
* This is narrow on purpose: it's only used when we're forced to assemble a shell string
|
|
62
|
+
* for `.cmd` / `.bat` wrappers on Windows (DEP0190 path). Callers still control what's
|
|
63
|
+
* in `args`, but this keeps a stray space, glob metachar, or paren from getting
|
|
64
|
+
* re-interpreted by the shell.
|
|
65
|
+
*/
|
|
66
|
+
export declare function windowsShellQuote(arg: string): string;
|
|
67
|
+
/**
|
|
68
|
+
* Build the single `cmd.exe` command line used when a Windows shim forces shell mode
|
|
69
|
+
* (Node's DEP0190 rejects `shell: true` with a separate args array, so command + args
|
|
70
|
+
* must be one string). `commandToken` is the already-resolved command — a bare PATH
|
|
71
|
+
* name that `cmd.exe` re-resolves via PATHEXT, or a pre-quoted explicit path — and each
|
|
72
|
+
* arg is quoted via {@link windowsShellQuote} so shell metacharacters can't re-interpret.
|
|
73
|
+
*/
|
|
74
|
+
export declare function buildWindowsShellLine(commandToken: string, args: string[]): string;
|
|
75
|
+
//# sourceMappingURL=windows-shell.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"windows-shell.d.ts","sourceRoot":"","sources":["../src/windows-shell.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAsCG;AACH,wBAAgB,cAAc,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAiB3D;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAKrD;AAED;;;;;;GAMG;AACH,wBAAgB,qBAAqB,CAAC,YAAY,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,GAAG,MAAM,CAElF"}
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Windows shell-invocation helpers, shared by every spawn wrapper in this package
|
|
3
|
+
* ({@link ./safe-exec.ts} for the sync `spawnSync` path, {@link ./spawn-hardened.ts}
|
|
4
|
+
* for the async streaming `spawn` path). Extracted so there is exactly ONE Windows
|
|
5
|
+
* `.cmd`/`.bat`/`.ps1` convention across the toolkit — never two subtly-different
|
|
6
|
+
* copies.
|
|
7
|
+
*
|
|
8
|
+
* Background: since the CVE-2024-27980 fix, Node's `child_process.spawn`/`spawnSync`
|
|
9
|
+
* throw `EINVAL` when asked to launch a `.cmd`/`.bat` wrapper without `shell: true`.
|
|
10
|
+
* npm-installed CLIs (`claude`, `npm`, `git` shims, …) resolve to exactly such a
|
|
11
|
+
* wrapper on Windows, so any spawn that omits this handling crashes on Windows only.
|
|
12
|
+
*/
|
|
13
|
+
/**
|
|
14
|
+
* Determine if shell should be used for command execution on Windows.
|
|
15
|
+
*
|
|
16
|
+
* ## Security Context
|
|
17
|
+
*
|
|
18
|
+
* This package's primary security model is `shell: false` to prevent command injection.
|
|
19
|
+
* Windows requires `shell: true` only for shell scripts (.cmd/.bat/.ps1) which require
|
|
20
|
+
* a shell interpreter by design (not executable binaries).
|
|
21
|
+
*
|
|
22
|
+
* ## Node.js on Windows - NO SHELL REQUIRED
|
|
23
|
+
*
|
|
24
|
+
* **Previous behavior (REMOVED):** Used shell:true for 'node' command
|
|
25
|
+
* **Problem discovered:** Node.js DEP0190 deprecation warning - passing args array with
|
|
26
|
+
* shell:true leads to incorrect command execution. Exit codes are ignored (always returns 0).
|
|
27
|
+
*
|
|
28
|
+
* **Testing shows:**
|
|
29
|
+
* - ✅ `shell: false` + absolute path from `which.sync('node')` → Works correctly
|
|
30
|
+
* - ❌ `shell: true` + args array → Exit codes ignored, security warning
|
|
31
|
+
*
|
|
32
|
+
* **Root Cause of Previous ENOENT Issues:** Likely resolved in newer Node.js versions.
|
|
33
|
+
* Current testing (Node 20+) shows shell:false works correctly with absolute path.
|
|
34
|
+
*
|
|
35
|
+
* ## Why This Is Secure
|
|
36
|
+
*
|
|
37
|
+
* 1. **Minimal Shell Usage:** Shell only used for .cmd/.bat/.ps1 files (required)
|
|
38
|
+
* 2. **Path Validation:** Command paths resolved via `which.sync()` before execution
|
|
39
|
+
* 3. **Array-Based Arguments:** Arguments passed as array, preventing injection
|
|
40
|
+
* 4. **Controlled Environment:** Commands from trusted configuration, not user input
|
|
41
|
+
* 5. **No String Interpolation:** Never concatenate user input into command strings
|
|
42
|
+
*
|
|
43
|
+
* ## References
|
|
44
|
+
*
|
|
45
|
+
* - Node.js deprecation: https://nodejs.org/api/deprecations.html#DEP0190
|
|
46
|
+
* - Security tests: `packages/utils/test/safe-exec.test.ts`
|
|
47
|
+
* - Windows fix: PR #94 (fix/windows-shell-independence-v2)
|
|
48
|
+
*
|
|
49
|
+
* @param commandPath - Resolved absolute path to command
|
|
50
|
+
* @returns true if shell should be used, false otherwise
|
|
51
|
+
*/
|
|
52
|
+
export function shouldUseShell(commandPath) {
|
|
53
|
+
if (process.platform !== 'win32') {
|
|
54
|
+
return false;
|
|
55
|
+
}
|
|
56
|
+
// Node.js deprecation warning (DEP0190): Passing args with shell:true leads to incorrect
|
|
57
|
+
// command execution and security vulnerabilities. Testing shows shell:false works correctly
|
|
58
|
+
// with absolute path from which.sync('node') on Windows.
|
|
59
|
+
// Previous ENOENT issues may have been resolved in newer Node.js versions.
|
|
60
|
+
//
|
|
61
|
+
// REMOVED: if (command === 'node') return true;
|
|
62
|
+
// Reason: shell:true causes exit codes to be ignored (always returns 0)
|
|
63
|
+
// Fix: Use shell:false with absolute path - works correctly
|
|
64
|
+
// Windows shell scripts require shell by design (case-insensitive check)
|
|
65
|
+
const lowerPath = commandPath.toLowerCase();
|
|
66
|
+
return lowerPath.endsWith('.cmd') || lowerPath.endsWith('.bat') || lowerPath.endsWith('.ps1');
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Quote a single argument for a Windows `cmd.exe` command line.
|
|
70
|
+
*
|
|
71
|
+
* Rules:
|
|
72
|
+
* - An arg that's empty, or contains whitespace, quotes, or any of `& | < > ^ ( ) % !`,
|
|
73
|
+
* gets wrapped in double quotes.
|
|
74
|
+
* - Embedded double quotes become `""` (cmd.exe's escape form inside quoted strings).
|
|
75
|
+
*
|
|
76
|
+
* This is narrow on purpose: it's only used when we're forced to assemble a shell string
|
|
77
|
+
* for `.cmd` / `.bat` wrappers on Windows (DEP0190 path). Callers still control what's
|
|
78
|
+
* in `args`, but this keeps a stray space, glob metachar, or paren from getting
|
|
79
|
+
* re-interpreted by the shell.
|
|
80
|
+
*/
|
|
81
|
+
export function windowsShellQuote(arg) {
|
|
82
|
+
if (arg === '' || /["\s&|<>^()%!]/.test(arg)) {
|
|
83
|
+
return `"${arg.replaceAll('"', '""')}"`;
|
|
84
|
+
}
|
|
85
|
+
return arg;
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* Build the single `cmd.exe` command line used when a Windows shim forces shell mode
|
|
89
|
+
* (Node's DEP0190 rejects `shell: true` with a separate args array, so command + args
|
|
90
|
+
* must be one string). `commandToken` is the already-resolved command — a bare PATH
|
|
91
|
+
* name that `cmd.exe` re-resolves via PATHEXT, or a pre-quoted explicit path — and each
|
|
92
|
+
* arg is quoted via {@link windowsShellQuote} so shell metacharacters can't re-interpret.
|
|
93
|
+
*/
|
|
94
|
+
export function buildWindowsShellLine(commandToken, args) {
|
|
95
|
+
return `${commandToken} ${args.map(windowsShellQuote).join(' ')}`;
|
|
96
|
+
}
|
|
97
|
+
//# sourceMappingURL=windows-shell.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"windows-shell.js","sourceRoot":"","sources":["../src/windows-shell.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAsCG;AACH,MAAM,UAAU,cAAc,CAAC,WAAmB;IAChD,IAAI,OAAO,CAAC,QAAQ,KAAK,OAAO,EAAE,CAAC;QACjC,OAAO,KAAK,CAAC;IACf,CAAC;IAED,yFAAyF;IACzF,4FAA4F;IAC5F,yDAAyD;IACzD,2EAA2E;IAC3E,EAAE;IACF,gDAAgD;IAChD,wEAAwE;IACxE,4DAA4D;IAE5D,yEAAyE;IACzE,MAAM,SAAS,GAAG,WAAW,CAAC,WAAW,EAAE,CAAC;IAC5C,OAAO,SAAS,CAAC,QAAQ,CAAC,MAAM,CAAC,IAAI,SAAS,CAAC,QAAQ,CAAC,MAAM,CAAC,IAAI,SAAS,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC;AAChG,CAAC;AAED;;;;;;;;;;;;GAYG;AACH,MAAM,UAAU,iBAAiB,CAAC,GAAW;IAC3C,IAAI,GAAG,KAAK,EAAE,IAAI,gBAAgB,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC;QAC7C,OAAO,IAAI,GAAG,CAAC,UAAU,CAAC,GAAG,EAAE,IAAI,CAAC,GAAG,CAAC;IAC1C,CAAC;IACD,OAAO,GAAG,CAAC;AACb,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,qBAAqB,CAAC,YAAoB,EAAE,IAAc;IACxE,OAAO,GAAG,YAAY,IAAI,IAAI,CAAC,GAAG,CAAC,iBAAiB,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC;AACpE,CAAC"}
|