@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.
Files changed (41) hide show
  1. package/dist/fs/file-hash.d.ts +17 -0
  2. package/dist/fs/file-hash.d.ts.map +1 -0
  3. package/dist/fs/file-hash.js +23 -0
  4. package/dist/fs/file-hash.js.map +1 -0
  5. package/dist/glob/glob-pattern.d.ts +65 -0
  6. package/dist/glob/glob-pattern.d.ts.map +1 -0
  7. package/dist/glob/glob-pattern.js +133 -0
  8. package/dist/glob/glob-pattern.js.map +1 -0
  9. package/dist/index.d.ts +4 -1
  10. package/dist/index.d.ts.map +1 -1
  11. package/dist/index.js +7 -0
  12. package/dist/index.js.map +1 -1
  13. package/dist/link-auth/resolve-token.d.ts +7 -1
  14. package/dist/link-auth/resolve-token.d.ts.map +1 -1
  15. package/dist/link-auth/resolve-token.js +8 -3
  16. package/dist/link-auth/resolve-token.js.map +1 -1
  17. package/dist/link-auth/resolve.d.ts +44 -0
  18. package/dist/link-auth/resolve.d.ts.map +1 -1
  19. package/dist/link-auth/resolve.js +12 -1
  20. package/dist/link-auth/resolve.js.map +1 -1
  21. package/dist/path-utils.d.ts +30 -0
  22. package/dist/path-utils.d.ts.map +1 -1
  23. package/dist/path-utils.js +34 -0
  24. package/dist/path-utils.js.map +1 -1
  25. package/dist/skill-test/env-scrub.d.ts +58 -0
  26. package/dist/skill-test/env-scrub.d.ts.map +1 -1
  27. package/dist/skill-test/env-scrub.js +148 -2
  28. package/dist/skill-test/env-scrub.js.map +1 -1
  29. package/dist/skill-test/index.d.ts +1 -1
  30. package/dist/skill-test/index.d.ts.map +1 -1
  31. package/dist/skill-test/index.js +1 -1
  32. package/dist/skill-test/index.js.map +1 -1
  33. package/dist/skill-test/spawn-claude.d.ts +34 -9
  34. package/dist/skill-test/spawn-claude.d.ts.map +1 -1
  35. package/dist/skill-test/spawn-claude.js +60 -14
  36. package/dist/skill-test/spawn-claude.js.map +1 -1
  37. package/dist/yaml/surgical-yaml.d.ts +71 -0
  38. package/dist/yaml/surgical-yaml.d.ts.map +1 -0
  39. package/dist/yaml/surgical-yaml.js +314 -0
  40. package/dist/yaml/surgical-yaml.js.map +1 -0
  41. 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;AAWD,wBAAgB,iBAAiB,CAC/B,MAAM,EAAE,MAAM,CAAC,UAAU,EACzB,IAAI,EAAE,iBAAiB,GACtB,MAAM,CAAC,UAAU,CAuBnB"}
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
- /** Process-essential vars that must survive for the child to even start. */
8
- const PROCESS_ESSENTIALS = ['PATH', 'Path', 'HOME', 'USERPROFILE', 'SystemRoot', 'TEMP', 'TMP', 'TMPDIR', 'LANG', 'LC_ALL'];
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,4EAA4E;AAC5E,MAAM,kBAAkB,GAAG,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,aAAa,EAAE,YAAY,EAAE,MAAM,EAAE,KAAK,EAAE,QAAQ,EAAE,MAAM,EAAE,QAAQ,CAAU,CAAC;AAErI,gDAAgD;AAChD,MAAM,qBAAqB,GAAG,CAAC,mBAAmB,CAAU,CAAC;AAE7D,8EAA8E;AAC9E,MAAM,qBAAqB,GAAG,CAAC,mBAAmB,EAAE,sBAAsB,CAAU,CAAC;AAErF,MAAM,UAAU,iBAAiB,CAC/B,MAAyB,EACzB,IAAuB;IAEvB,MAAM,GAAG,GAAsB,EAAE,CAAC;IAElC,MAAM,KAAK,GAAG,IAAI,GAAG,CAAS;QAC5B,GAAG,kBAAkB;QACrB,GAAG,qBAAqB;QACxB,GAAG,CAAC,IAAI,CAAC,SAAS,IAAI,EAAE,CAAC;KAC1B,CAAC,CAAC;IACH,IAAI,CAAC,IAAI,CAAC,iBAAiB,EAAE,CAAC;QAC5B,KAAK,MAAM,IAAI,IAAI,qBAAqB;YAAE,KAAK,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;IAC5D,CAAC;IAED,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;QACzB,MAAM,KAAK,GAAG,MAAM,CAAC,IAAI,CAAC,CAAC;QAC3B,IAAI,KAAK,KAAK,SAAS;YAAE,GAAG,CAAC,IAAI,CAAC,GAAG,KAAK,CAAC;IAC7C,CAAC;IAED,6EAA6E;IAC7E,8EAA8E;IAC9E,6EAA6E;IAC7E,OAAO,GAAG,CAAC,yBAAyB,CAAC,CAAC;IAEtC,OAAO,GAAG,CAAC;AACb,CAAC"}
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,EAAE,iBAAiB,EAAE,KAAK,iBAAiB,EAAE,MAAM,gBAAgB,CAAC;AAC3E,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,mBAAmB,EACnB,KAAK,eAAe,EACpB,KAAK,oBAAoB,EACzB,KAAK,WAAW,GACjB,MAAM,mBAAmB,CAAC"}
@@ -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,EAAE,iBAAiB,EAA0B,MAAM,gBAAgB,CAAC;AAC3E,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,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
- * Path to the experimenter-prompt file. Its contents are streamed to the
24
- * child's stdin (claude 2.x reads the `-p` prompt from stdin; there is no
25
- * `--prompt-file` flag). Kept off argv so the prompt is never inlined
26
- * (Windows cmd-quoting safety).
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
- promptFile: string;
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 file is streamed to the child's stdin
39
- * so the session reads its prompt and then sees EOF (it cannot block on input,
40
- * §16). Wall-clock kill on timeout. If `stallMs` is set, a resettable stall
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;;;;;OAKG;IACH,UAAU,EAAE,MAAM,CAAC;IACnB,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;CACpC;AAED;;;;;;GAMG;AACH,wBAAsB,mBAAmB,CAAC,IAAI,EAAE,oBAAoB,GAAG,OAAO,CAAC,WAAW,CAAC,CAsD1F"}
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 { createReadStream } from 'node:fs';
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
- * Spawn the headless `claude`. The prompt file is streamed to the child's stdin
36
- * so the session reads its prompt and then sees EOF (it cannot block on input,
37
- * §16). Wall-clock kill on timeout. If `stallMs` is set, a resettable stall
38
- * watchdog kills the child when no output is received for that duration
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
- const bin = which.sync('claude');
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, { cwd: opts.cwd, env: opts.env, stdio: ['pipe', 'pipe', 'pipe'] });
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.kill('SIGKILL');
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.kill('SIGKILL');
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.kill('SIGKILL');
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":"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"}
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"}