@vibe-agent-toolkit/utils 0.1.40 → 0.1.41-rc.2
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/index.d.ts +2 -1
- package/dist/skill-test/index.d.ts.map +1 -1
- package/dist/skill-test/index.js +2 -1
- package/dist/skill-test/index.js.map +1 -1
- package/dist/skill-test/spawn-claude.d.ts +8 -1
- package/dist/skill-test/spawn-claude.d.ts.map +1 -1
- package/dist/skill-test/spawn-claude.js +51 -6
- package/dist/skill-test/spawn-claude.js.map +1 -1
- package/dist/skill-test/transcript.d.ts +49 -0
- package/dist/skill-test/transcript.d.ts.map +1 -0
- package/dist/skill-test/transcript.js +148 -0
- package/dist/skill-test/transcript.js.map +1 -0
- 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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"}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
export { applyDeclaredEnv, buildForwardedEnv, formatForwardedEnvLine, protectedEnvNames, type DeclaredEnvInput, type DeclaredEnvResult, type ForwardEnvOptions, } from './env-scrub.js';
|
|
2
2
|
export { AuthPreflightError, probeAuthStatus, resolveAuth, type AuthMechanism, type AuthMode, type AuthStatusProbe, type AuthStatusResult, type ResolveAuthOptions, type ResolvedAuth, } from './auth-resolver.js';
|
|
3
|
-
export { assembleClaudeArgs, spawnHeadlessClaude, type ClaudeSpawnArgs, type SpawnHeadlessOptions, type SpawnResult, } from './spawn-claude.js';
|
|
3
|
+
export { assembleClaudeArgs, killAllActiveClaudeChildren, spawnHeadlessClaude, type ClaudeSpawnArgs, type SpawnHeadlessOptions, type SpawnResult, } from './spawn-claude.js';
|
|
4
|
+
export { detectInvocationFromTranscript, parseStreamJsonTranscript, type ParsedTranscript, } from './transcript.js';
|
|
4
5
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/skill-test/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,gBAAgB,EAChB,iBAAiB,EACjB,sBAAsB,EACtB,iBAAiB,EACjB,KAAK,gBAAgB,EACrB,KAAK,iBAAiB,EACtB,KAAK,iBAAiB,GACvB,MAAM,gBAAgB,CAAC;AACxB,OAAO,EACL,kBAAkB,EAClB,eAAe,EACf,WAAW,EACX,KAAK,aAAa,EAClB,KAAK,QAAQ,EACb,KAAK,eAAe,EACpB,KAAK,gBAAgB,EACrB,KAAK,kBAAkB,EACvB,KAAK,YAAY,GAClB,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EACL,kBAAkB,EAClB,mBAAmB,EACnB,KAAK,eAAe,EACpB,KAAK,oBAAoB,EACzB,KAAK,WAAW,GACjB,MAAM,mBAAmB,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/skill-test/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,gBAAgB,EAChB,iBAAiB,EACjB,sBAAsB,EACtB,iBAAiB,EACjB,KAAK,gBAAgB,EACrB,KAAK,iBAAiB,EACtB,KAAK,iBAAiB,GACvB,MAAM,gBAAgB,CAAC;AACxB,OAAO,EACL,kBAAkB,EAClB,eAAe,EACf,WAAW,EACX,KAAK,aAAa,EAClB,KAAK,QAAQ,EACb,KAAK,eAAe,EACpB,KAAK,gBAAgB,EACrB,KAAK,kBAAkB,EACvB,KAAK,YAAY,GAClB,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EACL,kBAAkB,EAClB,2BAA2B,EAC3B,mBAAmB,EACnB,KAAK,eAAe,EACpB,KAAK,oBAAoB,EACzB,KAAK,WAAW,GACjB,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EACL,8BAA8B,EAC9B,yBAAyB,EACzB,KAAK,gBAAgB,GACtB,MAAM,iBAAiB,CAAC"}
|
package/dist/skill-test/index.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
export { applyDeclaredEnv, buildForwardedEnv, formatForwardedEnvLine, protectedEnvNames, } from './env-scrub.js';
|
|
2
2
|
export { AuthPreflightError, probeAuthStatus, resolveAuth, } from './auth-resolver.js';
|
|
3
|
-
export { assembleClaudeArgs, spawnHeadlessClaude, } from './spawn-claude.js';
|
|
3
|
+
export { assembleClaudeArgs, killAllActiveClaudeChildren, spawnHeadlessClaude, } from './spawn-claude.js';
|
|
4
|
+
export { detectInvocationFromTranscript, parseStreamJsonTranscript, } from './transcript.js';
|
|
4
5
|
//# sourceMappingURL=index.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/skill-test/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,gBAAgB,EAChB,iBAAiB,EACjB,sBAAsB,EACtB,iBAAiB,GAIlB,MAAM,gBAAgB,CAAC;AACxB,OAAO,EACL,kBAAkB,EAClB,eAAe,EACf,WAAW,GAOZ,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EACL,kBAAkB,EAClB,mBAAmB,GAIpB,MAAM,mBAAmB,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/skill-test/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,gBAAgB,EAChB,iBAAiB,EACjB,sBAAsB,EACtB,iBAAiB,GAIlB,MAAM,gBAAgB,CAAC;AACxB,OAAO,EACL,kBAAkB,EAClB,eAAe,EACf,WAAW,GAOZ,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EACL,kBAAkB,EAClB,2BAA2B,EAC3B,mBAAmB,GAIpB,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EACL,8BAA8B,EAC9B,yBAAyB,GAE1B,MAAM,iBAAiB,CAAC"}
|
|
@@ -32,9 +32,16 @@ export interface SpawnResult {
|
|
|
32
32
|
export declare function killProcessTree(child: {
|
|
33
33
|
pid?: number | undefined;
|
|
34
34
|
}): void;
|
|
35
|
+
/**
|
|
36
|
+
* SIGKILL every currently in-flight `claude` child (and its process tree, via
|
|
37
|
+
* {@link killProcessTree}) and clear the registry. Idempotent — safe to call
|
|
38
|
+
* when the registry is already empty (e.g. every child has already settled,
|
|
39
|
+
* or this has already been called once on the current error path).
|
|
40
|
+
*/
|
|
41
|
+
export declare function killAllActiveClaudeChildren(): void;
|
|
35
42
|
export interface SpawnHeadlessOptions extends ClaudeSpawnArgs {
|
|
36
43
|
/**
|
|
37
|
-
* The
|
|
44
|
+
* The executor prompt, held IN MEMORY and streamed to the child's stdin
|
|
38
45
|
* (claude 2.x reads the `-p` prompt from stdin; there is no `--prompt-file`
|
|
39
46
|
* flag). Kept off argv so the prompt is never inlined (Windows cmd-quoting
|
|
40
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;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
|
|
@@ -60,6 +60,35 @@ export function killProcessTree(child) {
|
|
|
60
60
|
// ESRCH (no such process/group): the child and its group are already dead.
|
|
61
61
|
}
|
|
62
62
|
}
|
|
63
|
+
/**
|
|
64
|
+
* Registry of currently in-flight `claude` child processes, keyed by the
|
|
65
|
+
* `ChildProcess` object itself. A child is registered immediately after
|
|
66
|
+
* spawn and unregistered wherever it settles (normal 'close', child-process
|
|
67
|
+
* 'error', or the prompt-stream error path) — a normal completed run always
|
|
68
|
+
* leaves this Set empty.
|
|
69
|
+
*
|
|
70
|
+
* This exists for the concurrent pipeline's error path (spec §orphan-reap):
|
|
71
|
+
* when one worker in a bounded pool throws, `Promise.all` rejects without
|
|
72
|
+
* cancelling its siblings, and the top-level handler calls `process.exit(1)`
|
|
73
|
+
* — tearing down the in-process wall/stall watchdog timers before they fire.
|
|
74
|
+
* Without this registry, up to `concurrency - 1` in-flight `claude` sessions
|
|
75
|
+
* would be orphaned (detached process groups) and keep billing tokens until
|
|
76
|
+
* they self-terminate. The orchestrator's error handler calls
|
|
77
|
+
* {@link killAllActiveClaudeChildren} before exiting to reap them.
|
|
78
|
+
*/
|
|
79
|
+
const activeClaudeChildren = new Set();
|
|
80
|
+
/**
|
|
81
|
+
* SIGKILL every currently in-flight `claude` child (and its process tree, via
|
|
82
|
+
* {@link killProcessTree}) and clear the registry. Idempotent — safe to call
|
|
83
|
+
* when the registry is already empty (e.g. every child has already settled,
|
|
84
|
+
* or this has already been called once on the current error path).
|
|
85
|
+
*/
|
|
86
|
+
export function killAllActiveClaudeChildren() {
|
|
87
|
+
for (const child of activeClaudeChildren) {
|
|
88
|
+
killProcessTree(child);
|
|
89
|
+
}
|
|
90
|
+
activeClaudeChildren.clear();
|
|
91
|
+
}
|
|
63
92
|
/**
|
|
64
93
|
* Spawn the headless `claude`. The in-memory prompt is streamed to the child's
|
|
65
94
|
* stdin so the session reads its prompt and then sees EOF (it cannot block on
|
|
@@ -69,11 +98,16 @@ export function killProcessTree(child) {
|
|
|
69
98
|
*/
|
|
70
99
|
export async function spawnHeadlessClaude(opts) {
|
|
71
100
|
// opts.binPath is an internal test seam (see SpawnHeadlessOptions); production
|
|
72
|
-
// callers leave it unset and `claude` is resolved on PATH
|
|
73
|
-
|
|
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.
|
|
74
105
|
const args = assembleClaudeArgs(opts);
|
|
75
106
|
return await new Promise((resolve, reject) => {
|
|
76
|
-
|
|
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, {
|
|
77
111
|
cwd: opts.cwd,
|
|
78
112
|
env: opts.env,
|
|
79
113
|
stdio: ['pipe', 'pipe', 'pipe'],
|
|
@@ -82,6 +116,9 @@ export async function spawnHeadlessClaude(opts) {
|
|
|
82
116
|
// direct child. Windows has no process groups (taskkill handles the tree).
|
|
83
117
|
detached: process.platform !== 'win32',
|
|
84
118
|
});
|
|
119
|
+
// Register immediately after spawn so the child is reapable by
|
|
120
|
+
// killAllActiveClaudeChildren() for the entire time it can be in flight.
|
|
121
|
+
activeClaudeChildren.add(child);
|
|
85
122
|
let timedOut = false;
|
|
86
123
|
let stalled = false;
|
|
87
124
|
// Feed the prompt to the child via stdin (claude 2.x has no --prompt-file).
|
|
@@ -118,6 +155,10 @@ export async function spawnHeadlessClaude(opts) {
|
|
|
118
155
|
// here via 'close'; error paths call it directly) so an unconsumed in-memory
|
|
119
156
|
// stream is torn down. On a clean run the stream has already ended — no-op.
|
|
120
157
|
promptStream.destroy();
|
|
158
|
+
// This is the single choke point every settle path (close/error) runs
|
|
159
|
+
// through, so unregistering here guarantees a normally-completed run
|
|
160
|
+
// leaves the registry empty — no reference leak.
|
|
161
|
+
activeClaudeChildren.delete(child);
|
|
121
162
|
};
|
|
122
163
|
promptStream.on('error', err => {
|
|
123
164
|
clearAllTimers();
|
|
@@ -131,7 +172,11 @@ export async function spawnHeadlessClaude(opts) {
|
|
|
131
172
|
promptStream.pipe(child.stdin);
|
|
132
173
|
child.stdout.on('data', (d) => { opts.onStdout?.(d.toString()); });
|
|
133
174
|
child.stderr.on('data', (d) => { opts.onStderr?.(d.toString()); });
|
|
134
|
-
|
|
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); });
|
|
135
180
|
child.on('close', code => { clearAllTimers(); resolve({ status: code ?? -1, timedOut, stalled }); });
|
|
136
181
|
});
|
|
137
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,49 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Transcript parser for `claude -p --output-format stream-json`. The format
|
|
3
|
+
* is one JSON object per line; we collect the assistant text + tool-use
|
|
4
|
+
* events for the observation record.
|
|
5
|
+
*
|
|
6
|
+
* The stream-json schema is informally documented and may shift between
|
|
7
|
+
* Claude Code releases. This parser is intentionally tolerant of unknown
|
|
8
|
+
* event kinds — anything we don't recognise is appended to `raw`.
|
|
9
|
+
*
|
|
10
|
+
* Shape notes (empirically verified):
|
|
11
|
+
* - `tool_use` content blocks carry `input.command` for Bash calls.
|
|
12
|
+
* - `tool_result` content blocks (on `user` events) carry `is_error`.
|
|
13
|
+
* - `parent_tool_use_id` lives on the TOP-LEVEL event object (not the content
|
|
14
|
+
* block) — `null` for top-level tool calls, the parent's tool_use id for
|
|
15
|
+
* subagent-attributed calls.
|
|
16
|
+
* - The terminal `result` event carries `subtype`, `is_error`, `num_turns`,
|
|
17
|
+
* and `total_cost_usd`.
|
|
18
|
+
*/
|
|
19
|
+
export interface ParsedTranscript {
|
|
20
|
+
text: string;
|
|
21
|
+
toolUseEvents: Array<{
|
|
22
|
+
name: string;
|
|
23
|
+
inputSummary: string;
|
|
24
|
+
}>;
|
|
25
|
+
errors: string[];
|
|
26
|
+
raw: string[];
|
|
27
|
+
toolUses: Array<{
|
|
28
|
+
id: string | null;
|
|
29
|
+
name: string;
|
|
30
|
+
command?: string;
|
|
31
|
+
inputSummary: string;
|
|
32
|
+
parentToolUseId: string | null;
|
|
33
|
+
}>;
|
|
34
|
+
toolResults: Array<{
|
|
35
|
+
toolUseId: string | null;
|
|
36
|
+
isError: boolean;
|
|
37
|
+
contentSummary: string;
|
|
38
|
+
}>;
|
|
39
|
+
result?: {
|
|
40
|
+
subtype?: string;
|
|
41
|
+
isError: boolean;
|
|
42
|
+
numTurns?: number;
|
|
43
|
+
totalCostUsd?: number;
|
|
44
|
+
};
|
|
45
|
+
rateLimited: boolean;
|
|
46
|
+
}
|
|
47
|
+
export declare function parseStreamJsonTranscript(streamText: string): ParsedTranscript;
|
|
48
|
+
export declare function detectInvocationFromTranscript(parsed: ParsedTranscript, invocationSignals: readonly string[]): boolean;
|
|
49
|
+
//# sourceMappingURL=transcript.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"transcript.d.ts","sourceRoot":"","sources":["../../src/skill-test/transcript.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAEH,MAAM,WAAW,gBAAgB;IAC/B,IAAI,EAAE,MAAM,CAAC;IACb,aAAa,EAAE,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,YAAY,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IAC7D,MAAM,EAAE,MAAM,EAAE,CAAC;IACjB,GAAG,EAAE,MAAM,EAAE,CAAC;IACd,QAAQ,EAAE,KAAK,CAAC;QACd,EAAE,EAAE,MAAM,GAAG,IAAI,CAAC;QAClB,IAAI,EAAE,MAAM,CAAC;QACb,OAAO,CAAC,EAAE,MAAM,CAAC;QACjB,YAAY,EAAE,MAAM,CAAC;QACrB,eAAe,EAAE,MAAM,GAAG,IAAI,CAAC;KAChC,CAAC,CAAC;IACH,WAAW,EAAE,KAAK,CAAC;QAAE,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;QAAC,OAAO,EAAE,OAAO,CAAC;QAAC,cAAc,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IAC3F,MAAM,CAAC,EAAE;QAAE,OAAO,CAAC,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,OAAO,CAAC;QAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;QAAC,YAAY,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;IAC1F,WAAW,EAAE,OAAO,CAAC;CACtB;AA4HD,wBAAgB,yBAAyB,CAAC,UAAU,EAAE,MAAM,GAAG,gBAAgB,CA4B9E;AAED,wBAAgB,8BAA8B,CAC5C,MAAM,EAAE,gBAAgB,EACxB,iBAAiB,EAAE,SAAS,MAAM,EAAE,GACnC,OAAO,CAOT"}
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Transcript parser for `claude -p --output-format stream-json`. The format
|
|
3
|
+
* is one JSON object per line; we collect the assistant text + tool-use
|
|
4
|
+
* events for the observation record.
|
|
5
|
+
*
|
|
6
|
+
* The stream-json schema is informally documented and may shift between
|
|
7
|
+
* Claude Code releases. This parser is intentionally tolerant of unknown
|
|
8
|
+
* event kinds — anything we don't recognise is appended to `raw`.
|
|
9
|
+
*
|
|
10
|
+
* Shape notes (empirically verified):
|
|
11
|
+
* - `tool_use` content blocks carry `input.command` for Bash calls.
|
|
12
|
+
* - `tool_result` content blocks (on `user` events) carry `is_error`.
|
|
13
|
+
* - `parent_tool_use_id` lives on the TOP-LEVEL event object (not the content
|
|
14
|
+
* block) — `null` for top-level tool calls, the parent's tool_use id for
|
|
15
|
+
* subagent-attributed calls.
|
|
16
|
+
* - The terminal `result` event carries `subtype`, `is_error`, `num_turns`,
|
|
17
|
+
* and `total_cost_usd`.
|
|
18
|
+
*/
|
|
19
|
+
function summarizeValue(value) {
|
|
20
|
+
try {
|
|
21
|
+
const json = JSON.stringify(value);
|
|
22
|
+
return json.length > 200 ? `${json.slice(0, 197)}...` : json;
|
|
23
|
+
}
|
|
24
|
+
catch {
|
|
25
|
+
return '<unserializable>';
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
function extractCommand(input) {
|
|
29
|
+
if (input !== null && typeof input === 'object' && typeof input.command === 'string') {
|
|
30
|
+
return input.command;
|
|
31
|
+
}
|
|
32
|
+
return undefined;
|
|
33
|
+
}
|
|
34
|
+
function consumeAssistantBlocks(blocks, parentToolUseId, out) {
|
|
35
|
+
for (const block of blocks) {
|
|
36
|
+
if (block.type === 'text' && typeof block.text === 'string') {
|
|
37
|
+
out.text += block.text;
|
|
38
|
+
}
|
|
39
|
+
else if (block.type === 'tool_use' && typeof block.name === 'string') {
|
|
40
|
+
const inputSummary = summarizeValue(block.input);
|
|
41
|
+
out.toolUseEvents.push({ name: block.name, inputSummary });
|
|
42
|
+
const command = extractCommand(block.input);
|
|
43
|
+
out.toolUses.push({
|
|
44
|
+
id: typeof block.id === 'string' ? block.id : null,
|
|
45
|
+
name: block.name,
|
|
46
|
+
...(command === undefined ? {} : { command }),
|
|
47
|
+
inputSummary,
|
|
48
|
+
parentToolUseId,
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
function consumeUserBlocks(blocks, out) {
|
|
54
|
+
for (const block of blocks) {
|
|
55
|
+
if (block.type === 'tool_result') {
|
|
56
|
+
out.toolResults.push({
|
|
57
|
+
toolUseId: typeof block.tool_use_id === 'string' ? block.tool_use_id : null,
|
|
58
|
+
isError: block.is_error === true,
|
|
59
|
+
contentSummary: summarizeValue(block.content),
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
/** True when the event's `type`/`subtype` mentions rate-limiting, or it carries a `rate_limit_event` field. */
|
|
65
|
+
function detectRateLimit(parsed) {
|
|
66
|
+
const { type, subtype } = parsed;
|
|
67
|
+
if (typeof type === 'string' && type.includes('rate_limit'))
|
|
68
|
+
return true;
|
|
69
|
+
if (typeof subtype === 'string' && subtype.includes('rate_limit'))
|
|
70
|
+
return true;
|
|
71
|
+
return Object.prototype.hasOwnProperty.call(parsed, 'rate_limit_event');
|
|
72
|
+
}
|
|
73
|
+
function consumeResultEvent(parsed, out) {
|
|
74
|
+
// `claude -p --output-format stream-json` reports in-stream failures
|
|
75
|
+
// (max-turns, rate-limit, content-filter, tool error) as a terminal
|
|
76
|
+
// `result` event with is_error:true and a `subtype` discriminator.
|
|
77
|
+
out.result = {
|
|
78
|
+
...(typeof parsed.subtype === 'string' ? { subtype: parsed.subtype } : {}),
|
|
79
|
+
isError: parsed.is_error === true,
|
|
80
|
+
...(typeof parsed.num_turns === 'number' ? { numTurns: parsed.num_turns } : {}),
|
|
81
|
+
...(typeof parsed.total_cost_usd === 'number' ? { totalCostUsd: parsed.total_cost_usd } : {}),
|
|
82
|
+
};
|
|
83
|
+
if (parsed.is_error === true) {
|
|
84
|
+
out.errors.push(`runtime ${parsed.subtype ?? 'error'}`);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
function consumeErrorEvent(parsed, out) {
|
|
88
|
+
// Kept for forward compatibility — the stream-json schema is informally
|
|
89
|
+
// documented and may emit standalone error events in future releases.
|
|
90
|
+
const errMsg = typeof parsed.error === 'string'
|
|
91
|
+
? parsed.error
|
|
92
|
+
: parsed.error?.message ?? 'unknown error event';
|
|
93
|
+
out.errors.push(errMsg);
|
|
94
|
+
}
|
|
95
|
+
function applyEvent(parsed, out) {
|
|
96
|
+
if (detectRateLimit(parsed)) {
|
|
97
|
+
out.rateLimited = true;
|
|
98
|
+
}
|
|
99
|
+
if (parsed.type === 'assistant' && parsed.message?.content) {
|
|
100
|
+
consumeAssistantBlocks(parsed.message.content, parsed.parent_tool_use_id ?? null, out);
|
|
101
|
+
}
|
|
102
|
+
else if (parsed.type === 'user' && parsed.message?.content) {
|
|
103
|
+
consumeUserBlocks(parsed.message.content, out);
|
|
104
|
+
}
|
|
105
|
+
else if (parsed.type === 'result') {
|
|
106
|
+
consumeResultEvent(parsed, out);
|
|
107
|
+
}
|
|
108
|
+
else if (parsed.type === 'error') {
|
|
109
|
+
consumeErrorEvent(parsed, out);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
export function parseStreamJsonTranscript(streamText) {
|
|
113
|
+
const out = {
|
|
114
|
+
text: '',
|
|
115
|
+
toolUseEvents: [],
|
|
116
|
+
errors: [],
|
|
117
|
+
raw: [],
|
|
118
|
+
toolUses: [],
|
|
119
|
+
toolResults: [],
|
|
120
|
+
rateLimited: false,
|
|
121
|
+
};
|
|
122
|
+
const lines = streamText.split('\n');
|
|
123
|
+
for (const line of lines) {
|
|
124
|
+
const trimmed = line.trim();
|
|
125
|
+
if (trimmed.length === 0)
|
|
126
|
+
continue;
|
|
127
|
+
out.raw.push(trimmed);
|
|
128
|
+
let parsed;
|
|
129
|
+
try {
|
|
130
|
+
parsed = JSON.parse(trimmed);
|
|
131
|
+
}
|
|
132
|
+
catch {
|
|
133
|
+
continue;
|
|
134
|
+
}
|
|
135
|
+
applyEvent(parsed, out);
|
|
136
|
+
}
|
|
137
|
+
return out;
|
|
138
|
+
}
|
|
139
|
+
export function detectInvocationFromTranscript(parsed, invocationSignals) {
|
|
140
|
+
if (parsed.toolUseEvents.length > 0)
|
|
141
|
+
return true;
|
|
142
|
+
if (invocationSignals.length === 0) {
|
|
143
|
+
return parsed.text.trim().length > 0;
|
|
144
|
+
}
|
|
145
|
+
const haystack = parsed.text.toLowerCase();
|
|
146
|
+
return invocationSignals.some((s) => haystack.includes(s.toLowerCase()));
|
|
147
|
+
}
|
|
148
|
+
//# sourceMappingURL=transcript.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"transcript.js","sourceRoot":"","sources":["../../src/skill-test/transcript.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"}
|