@vibe-agent-toolkit/utils 0.1.39-rc.8 → 0.1.39
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/fs/file-hash.d.ts +17 -0
- package/dist/fs/file-hash.d.ts.map +1 -0
- package/dist/fs/file-hash.js +23 -0
- package/dist/fs/file-hash.js.map +1 -0
- package/dist/glob/glob-pattern.d.ts +65 -0
- package/dist/glob/glob-pattern.d.ts.map +1 -0
- package/dist/glob/glob-pattern.js +133 -0
- package/dist/glob/glob-pattern.js.map +1 -0
- package/dist/index.d.ts +4 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +7 -0
- package/dist/index.js.map +1 -1
- package/dist/link-auth/resolve-token.d.ts +7 -1
- package/dist/link-auth/resolve-token.d.ts.map +1 -1
- package/dist/link-auth/resolve-token.js +8 -3
- package/dist/link-auth/resolve-token.js.map +1 -1
- package/dist/link-auth/resolve.d.ts +44 -0
- package/dist/link-auth/resolve.d.ts.map +1 -1
- package/dist/link-auth/resolve.js +12 -1
- package/dist/link-auth/resolve.js.map +1 -1
- package/dist/path-utils.d.ts +65 -3
- package/dist/path-utils.d.ts.map +1 -1
- package/dist/path-utils.js +97 -3
- package/dist/path-utils.js.map +1 -1
- package/dist/skill-test/env-scrub.d.ts +58 -0
- package/dist/skill-test/env-scrub.d.ts.map +1 -1
- package/dist/skill-test/env-scrub.js +148 -2
- package/dist/skill-test/env-scrub.js.map +1 -1
- package/dist/skill-test/index.d.ts +1 -1
- package/dist/skill-test/index.d.ts.map +1 -1
- package/dist/skill-test/index.js +1 -1
- package/dist/skill-test/index.js.map +1 -1
- package/dist/skill-test/spawn-claude.d.ts +34 -9
- package/dist/skill-test/spawn-claude.d.ts.map +1 -1
- package/dist/skill-test/spawn-claude.js +60 -14
- package/dist/skill-test/spawn-claude.js.map +1 -1
- package/dist/yaml/surgical-yaml.d.ts +71 -0
- package/dist/yaml/surgical-yaml.d.ts.map +1 -0
- package/dist/yaml/surgical-yaml.js +314 -0
- package/dist/yaml/surgical-yaml.js.map +1 -0
- package/package.json +1 -1
package/dist/path-utils.js
CHANGED
|
@@ -179,6 +179,40 @@ export function mkdirSyncReal(dirPath, options) {
|
|
|
179
179
|
export function isAbsolutePath(p) {
|
|
180
180
|
return path.isAbsolute(p);
|
|
181
181
|
}
|
|
182
|
+
/**
|
|
183
|
+
* True if `p` is absolute on ANY platform — a POSIX root path (`/etc`), a
|
|
184
|
+
* Windows drive-letter path (`C:\…` or `C:/…`), or a UNC path (`\\host\share`).
|
|
185
|
+
*
|
|
186
|
+
* Unlike {@link isAbsolutePath} (host-platform only), this is host-independent,
|
|
187
|
+
* so config-containment checks reject Windows-absolute paths even when run on
|
|
188
|
+
* POSIX CI, and vice versa. Used to keep config-supplied relative paths (skill
|
|
189
|
+
* `files:` dest) from escaping their anchor directory (zip-slip class).
|
|
190
|
+
*
|
|
191
|
+
* @example
|
|
192
|
+
* isAbsoluteAnyPlatform('/etc/passwd') // true (POSIX)
|
|
193
|
+
* isAbsoluteAnyPlatform('C:\\Users') // true (Windows drive)
|
|
194
|
+
* isAbsoluteAnyPlatform('scripts/cli') // false (relative)
|
|
195
|
+
*/
|
|
196
|
+
export function isAbsoluteAnyPlatform(p) {
|
|
197
|
+
return path.posix.isAbsolute(p) || path.win32.isAbsolute(p);
|
|
198
|
+
}
|
|
199
|
+
/**
|
|
200
|
+
* True if `p` contains a `..` parent-directory traversal segment.
|
|
201
|
+
*
|
|
202
|
+
* Forward-slash-normalized, then inspects each `/`-delimited segment — so a
|
|
203
|
+
* `..` is caught regardless of the original OS separator. A containment guard
|
|
204
|
+
* for config-supplied relative paths (skill `files:` dest values, glob magic
|
|
205
|
+
* remainders) that must never climb above their anchor directory.
|
|
206
|
+
*
|
|
207
|
+
* @example
|
|
208
|
+
* hasParentTraversalSegment('a/../b') // true
|
|
209
|
+
* hasParentTraversalSegment('a/b/c') // false
|
|
210
|
+
* hasParentTraversalSegment('..\\evil') // true (backslash normalized)
|
|
211
|
+
* hasParentTraversalSegment('a..b/c') // false (".." must be a whole segment)
|
|
212
|
+
*/
|
|
213
|
+
export function hasParentTraversalSegment(p) {
|
|
214
|
+
return toForwardSlash(p).split('/').includes('..');
|
|
215
|
+
}
|
|
182
216
|
/**
|
|
183
217
|
* Convert a relative path to absolute
|
|
184
218
|
*
|
|
@@ -260,9 +294,11 @@ export function toForwardSlash(p) {
|
|
|
260
294
|
* import { safePath } from '@vibe-agent-toolkit/utils';
|
|
261
295
|
*
|
|
262
296
|
* // Always forward slashes, even on Windows
|
|
263
|
-
* safePath.join('C:\\Users', 'docs', 'file.md')
|
|
264
|
-
* safePath.resolve('/project', './docs')
|
|
265
|
-
* safePath.relative('/project/docs', '/project')
|
|
297
|
+
* safePath.join('C:\\Users', 'docs', 'file.md') // → 'C:/Users/docs/file.md'
|
|
298
|
+
* safePath.resolve('/project', './docs') // → '/project/docs'
|
|
299
|
+
* safePath.relative('/project/docs', '/project') // → '..'
|
|
300
|
+
* safePath.joinUnderRoot('/harness', 'skill-abc') // → '/harness/skill-abc'
|
|
301
|
+
* safePath.joinUnderRoot('/harness', '../escape') // throws Error
|
|
266
302
|
* ```
|
|
267
303
|
*/
|
|
268
304
|
export const safePath = {
|
|
@@ -278,6 +314,64 @@ export const safePath = {
|
|
|
278
314
|
relative(from, to) {
|
|
279
315
|
return toForwardSlash(path.relative(from, to));
|
|
280
316
|
},
|
|
317
|
+
/**
|
|
318
|
+
* Join path segments under a security root, throwing if the result would escape.
|
|
319
|
+
*
|
|
320
|
+
* Resolves `root + segments` and verifies the result is strictly inside `root`
|
|
321
|
+
* (or equal to it). Throws when any segment would cause the result to escape:
|
|
322
|
+
*
|
|
323
|
+
* - A `..` traversal that climbs above root
|
|
324
|
+
* - An absolute POSIX path segment (e.g. `/etc/passwd`)
|
|
325
|
+
* - A Windows drive-letter segment (e.g. `C:\Users\evil`)
|
|
326
|
+
*
|
|
327
|
+
* On success returns a forward-slash-normalized absolute path (consistent with
|
|
328
|
+
* the other `safePath` helpers).
|
|
329
|
+
*
|
|
330
|
+
* **Use this instead of `safePath.join(root, segment)` whenever `segment` may
|
|
331
|
+
* contain caller-controlled input** — this is the bug class that the original
|
|
332
|
+
* skill-test staging code was vulnerable to on Windows.
|
|
333
|
+
*
|
|
334
|
+
* @returns Forward-slash absolute path guaranteed to be inside `root`.
|
|
335
|
+
* @throws {Error} If the resolved path would escape `root`.
|
|
336
|
+
*
|
|
337
|
+
* @example
|
|
338
|
+
* ```typescript
|
|
339
|
+
* // ✅ Safe — throws if caller passes '../../../etc'
|
|
340
|
+
* const dest = safePath.joinUnderRoot(harnessRoot, stagedDirName(item.name));
|
|
341
|
+
*
|
|
342
|
+
* // ❌ Unsafe — silently escapes on Windows with absolute segment
|
|
343
|
+
* const dest = safePath.join(harnessRoot, item.name);
|
|
344
|
+
* ```
|
|
345
|
+
*/
|
|
346
|
+
joinUnderRoot(root, ...segments) {
|
|
347
|
+
// Eagerly reject any segment that is absolute (POSIX or Windows drive-letter)
|
|
348
|
+
// BEFORE resolving, so the error message can name the offending segment.
|
|
349
|
+
for (const seg of segments) {
|
|
350
|
+
if (path.isAbsolute(seg)) {
|
|
351
|
+
throw new Error(`safePath.joinUnderRoot: segment "${seg}" is absolute and escapes root "${root}".`);
|
|
352
|
+
}
|
|
353
|
+
// Windows drive-letter check for POSIX hosts (path.isAbsolute won't catch
|
|
354
|
+
// 'C:\...' on POSIX, but node's path.win32.isAbsolute does).
|
|
355
|
+
if (path.win32.isAbsolute(seg)) {
|
|
356
|
+
throw new Error(`safePath.joinUnderRoot: segment "${seg}" contains a Windows drive letter and escapes root "${root}".`);
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
const resolvedRoot = path.resolve(root);
|
|
360
|
+
const resolvedResult = segments.length > 0
|
|
361
|
+
? path.resolve(resolvedRoot, ...segments)
|
|
362
|
+
: resolvedRoot;
|
|
363
|
+
// Containment check: normalize both to forward slashes so the comparison
|
|
364
|
+
// is platform-independent and no path.sep is needed in string operations.
|
|
365
|
+
const fwdRoot = toForwardSlash(resolvedRoot);
|
|
366
|
+
const fwdResult = toForwardSlash(resolvedResult);
|
|
367
|
+
// Result must equal root or start with root + '/' (not just startsWith(root)
|
|
368
|
+
// which would match '/rootEvil' when root is '/root').
|
|
369
|
+
const rootPrefix = fwdRoot.endsWith('/') ? fwdRoot : `${fwdRoot}/`;
|
|
370
|
+
if (fwdResult !== fwdRoot && !fwdResult.startsWith(rootPrefix)) {
|
|
371
|
+
throw new Error(`safePath.joinUnderRoot: result "${fwdResult}" escapes root "${fwdRoot}".`);
|
|
372
|
+
}
|
|
373
|
+
return fwdResult;
|
|
374
|
+
},
|
|
281
375
|
};
|
|
282
376
|
/**
|
|
283
377
|
* Resolve an OS-native absolute path from an ESM module's `import.meta.url` and
|
package/dist/path-utils.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"path-utils.js","sourceRoot":"","sources":["../src/path-utils.ts"],"names":[],"mappings":"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
|
|
1
|
+
{"version":3,"file":"path-utils.js","sourceRoot":"","sources":["../src/path-utils.ts"],"names":[],"mappings":"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"}
|
|
@@ -11,4 +11,62 @@ export interface ForwardEnvOptions {
|
|
|
11
11
|
modelVars?: string[];
|
|
12
12
|
}
|
|
13
13
|
export declare function buildForwardedEnv(source: NodeJS.ProcessEnv, opts: ForwardEnvOptions): NodeJS.ProcessEnv;
|
|
14
|
+
/**
|
|
15
|
+
* The full set of env var names protected from declared override. A declared
|
|
16
|
+
* passEnv/injectEnv entry naming one of these is ignored (the protected value
|
|
17
|
+
* wins) and a warning is surfaced — a test must never clobber PATH, the auth
|
|
18
|
+
* credentials, or the admin key. `modelVars` (run-specific model env var names)
|
|
19
|
+
* join the set so a declared var can't shadow them either.
|
|
20
|
+
*/
|
|
21
|
+
export declare function protectedEnvNames(modelVars?: readonly string[]): Set<string>;
|
|
22
|
+
/**
|
|
23
|
+
* Returns true if `name` is in `protectedSet`.
|
|
24
|
+
*
|
|
25
|
+
* On Windows (win32) env names are case-insensitive — `path` and `PATH` are the
|
|
26
|
+
* same variable — so we upper-case both sides before comparing. On POSIX, names
|
|
27
|
+
* are genuinely case-distinct, so the comparison stays exact.
|
|
28
|
+
*
|
|
29
|
+
* The `platform` parameter defaults to `process.platform` but is exposed so
|
|
30
|
+
* unit tests can exercise the win32 branch on any host OS.
|
|
31
|
+
*/
|
|
32
|
+
export declare function isProtectedName(name: string, protectedSet: Set<string>, platform?: string): boolean;
|
|
33
|
+
/** Declared test env (Features A + B) to union onto a forwarded env. */
|
|
34
|
+
export interface DeclaredEnvInput {
|
|
35
|
+
/** Parent env to read Feature-A pass-through values from. */
|
|
36
|
+
source: NodeJS.ProcessEnv;
|
|
37
|
+
/** Feature A: names to forward from `source` if present. */
|
|
38
|
+
passEnv?: readonly string[];
|
|
39
|
+
/** Feature B: explicit key→value injections (already interpolated). */
|
|
40
|
+
injectEnv?: Record<string, string>;
|
|
41
|
+
/** Run-specific model env var names that are also protected. */
|
|
42
|
+
modelVars?: readonly string[];
|
|
43
|
+
}
|
|
44
|
+
export interface DeclaredEnvResult {
|
|
45
|
+
/** The forwarded env with declared additions unioned in. */
|
|
46
|
+
env: NodeJS.ProcessEnv;
|
|
47
|
+
/** Human-readable warnings (protected-key collisions). */
|
|
48
|
+
warnings: string[];
|
|
49
|
+
/** Names injected via Feature B (shown in the transparency line). */
|
|
50
|
+
injected: string[];
|
|
51
|
+
/** Names passed through via Feature A (redacted in the transparency line). */
|
|
52
|
+
passedThrough: string[];
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Union declared test env (Features A + B) onto an already-built forwarded env.
|
|
56
|
+
* Protected keys always win: a declared name colliding with a process-essential,
|
|
57
|
+
* auth, model, or admin var is ignored and a warning emitted. Feature-B injection
|
|
58
|
+
* (explicit value) takes precedence over Feature-A pass-through for the same key.
|
|
59
|
+
* The input `base` object is never mutated.
|
|
60
|
+
*/
|
|
61
|
+
export declare function applyDeclaredEnv(base: NodeJS.ProcessEnv, input: DeclaredEnvInput): DeclaredEnvResult;
|
|
62
|
+
/**
|
|
63
|
+
* Render the single-line stderr transparency summary of the forwarded env. Key
|
|
64
|
+
* names are always shown. Auth/secret values are redacted; Feature-A pass-through
|
|
65
|
+
* values are redacted (host-sourced, may be a secret); Feature-B injected values
|
|
66
|
+
* are shown (they come from committed config).
|
|
67
|
+
*/
|
|
68
|
+
export declare function formatForwardedEnvLine(env: NodeJS.ProcessEnv, classified: {
|
|
69
|
+
injected: readonly string[];
|
|
70
|
+
passedThrough: readonly string[];
|
|
71
|
+
}): string;
|
|
14
72
|
//# sourceMappingURL=env-scrub.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"env-scrub.d.ts","sourceRoot":"","sources":["../../src/skill-test/env-scrub.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,MAAM,WAAW,iBAAiB;IAChC,sHAAsH;IACtH,iBAAiB,EAAE,OAAO,CAAC;IAC3B,6EAA6E;IAC7E,SAAS,CAAC,EAAE,MAAM,EAAE,CAAC;CACtB;
|
|
1
|
+
{"version":3,"file":"env-scrub.d.ts","sourceRoot":"","sources":["../../src/skill-test/env-scrub.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,MAAM,WAAW,iBAAiB;IAChC,sHAAsH;IACtH,iBAAiB,EAAE,OAAO,CAAC;IAC3B,6EAA6E;IAC7E,SAAS,CAAC,EAAE,MAAM,EAAE,CAAC;CACtB;AA2DD,wBAAgB,iBAAiB,CAC/B,MAAM,EAAE,MAAM,CAAC,UAAU,EACzB,IAAI,EAAE,iBAAiB,GACtB,MAAM,CAAC,UAAU,CAuBnB;AAED;;;;;;GAMG;AACH,wBAAgB,iBAAiB,CAAC,SAAS,GAAE,SAAS,MAAM,EAAO,GAAG,GAAG,CAAC,MAAM,CAAC,CAShF;AAED;;;;;;;;;GASG;AACH,wBAAgB,eAAe,CAC7B,IAAI,EAAE,MAAM,EACZ,YAAY,EAAE,GAAG,CAAC,MAAM,CAAC,EACzB,QAAQ,GAAE,MAAyB,GAClC,OAAO,CAST;AAED,wEAAwE;AACxE,MAAM,WAAW,gBAAgB;IAC/B,6DAA6D;IAC7D,MAAM,EAAE,MAAM,CAAC,UAAU,CAAC;IAC1B,4DAA4D;IAC5D,OAAO,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IAC5B,uEAAuE;IACvE,SAAS,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACnC,gEAAgE;IAChE,SAAS,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;CAC/B;AAED,MAAM,WAAW,iBAAiB;IAChC,4DAA4D;IAC5D,GAAG,EAAE,MAAM,CAAC,UAAU,CAAC;IACvB,0DAA0D;IAC1D,QAAQ,EAAE,MAAM,EAAE,CAAC;IACnB,qEAAqE;IACrE,QAAQ,EAAE,MAAM,EAAE,CAAC;IACnB,8EAA8E;IAC9E,aAAa,EAAE,MAAM,EAAE,CAAC;CACzB;AAED;;;;;;GAMG;AACH,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,MAAM,CAAC,UAAU,EAAE,KAAK,EAAE,gBAAgB,GAAG,iBAAiB,CAiCpG;AAKD;;;;;GAKG;AACH,wBAAgB,sBAAsB,CACpC,GAAG,EAAE,MAAM,CAAC,UAAU,EACtB,UAAU,EAAE;IAAE,QAAQ,EAAE,SAAS,MAAM,EAAE,CAAC;IAAC,aAAa,EAAE,SAAS,MAAM,EAAE,CAAA;CAAE,GAC5E,MAAM,CAUR"}
|
|
@@ -4,12 +4,59 @@
|
|
|
4
4
|
* prefix — that would silently leak ANTHROPIC_ADMIN_API_KEY, proxy overrides,
|
|
5
5
|
* etc. into untrusted skill code. Anything not enumerated is dropped.
|
|
6
6
|
*/
|
|
7
|
-
/**
|
|
8
|
-
|
|
7
|
+
/**
|
|
8
|
+
* Process-essential vars that must survive for the child to even start. Includes
|
|
9
|
+
* the standard Windows essentials (APPDATA/LOCALAPPDATA — Claude's default config
|
|
10
|
+
* dir derives from these when CLAUDE_CONFIG_DIR is unset; SystemDrive, windir,
|
|
11
|
+
* PATHEXT, COMSPEC — needed to locate and launch executables). None are
|
|
12
|
+
* secret-bearing; the allowlist stays strict/exact-name.
|
|
13
|
+
*
|
|
14
|
+
* USER/LOGNAME (the POSIX username vars) are load-bearing for subscription auth
|
|
15
|
+
* on macOS: the `claude` CLI resolves the OAuth/subscription token from the login
|
|
16
|
+
* Keychain, and that lookup needs $USER — strip it and `claude auth status`
|
|
17
|
+
* reports loggedIn:false even with an active subscription, so `--auth
|
|
18
|
+
* subscription` (and inherit's subscription fallback) wrongly fail preflight, and
|
|
19
|
+
* the experimenter child can't authenticate. Neither is secret-bearing (the
|
|
20
|
+
* username is already derivable from the forwarded HOME), so they fit the policy.
|
|
21
|
+
* Not needed for api-key auth (key is self-contained) or Linux subscription auth
|
|
22
|
+
* (token is a HOME-relative file) — which is why this gap went unnoticed.
|
|
23
|
+
*/
|
|
24
|
+
const PROCESS_ESSENTIALS = [
|
|
25
|
+
'PATH', 'Path', 'HOME', 'USER', 'LOGNAME', 'USERPROFILE', 'SystemRoot', 'TEMP', 'TMP', 'TMPDIR', 'LANG', 'LC_ALL',
|
|
26
|
+
'APPDATA', 'LOCALAPPDATA', 'SystemDrive', 'windir', 'PATHEXT', 'COMSPEC',
|
|
27
|
+
];
|
|
9
28
|
/** The only auth/config vars ever forwarded. */
|
|
10
29
|
const AUTH_CONFIG_ALLOWLIST = ['CLAUDE_CONFIG_DIR'];
|
|
11
30
|
/** The active inference credential candidates (forwarded unless scrubbed). */
|
|
12
31
|
const INFERENCE_CREDENTIALS = ['ANTHROPIC_API_KEY', 'ANTHROPIC_AUTH_TOKEN'];
|
|
32
|
+
/**
|
|
33
|
+
* Vars that a declared env entry (injectEnv / passEnv) may NEVER override.
|
|
34
|
+
* These are deny-only — they are not in the forward allowlist either.
|
|
35
|
+
*
|
|
36
|
+
* WHY: Any of these names let a committed config attack without skill code:
|
|
37
|
+
* • ANTHROPIC_*_BASE_URL / ANTHROPIC_API_URL — redirect the inference endpoint
|
|
38
|
+
* so the forwarded ANTHROPIC_API_KEY is sent to an attacker-controlled server.
|
|
39
|
+
* • *_PROXY / *_proxy — redirect all HTTPS traffic (including API calls) through
|
|
40
|
+
* a MITM proxy even when the base URL looks correct.
|
|
41
|
+
* • NODE_OPTIONS — inject arbitrary code into the claude child process via
|
|
42
|
+
* --require or --import before any userland code runs.
|
|
43
|
+
* • NODE_EXTRA_CA_CERTS — add a rogue CA certificate, enabling TLS interception.
|
|
44
|
+
* • LD_PRELOAD / LD_LIBRARY_PATH / DYLD_INSERT_LIBRARIES / DYLD_LIBRARY_PATH —
|
|
45
|
+
* the OS-linker siblings of NODE_OPTIONS: load an attacker .so/.dylib into the
|
|
46
|
+
* child before any userland code runs (native code injection, bundler-agnostic).
|
|
47
|
+
* • NODE_PATH — prepend an attacker directory to Node's module resolution so a
|
|
48
|
+
* rogue package shadows a legitimate `require`.
|
|
49
|
+
* • GIT_SSH_COMMAND — run an arbitrary command on any git operation the harness
|
|
50
|
+
* performs (e.g. a git: source clone).
|
|
51
|
+
*/
|
|
52
|
+
const CREDENTIAL_ROUTING_DENY = [
|
|
53
|
+
'ANTHROPIC_BASE_URL', 'ANTHROPIC_API_URL', 'ANTHROPIC_BEDROCK_BASE_URL', 'ANTHROPIC_VERTEX_BASE_URL',
|
|
54
|
+
'HTTP_PROXY', 'HTTPS_PROXY', 'ALL_PROXY', 'NO_PROXY',
|
|
55
|
+
'http_proxy', 'https_proxy', 'all_proxy', 'no_proxy',
|
|
56
|
+
'NODE_OPTIONS', 'NODE_EXTRA_CA_CERTS', 'NODE_PATH',
|
|
57
|
+
'LD_PRELOAD', 'LD_LIBRARY_PATH', 'DYLD_INSERT_LIBRARIES', 'DYLD_LIBRARY_PATH',
|
|
58
|
+
'GIT_SSH_COMMAND',
|
|
59
|
+
];
|
|
13
60
|
export function buildForwardedEnv(source, opts) {
|
|
14
61
|
const out = {};
|
|
15
62
|
const allow = new Set([
|
|
@@ -32,4 +79,103 @@ export function buildForwardedEnv(source, opts) {
|
|
|
32
79
|
delete out['ANTHROPIC_ADMIN_API_KEY'];
|
|
33
80
|
return out;
|
|
34
81
|
}
|
|
82
|
+
/**
|
|
83
|
+
* The full set of env var names protected from declared override. A declared
|
|
84
|
+
* passEnv/injectEnv entry naming one of these is ignored (the protected value
|
|
85
|
+
* wins) and a warning is surfaced — a test must never clobber PATH, the auth
|
|
86
|
+
* credentials, or the admin key. `modelVars` (run-specific model env var names)
|
|
87
|
+
* join the set so a declared var can't shadow them either.
|
|
88
|
+
*/
|
|
89
|
+
export function protectedEnvNames(modelVars = []) {
|
|
90
|
+
return new Set([
|
|
91
|
+
...PROCESS_ESSENTIALS,
|
|
92
|
+
...AUTH_CONFIG_ALLOWLIST,
|
|
93
|
+
...INFERENCE_CREDENTIALS,
|
|
94
|
+
'ANTHROPIC_ADMIN_API_KEY',
|
|
95
|
+
...CREDENTIAL_ROUTING_DENY,
|
|
96
|
+
...modelVars,
|
|
97
|
+
]);
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* Returns true if `name` is in `protectedSet`.
|
|
101
|
+
*
|
|
102
|
+
* On Windows (win32) env names are case-insensitive — `path` and `PATH` are the
|
|
103
|
+
* same variable — so we upper-case both sides before comparing. On POSIX, names
|
|
104
|
+
* are genuinely case-distinct, so the comparison stays exact.
|
|
105
|
+
*
|
|
106
|
+
* The `platform` parameter defaults to `process.platform` but is exposed so
|
|
107
|
+
* unit tests can exercise the win32 branch on any host OS.
|
|
108
|
+
*/
|
|
109
|
+
export function isProtectedName(name, protectedSet, platform = process.platform) {
|
|
110
|
+
if (platform === 'win32') {
|
|
111
|
+
const upper = name.toUpperCase();
|
|
112
|
+
for (const p of protectedSet) {
|
|
113
|
+
if (p.toUpperCase() === upper)
|
|
114
|
+
return true;
|
|
115
|
+
}
|
|
116
|
+
return false;
|
|
117
|
+
}
|
|
118
|
+
return protectedSet.has(name);
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
121
|
+
* Union declared test env (Features A + B) onto an already-built forwarded env.
|
|
122
|
+
* Protected keys always win: a declared name colliding with a process-essential,
|
|
123
|
+
* auth, model, or admin var is ignored and a warning emitted. Feature-B injection
|
|
124
|
+
* (explicit value) takes precedence over Feature-A pass-through for the same key.
|
|
125
|
+
* The input `base` object is never mutated.
|
|
126
|
+
*/
|
|
127
|
+
export function applyDeclaredEnv(base, input) {
|
|
128
|
+
const out = { ...base };
|
|
129
|
+
const protectedNames = protectedEnvNames(input.modelVars);
|
|
130
|
+
const warnings = [];
|
|
131
|
+
const injected = [];
|
|
132
|
+
const passedThrough = [];
|
|
133
|
+
const injectKeys = new Set(Object.keys(input.injectEnv ?? {}));
|
|
134
|
+
// Feature A: pass-through by name.
|
|
135
|
+
for (const name of input.passEnv ?? []) {
|
|
136
|
+
if (isProtectedName(name, protectedNames)) {
|
|
137
|
+
warnings.push(`passEnv "${name}" ignored: it collides with a protected variable.`);
|
|
138
|
+
continue;
|
|
139
|
+
}
|
|
140
|
+
if (injectKeys.has(name))
|
|
141
|
+
continue; // Feature B wins for the same key.
|
|
142
|
+
const value = input.source[name];
|
|
143
|
+
if (value === undefined)
|
|
144
|
+
continue; // absent host var → simply not forwarded.
|
|
145
|
+
out[name] = value;
|
|
146
|
+
passedThrough.push(name);
|
|
147
|
+
}
|
|
148
|
+
// Feature B: explicit value injection.
|
|
149
|
+
for (const [name, value] of Object.entries(input.injectEnv ?? {})) {
|
|
150
|
+
if (isProtectedName(name, protectedNames)) {
|
|
151
|
+
warnings.push(`env "${name}" ignored: it collides with a protected variable.`);
|
|
152
|
+
continue;
|
|
153
|
+
}
|
|
154
|
+
out[name] = value;
|
|
155
|
+
injected.push(name);
|
|
156
|
+
}
|
|
157
|
+
return { env: out, warnings, injected, passedThrough };
|
|
158
|
+
}
|
|
159
|
+
/** Names whose VALUES are secrets and must be redacted in the transparency line. */
|
|
160
|
+
const SECRET_NAMES = new Set(INFERENCE_CREDENTIALS);
|
|
161
|
+
/**
|
|
162
|
+
* Render the single-line stderr transparency summary of the forwarded env. Key
|
|
163
|
+
* names are always shown. Auth/secret values are redacted; Feature-A pass-through
|
|
164
|
+
* values are redacted (host-sourced, may be a secret); Feature-B injected values
|
|
165
|
+
* are shown (they come from committed config).
|
|
166
|
+
*/
|
|
167
|
+
export function formatForwardedEnvLine(env, classified) {
|
|
168
|
+
const injected = new Set(classified.injected);
|
|
169
|
+
const passedThrough = new Set(classified.passedThrough);
|
|
170
|
+
const parts = Object.keys(env).map((name) => {
|
|
171
|
+
if (injected.has(name))
|
|
172
|
+
return `${name}=${env[name] ?? ''}`;
|
|
173
|
+
if (passedThrough.has(name))
|
|
174
|
+
return `${name}(passed-through, redacted)`;
|
|
175
|
+
if (SECRET_NAMES.has(name))
|
|
176
|
+
return `${name}(redacted)`;
|
|
177
|
+
return name;
|
|
178
|
+
});
|
|
179
|
+
return `forwarded env: ${parts.join(', ')}`;
|
|
180
|
+
}
|
|
35
181
|
//# sourceMappingURL=env-scrub.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"env-scrub.js","sourceRoot":"","sources":["../../src/skill-test/env-scrub.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AASH
|
|
1
|
+
{"version":3,"file":"env-scrub.js","sourceRoot":"","sources":["../../src/skill-test/env-scrub.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export { buildForwardedEnv, type ForwardEnvOptions } from './env-scrub.js';
|
|
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
3
|
export { assembleClaudeArgs, spawnHeadlessClaude, type ClaudeSpawnArgs, type SpawnHeadlessOptions, type SpawnResult, } from './spawn-claude.js';
|
|
4
4
|
//# 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,
|
|
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"}
|
package/dist/skill-test/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export { buildForwardedEnv } from './env-scrub.js';
|
|
1
|
+
export { applyDeclaredEnv, buildForwardedEnv, formatForwardedEnvLine, protectedEnvNames, } from './env-scrub.js';
|
|
2
2
|
export { AuthPreflightError, probeAuthStatus, resolveAuth, } from './auth-resolver.js';
|
|
3
3
|
export { assembleClaudeArgs, spawnHeadlessClaude, } from './spawn-claude.js';
|
|
4
4
|
//# sourceMappingURL=index.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/skill-test/index.ts"],"names":[],"mappings":"AAAA,OAAO,
|
|
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"}
|
|
@@ -18,14 +18,32 @@ export interface SpawnResult {
|
|
|
18
18
|
timedOut: boolean;
|
|
19
19
|
stalled: boolean;
|
|
20
20
|
}
|
|
21
|
+
/**
|
|
22
|
+
* SIGKILL the child AND every process in its group. The child is spawned
|
|
23
|
+
* `detached` (POSIX) so it leads a new process group; killing the negated pid
|
|
24
|
+
* reaps backgrounded grandchildren (e.g. a skill that runs `nohup … &`) and
|
|
25
|
+
* orphaned MCP subprocesses that a direct `child.kill()` would leave running.
|
|
26
|
+
*
|
|
27
|
+
* - Guards an undefined pid (child never spawned — nothing to kill).
|
|
28
|
+
* - Swallows the throw (ESRCH) when the group is already gone.
|
|
29
|
+
* - Windows has no POSIX process groups: fall back to `taskkill /T /F`, which
|
|
30
|
+
* terminates the whole process tree.
|
|
31
|
+
*/
|
|
32
|
+
export declare function killProcessTree(child: {
|
|
33
|
+
pid?: number | undefined;
|
|
34
|
+
}): void;
|
|
21
35
|
export interface SpawnHeadlessOptions extends ClaudeSpawnArgs {
|
|
22
36
|
/**
|
|
23
|
-
*
|
|
24
|
-
*
|
|
25
|
-
*
|
|
26
|
-
*
|
|
37
|
+
* The experimenter prompt, held IN MEMORY and streamed to the child's stdin
|
|
38
|
+
* (claude 2.x reads the `-p` prompt from stdin; there is no `--prompt-file`
|
|
39
|
+
* flag). Kept off argv so the prompt is never inlined (Windows cmd-quoting
|
|
40
|
+
* safety) AND, deliberately, off disk: the harness stamps a per-run integrity
|
|
41
|
+
* nonce into this prompt, and writing it to a file inside (or beside) the
|
|
42
|
+
* skill-writable sandbox would let untrusted skill code read the nonce back and
|
|
43
|
+
* forge a passing grading.json. Passing it in memory keeps the nonce out of any
|
|
44
|
+
* file the skill can read.
|
|
27
45
|
*/
|
|
28
|
-
|
|
46
|
+
prompt: string;
|
|
29
47
|
cwd: string;
|
|
30
48
|
env: NodeJS.ProcessEnv;
|
|
31
49
|
timeoutMs: number;
|
|
@@ -33,12 +51,19 @@ export interface SpawnHeadlessOptions extends ClaudeSpawnArgs {
|
|
|
33
51
|
stallMs?: number;
|
|
34
52
|
onStdout?: (chunk: string) => void;
|
|
35
53
|
onStderr?: (chunk: string) => void;
|
|
54
|
+
/**
|
|
55
|
+
* INTERNAL test seam. When set, this executable is spawned instead of resolving
|
|
56
|
+
* `claude` on PATH — letting tests exercise the wall-timeout / stall watchdog
|
|
57
|
+
* against a tiny fake child without a real `claude` install. Production callers
|
|
58
|
+
* never set this; `claude` is resolved via `which`.
|
|
59
|
+
*/
|
|
60
|
+
binPath?: string;
|
|
36
61
|
}
|
|
37
62
|
/**
|
|
38
|
-
* Spawn the headless `claude`. The prompt
|
|
39
|
-
* so the session reads its prompt and then sees EOF (it cannot block on
|
|
40
|
-
* §16). Wall-clock kill on timeout. If `stallMs` is set, a resettable
|
|
41
|
-
* watchdog kills the child when no output is received for that duration
|
|
63
|
+
* Spawn the headless `claude`. The in-memory prompt is streamed to the child's
|
|
64
|
+
* stdin so the session reads its prompt and then sees EOF (it cannot block on
|
|
65
|
+
* input, §16). Wall-clock kill on timeout. If `stallMs` is set, a resettable
|
|
66
|
+
* stall watchdog kills the child when no output is received for that duration
|
|
42
67
|
* (stalled: true in result).
|
|
43
68
|
*/
|
|
44
69
|
export declare function spawnHeadlessClaude(opts: SpawnHeadlessOptions): Promise<SpawnResult>;
|
|
@@ -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,MAAM,WAAW,oBAAqB,SAAQ,eAAe;IAC3D
|
|
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,CAwE1F"}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { spawn } from 'node:child_process';
|
|
2
|
-
import {
|
|
1
|
+
import { spawn, spawnSync } from 'node:child_process';
|
|
2
|
+
import { Readable } from 'node:stream';
|
|
3
3
|
import which from 'which';
|
|
4
4
|
/**
|
|
5
5
|
* Pure assembly of the headless `claude -p` argv (spec §9). The prompt is never
|
|
@@ -32,22 +32,67 @@ export function assembleClaudeArgs(opts) {
|
|
|
32
32
|
return args;
|
|
33
33
|
}
|
|
34
34
|
/**
|
|
35
|
-
*
|
|
36
|
-
*
|
|
37
|
-
*
|
|
38
|
-
*
|
|
35
|
+
* SIGKILL the child AND every process in its group. The child is spawned
|
|
36
|
+
* `detached` (POSIX) so it leads a new process group; killing the negated pid
|
|
37
|
+
* reaps backgrounded grandchildren (e.g. a skill that runs `nohup … &`) and
|
|
38
|
+
* orphaned MCP subprocesses that a direct `child.kill()` would leave running.
|
|
39
|
+
*
|
|
40
|
+
* - Guards an undefined pid (child never spawned — nothing to kill).
|
|
41
|
+
* - Swallows the throw (ESRCH) when the group is already gone.
|
|
42
|
+
* - Windows has no POSIX process groups: fall back to `taskkill /T /F`, which
|
|
43
|
+
* terminates the whole process tree.
|
|
44
|
+
*/
|
|
45
|
+
export function killProcessTree(child) {
|
|
46
|
+
const { pid } = child;
|
|
47
|
+
if (pid === undefined)
|
|
48
|
+
return;
|
|
49
|
+
try {
|
|
50
|
+
if (process.platform === 'win32') {
|
|
51
|
+
// eslint-disable-next-line sonarjs/no-os-command-from-path -- taskkill is a fixed Windows system command (no PATH-injection surface); it is the only POSIX-process-group-free way to terminate the child's tree.
|
|
52
|
+
spawnSync('taskkill', ['/pid', String(pid), '/T', '/F']);
|
|
53
|
+
}
|
|
54
|
+
else {
|
|
55
|
+
// Negated pid → deliver the signal to the whole process group.
|
|
56
|
+
process.kill(-pid, 'SIGKILL');
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
catch {
|
|
60
|
+
// ESRCH (no such process/group): the child and its group are already dead.
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Spawn the headless `claude`. The in-memory prompt is streamed to the child's
|
|
65
|
+
* stdin so the session reads its prompt and then sees EOF (it cannot block on
|
|
66
|
+
* input, §16). Wall-clock kill on timeout. If `stallMs` is set, a resettable
|
|
67
|
+
* stall watchdog kills the child when no output is received for that duration
|
|
39
68
|
* (stalled: true in result).
|
|
40
69
|
*/
|
|
41
70
|
export async function spawnHeadlessClaude(opts) {
|
|
42
|
-
|
|
71
|
+
// opts.binPath is an internal test seam (see SpawnHeadlessOptions); production
|
|
72
|
+
// callers leave it unset and `claude` is resolved on PATH.
|
|
73
|
+
const bin = opts.binPath ?? which.sync('claude');
|
|
43
74
|
const args = assembleClaudeArgs(opts);
|
|
44
75
|
return await new Promise((resolve, reject) => {
|
|
45
|
-
const child = spawn(bin, args, {
|
|
76
|
+
const child = spawn(bin, args, {
|
|
77
|
+
cwd: opts.cwd,
|
|
78
|
+
env: opts.env,
|
|
79
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
80
|
+
// POSIX: lead a new process group so the kill paths can SIGKILL the whole
|
|
81
|
+
// group (backgrounded grandchildren + MCP subprocesses), not just the
|
|
82
|
+
// direct child. Windows has no process groups (taskkill handles the tree).
|
|
83
|
+
detached: process.platform !== 'win32',
|
|
84
|
+
});
|
|
46
85
|
let timedOut = false;
|
|
47
86
|
let stalled = false;
|
|
87
|
+
// Feed the prompt to the child via stdin (claude 2.x has no --prompt-file).
|
|
88
|
+
// Built from the in-memory string (never a file — the prompt carries a secret
|
|
89
|
+
// per-run nonce that must not land on the skill-readable filesystem). Created
|
|
90
|
+
// here (before the timers) so every kill path can destroy it if the child is
|
|
91
|
+
// SIGKILL'd before stdin is fully consumed.
|
|
92
|
+
const promptStream = Readable.from([opts.prompt], { objectMode: false });
|
|
48
93
|
const wallTimer = setTimeout(() => {
|
|
49
94
|
timedOut = true;
|
|
50
|
-
child
|
|
95
|
+
killProcessTree(child);
|
|
51
96
|
}, opts.timeoutMs);
|
|
52
97
|
// Stall watchdog: reset on every stdout/stderr chunk; fire if silent for stallMs.
|
|
53
98
|
let stallTimer;
|
|
@@ -58,7 +103,7 @@ export async function spawnHeadlessClaude(opts) {
|
|
|
58
103
|
clearTimeout(stallTimer);
|
|
59
104
|
stallTimer = setTimeout(() => {
|
|
60
105
|
stalled = true;
|
|
61
|
-
child
|
|
106
|
+
killProcessTree(child);
|
|
62
107
|
}, stallMs);
|
|
63
108
|
};
|
|
64
109
|
resetStallTimer();
|
|
@@ -69,13 +114,14 @@ export async function spawnHeadlessClaude(opts) {
|
|
|
69
114
|
clearTimeout(wallTimer);
|
|
70
115
|
if (stallTimer !== undefined)
|
|
71
116
|
clearTimeout(stallTimer);
|
|
117
|
+
// Destroy the prompt stream on every terminal path (timeout/stall reach
|
|
118
|
+
// here via 'close'; error paths call it directly) so an unconsumed in-memory
|
|
119
|
+
// stream is torn down. On a clean run the stream has already ended — no-op.
|
|
120
|
+
promptStream.destroy();
|
|
72
121
|
};
|
|
73
|
-
// Feed the prompt to the child via stdin (claude 2.x has no --prompt-file).
|
|
74
|
-
// eslint-disable-next-line security/detect-non-literal-fs-filename -- our own derived prompt path
|
|
75
|
-
const promptStream = createReadStream(opts.promptFile);
|
|
76
122
|
promptStream.on('error', err => {
|
|
77
123
|
clearAllTimers();
|
|
78
|
-
child
|
|
124
|
+
killProcessTree(child);
|
|
79
125
|
reject(err);
|
|
80
126
|
});
|
|
81
127
|
// Swallow EPIPE: if the child exits before consuming all stdin, the write
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"spawn-claude.js","sourceRoot":"","sources":["../../src/skill-test/spawn-claude.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,MAAM,oBAAoB,CAAC;
|
|
1
|
+
{"version":3,"file":"spawn-claude.js","sourceRoot":"","sources":["../../src/skill-test/spawn-claude.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,SAAS,EAAE,MAAM,oBAAoB,CAAC;AACtD,OAAO,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AAEvC,OAAO,KAAK,MAAM,OAAO,CAAC;AAU1B;;;;;;GAMG;AACH,MAAM,UAAU,kBAAkB,CAAC,IAAqB;IACtD,MAAM,IAAI,GAAa;QACrB,IAAI;QACJ,0EAA0E;QAC1E,sEAAsE;QACtE,4DAA4D;QAC5D,iBAAiB,EAAE,aAAa;QAChC,WAAW;QACX,mBAAmB,EAAE,EAAE;QACvB,mBAAmB,EAAE,mBAAmB;QACxC,WAAW,EAAE,IAAI,CAAC,UAAU;KAC7B,CAAC;IACF,KAAK,MAAM,GAAG,IAAI,IAAI,CAAC,UAAU,EAAE,CAAC;QAClC,IAAI,CAAC,IAAI,CAAC,cAAc,EAAE,GAAG,CAAC,CAAC;IACjC,CAAC;IACD,IAAI,IAAI,CAAC,KAAK,KAAK,SAAS;QAAE,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE,IAAI,CAAC,KAAK,CAAC,CAAC;IAC/D,IAAI,IAAI,CAAC,QAAQ,KAAK,SAAS;QAAE,IAAI,CAAC,IAAI,CAAC,aAAa,EAAE,MAAM,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC,CAAC;IACjF,IAAI,IAAI,CAAC,YAAY,KAAK,SAAS;QAAE,IAAI,CAAC,IAAI,CAAC,kBAAkB,EAAE,MAAM,CAAC,IAAI,CAAC,YAAY,CAAC,CAAC,CAAC;IAC9F,OAAO,IAAI,CAAC;AACd,CAAC;AAQD;;;;;;;;;;GAUG;AACH,MAAM,UAAU,eAAe,CAAC,KAAmC;IACjE,MAAM,EAAE,GAAG,EAAE,GAAG,KAAK,CAAC;IACtB,IAAI,GAAG,KAAK,SAAS;QAAE,OAAO;IAC9B,IAAI,CAAC;QACH,IAAI,OAAO,CAAC,QAAQ,KAAK,OAAO,EAAE,CAAC;YACjC,iNAAiN;YACjN,SAAS,CAAC,UAAU,EAAE,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,CAAC,EAAE,IAAI,EAAE,IAAI,CAAC,CAAC,CAAC;QAC3D,CAAC;aAAM,CAAC;YACN,+DAA+D;YAC/D,OAAO,CAAC,IAAI,CAAC,CAAC,GAAG,EAAE,SAAS,CAAC,CAAC;QAChC,CAAC;IACH,CAAC;IAAC,MAAM,CAAC;QACP,2EAA2E;IAC7E,CAAC;AACH,CAAC;AA8BD;;;;;;GAMG;AACH,MAAM,CAAC,KAAK,UAAU,mBAAmB,CAAC,IAA0B;IAClE,+EAA+E;IAC/E,2DAA2D;IAC3D,MAAM,GAAG,GAAG,IAAI,CAAC,OAAO,IAAI,KAAK,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;IACjD,MAAM,IAAI,GAAG,kBAAkB,CAAC,IAAI,CAAC,CAAC;IAEtC,OAAO,MAAM,IAAI,OAAO,CAAc,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;QACxD,MAAM,KAAK,GAAG,KAAK,CAAC,GAAG,EAAE,IAAI,EAAE;YAC7B,GAAG,EAAE,IAAI,CAAC,GAAG;YACb,GAAG,EAAE,IAAI,CAAC,GAAG;YACb,KAAK,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC;YAC/B,0EAA0E;YAC1E,sEAAsE;YACtE,2EAA2E;YAC3E,QAAQ,EAAE,OAAO,CAAC,QAAQ,KAAK,OAAO;SACvC,CAAC,CAAC;QACH,IAAI,QAAQ,GAAG,KAAK,CAAC;QACrB,IAAI,OAAO,GAAG,KAAK,CAAC;QAEpB,4EAA4E;QAC5E,8EAA8E;QAC9E,8EAA8E;QAC9E,6EAA6E;QAC7E,4CAA4C;QAC5C,MAAM,YAAY,GAAG,QAAQ,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,EAAE,EAAE,UAAU,EAAE,KAAK,EAAE,CAAC,CAAC;QAEzE,MAAM,SAAS,GAAG,UAAU,CAAC,GAAG,EAAE;YAChC,QAAQ,GAAG,IAAI,CAAC;YAChB,eAAe,CAAC,KAAK,CAAC,CAAC;QACzB,CAAC,EAAE,IAAI,CAAC,SAAS,CAAC,CAAC;QAEnB,kFAAkF;QAClF,IAAI,UAAqD,CAAC;QAC1D,IAAI,IAAI,CAAC,OAAO,KAAK,SAAS,EAAE,CAAC;YAC/B,MAAM,OAAO,GAAG,IAAI,CAAC,OAAO,CAAC;YAC7B,MAAM,eAAe,GAAG,GAAS,EAAE;gBACjC,IAAI,UAAU,KAAK,SAAS;oBAAE,YAAY,CAAC,UAAU,CAAC,CAAC;gBACvD,UAAU,GAAG,UAAU,CAAC,GAAG,EAAE;oBAC3B,OAAO,GAAG,IAAI,CAAC;oBACf,eAAe,CAAC,KAAK,CAAC,CAAC;gBACzB,CAAC,EAAE,OAAO,CAAC,CAAC;YACd,CAAC,CAAC;YACF,eAAe,EAAE,CAAC;YAElB,KAAK,CAAC,MAAM,CAAC,EAAE,CAAC,MAAM,EAAE,GAAG,EAAE,GAAG,eAAe,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC;YACtD,KAAK,CAAC,MAAM,CAAC,EAAE,CAAC,MAAM,EAAE,GAAG,EAAE,GAAG,eAAe,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC;QACxD,CAAC;QAED,MAAM,cAAc,GAAG,GAAS,EAAE;YAChC,YAAY,CAAC,SAAS,CAAC,CAAC;YACxB,IAAI,UAAU,KAAK,SAAS;gBAAE,YAAY,CAAC,UAAU,CAAC,CAAC;YACvD,wEAAwE;YACxE,6EAA6E;YAC7E,4EAA4E;YAC5E,YAAY,CAAC,OAAO,EAAE,CAAC;QACzB,CAAC,CAAC;QAEF,YAAY,CAAC,EAAE,CAAC,OAAO,EAAE,GAAG,CAAC,EAAE;YAC7B,cAAc,EAAE,CAAC;YACjB,eAAe,CAAC,KAAK,CAAC,CAAC;YACvB,MAAM,CAAC,GAAG,CAAC,CAAC;QACd,CAAC,CAAC,CAAC;QACH,0EAA0E;QAC1E,4EAA4E;QAC5E,6BAA6B;QAC7B,KAAK,CAAC,KAAK,CAAC,EAAE,CAAC,OAAO,EAAE,GAAG,EAAE,GAA0C,CAAC,CAAC,CAAC;QAC1E,YAAY,CAAC,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC;QAC/B,KAAK,CAAC,MAAM,CAAC,EAAE,CAAC,MAAM,EAAE,CAAC,CAAS,EAAE,EAAE,GAAG,IAAI,CAAC,QAAQ,EAAE,CAAC,CAAC,CAAC,QAAQ,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;QAC3E,KAAK,CAAC,MAAM,CAAC,EAAE,CAAC,MAAM,EAAE,CAAC,CAAS,EAAE,EAAE,GAAG,IAAI,CAAC,QAAQ,EAAE,CAAC,CAAC,CAAC,QAAQ,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;QAC3E,KAAK,CAAC,EAAE,CAAC,OAAO,EAAE,GAAG,CAAC,EAAE,GAAG,cAAc,EAAE,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;QAC7D,KAAK,CAAC,EAAE,CAAC,OAAO,EAAE,IAAI,CAAC,EAAE,GAAG,cAAc,EAAE,CAAC,CAAC,OAAO,CAAC,EAAE,MAAM,EAAE,IAAI,IAAI,CAAC,CAAC,EAAE,QAAQ,EAAE,OAAO,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;IACvG,CAAC,CAAC,CAAC;AACL,CAAC"}
|