@vibe-agent-toolkit/utils 0.1.39-rc.9 → 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 +30 -0
- package/dist/path-utils.d.ts.map +1 -1
- package/dist/path-utils.js +34 -0
- 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
|
@@ -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":"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"}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Byte-surgical YAML value updater.
|
|
3
|
+
*
|
|
4
|
+
* The motivating problem: editing one value in an adopter's config file with the
|
|
5
|
+
* naive `doc.setIn(...); doc.toString()` round-trip reflows the ENTIRE document —
|
|
6
|
+
* collapsing flow sequences, re-wrapping long scalars, shifting comment alignment,
|
|
7
|
+
* normalising quote styles. That destroys hand-authored formatting the user never
|
|
8
|
+
* asked us to touch.
|
|
9
|
+
*
|
|
10
|
+
* This module instead changes the **minimum bytes possible**:
|
|
11
|
+
*
|
|
12
|
+
* - **Replace** an existing scalar: locate its source byte range via the parsed
|
|
13
|
+
* node and splice the new serialized token in place. Every other byte is
|
|
14
|
+
* identical.
|
|
15
|
+
* - **Insert** a new key: render ONLY the new nested fragment, indent it to the
|
|
16
|
+
* target map's child column, and splice it in after the map's last item. The
|
|
17
|
+
* rest of the document is never re-rendered.
|
|
18
|
+
*
|
|
19
|
+
* `verifyConfinedYamlEdit` is the double-checker: it re-parses before/after and
|
|
20
|
+
* asserts the edit landed, no other leaf value drifted, and no comment was lost.
|
|
21
|
+
* `updateYamlIn` calls it internally as a defensive post-condition.
|
|
22
|
+
*
|
|
23
|
+
* No filesystem access — these are pure string transforms.
|
|
24
|
+
*/
|
|
25
|
+
/** A path into a YAML document: map keys (strings) and/or sequence indices. */
|
|
26
|
+
export type YamlPath = (string | number)[];
|
|
27
|
+
/** A scalar value this module knows how to serialize as a single YAML token. */
|
|
28
|
+
export type YamlScalarValue = string | number | boolean | null;
|
|
29
|
+
/**
|
|
30
|
+
* Upsert a single scalar at `path` in `text`, changing the minimum bytes possible.
|
|
31
|
+
*
|
|
32
|
+
* - If `path` already resolves to a scalar, its source token is replaced in place
|
|
33
|
+
* (byte-identical output except the one value, including surrounding comments,
|
|
34
|
+
* alignment whitespace, flow-collection padding, and sibling formatting).
|
|
35
|
+
* - If `path` does not yet exist, a new nested fragment is rendered and spliced
|
|
36
|
+
* into the deepest existing ancestor map at the correct child indentation —
|
|
37
|
+
* without re-rendering (and thus reflowing) the rest of the document.
|
|
38
|
+
*
|
|
39
|
+
* The input's EOL style (`\n` vs `\r\n`) is detected and preserved.
|
|
40
|
+
*
|
|
41
|
+
* @param text - The full YAML source document.
|
|
42
|
+
* @param path - Key/index path to the scalar to set.
|
|
43
|
+
* @param value - The scalar value to write (string, number, boolean, or null).
|
|
44
|
+
* @returns The updated document text.
|
|
45
|
+
* @throws If `text` is not valid YAML, if `path` is empty, or if `path` resolves
|
|
46
|
+
* to a collection that would be clobbered by a scalar (or an intermediate
|
|
47
|
+
* ancestor is a scalar that cannot hold a child).
|
|
48
|
+
*
|
|
49
|
+
* @example
|
|
50
|
+
* updateYamlIn('model: haiku # note\n', ['model'], 'opus')
|
|
51
|
+
* // => 'model: opus # note\n' (alignment + comment preserved)
|
|
52
|
+
*/
|
|
53
|
+
export declare function updateYamlIn(text: string, path: YamlPath, value: YamlScalarValue): string;
|
|
54
|
+
/**
|
|
55
|
+
* Re-parse `before` and `after` and assert the edit was correct and confined:
|
|
56
|
+
*
|
|
57
|
+
* 1. **Correctness** — every entry in `changedPaths` resolves in `after`.
|
|
58
|
+
* 2. **Confinement** — every other leaf path present in `before` has a deep-equal
|
|
59
|
+
* value in `after` (no collateral value edits).
|
|
60
|
+
* 3. **Comment preservation** — the multiset of all comment strings is unchanged.
|
|
61
|
+
*
|
|
62
|
+
* Safe to call as an internal assertion from {@link updateYamlIn}.
|
|
63
|
+
*
|
|
64
|
+
* @param before - The original document text.
|
|
65
|
+
* @param after - The edited document text.
|
|
66
|
+
* @param changedPaths - The paths the caller intended to change.
|
|
67
|
+
* @throws A descriptive `Error` naming the offending path or lost comment on any
|
|
68
|
+
* violation.
|
|
69
|
+
*/
|
|
70
|
+
export declare function verifyConfinedYamlEdit(before: string, after: string, changedPaths: YamlPath[]): void;
|
|
71
|
+
//# sourceMappingURL=surgical-yaml.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"surgical-yaml.d.ts","sourceRoot":"","sources":["../../src/yaml/surgical-yaml.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AAcH,+EAA+E;AAC/E,MAAM,MAAM,QAAQ,GAAG,CAAC,MAAM,GAAG,MAAM,CAAC,EAAE,CAAC;AAE3C,gFAAgF;AAChF,MAAM,MAAM,eAAe,GAAG,MAAM,GAAG,MAAM,GAAG,OAAO,GAAG,IAAI,CAAC;AAK/D;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,QAAQ,EAAE,KAAK,EAAE,eAAe,GAAG,MAAM,CA2BzF;AAED;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,sBAAsB,CACpC,MAAM,EAAE,MAAM,EACd,KAAK,EAAE,MAAM,EACb,YAAY,EAAE,QAAQ,EAAE,GACvB,IAAI,CAYN"}
|