@vibe-agent-toolkit/utils 0.1.41 → 0.1.42-rc.1
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/README.md +146 -31
- package/dist/asset.d.ts +9 -0
- package/dist/asset.d.ts.map +1 -0
- package/dist/asset.js +9 -0
- package/dist/asset.js.map +1 -0
- package/dist/crawl.d.ts +19 -0
- package/dist/crawl.d.ts.map +1 -0
- package/dist/crawl.js +19 -0
- package/dist/crawl.js.map +1 -0
- package/dist/file-crawler.d.ts +19 -2
- package/dist/file-crawler.d.ts.map +1 -1
- package/dist/file-crawler.js +19 -1
- package/dist/file-crawler.js.map +1 -1
- package/dist/fs-utils.d.ts +60 -3
- package/dist/fs-utils.d.ts.map +1 -1
- package/dist/fs-utils.js +88 -10
- package/dist/fs-utils.js.map +1 -1
- package/dist/fs.d.ts +7 -6
- package/dist/fs.d.ts.map +1 -1
- package/dist/fs.js +7 -6
- package/dist/fs.js.map +1 -1
- package/dist/git-utils.d.ts +2 -0
- package/dist/git-utils.d.ts.map +1 -1
- package/dist/git-utils.js +16 -0
- package/dist/git-utils.js.map +1 -1
- package/dist/git.d.ts +23 -0
- package/dist/git.d.ts.map +1 -0
- package/dist/git.js +23 -0
- package/dist/git.js.map +1 -0
- package/dist/gitignore-checker.d.ts +0 -9
- package/dist/gitignore-checker.d.ts.map +1 -1
- package/dist/gitignore-checker.js +0 -12
- package/dist/gitignore-checker.js.map +1 -1
- package/dist/glob/glob-pattern.js +1 -1
- package/dist/glob/glob-pattern.js.map +1 -1
- package/dist/glob.d.ts +8 -0
- package/dist/glob.d.ts.map +1 -0
- package/dist/glob.js +8 -0
- package/dist/glob.js.map +1 -0
- package/dist/path-core.d.ts +196 -0
- package/dist/path-core.d.ts.map +1 -0
- package/dist/path-core.js +251 -0
- package/dist/path-core.js.map +1 -0
- package/dist/path-utils.d.ts +12 -183
- package/dist/path-utils.d.ts.map +1 -1
- package/dist/path-utils.js +13 -237
- package/dist/path-utils.js.map +1 -1
- package/dist/path.d.ts +12 -0
- package/dist/path.d.ts.map +1 -0
- package/dist/path.js +12 -0
- package/dist/path.js.map +1 -0
- package/dist/process.d.ts +7 -4
- package/dist/process.d.ts.map +1 -1
- package/dist/process.js +7 -4
- package/dist/process.js.map +1 -1
- package/dist/project.d.ts +31 -0
- package/dist/project.d.ts.map +1 -0
- package/dist/project.js +31 -0
- package/dist/project.js.map +1 -0
- package/dist/safe-exec.d.ts.map +1 -1
- package/dist/safe-exec.js +8 -4
- package/dist/safe-exec.js.map +1 -1
- package/dist/spawn-hardened.d.ts.map +1 -1
- package/dist/spawn-hardened.js +4 -17
- package/dist/spawn-hardened.js.map +1 -1
- package/dist/template-entry.d.ts +10 -0
- package/dist/template-entry.d.ts.map +1 -0
- package/dist/template-entry.js +10 -0
- package/dist/template-entry.js.map +1 -0
- package/dist/testing.d.ts +8 -0
- package/dist/testing.d.ts.map +1 -0
- package/dist/testing.js +8 -0
- package/dist/testing.js.map +1 -0
- package/dist/windows-shell.d.ts +140 -8
- package/dist/windows-shell.d.ts.map +1 -1
- package/dist/windows-shell.js +218 -11
- package/dist/windows-shell.js.map +1 -1
- package/dist/yaml.d.ts +9 -0
- package/dist/yaml.d.ts.map +1 -0
- package/dist/yaml.js +9 -0
- package/dist/yaml.js.map +1 -0
- package/dist/zod.d.ts +10 -0
- package/dist/zod.d.ts.map +1 -0
- package/dist/zod.js +10 -0
- package/dist/zod.js.map +1 -0
- package/eslint/README.md +191 -0
- package/eslint/index.cjs +161 -0
- package/eslint/index.d.cts +60 -0
- package/eslint/rules/eslint-rule-factory.cjs +241 -0
- package/eslint/rules/exempt-path-matcher.cjs +265 -0
- package/eslint/rules/no-bare-dynamic-import-path.cjs +142 -0
- package/eslint/rules/no-child-process-execSync.cjs +23 -0
- package/eslint/rules/no-command-direct-factory.cjs +246 -0
- package/eslint/rules/no-file-url-string-concat.cjs +77 -0
- package/eslint/rules/no-fs-mkdirSync.cjs +23 -0
- package/eslint/rules/no-fs-promises-cp.cjs +36 -0
- package/eslint/rules/no-fs-realpathSync.cjs +23 -0
- package/eslint/rules/no-hardcoded-path-split.cjs +141 -0
- package/eslint/rules/no-manual-path-normalize.cjs +130 -0
- package/eslint/rules/no-os-tmpdir.cjs +24 -0
- package/eslint/rules/no-path-join.cjs +14 -0
- package/eslint/rules/no-path-operations-in-comparisons.cjs +148 -0
- package/eslint/rules/no-path-relative.cjs +14 -0
- package/eslint/rules/no-path-resolve.cjs +14 -0
- package/eslint/rules/no-path-sep-in-strings.cjs +130 -0
- package/eslint/rules/no-path-startswith.cjs +139 -0
- package/eslint/rules/no-test-scoped-functions.cjs +134 -0
- package/eslint/rules/no-unix-shell-commands.cjs +152 -0
- package/eslint/rules/no-unsafe-root-join.cjs +85 -0
- package/eslint/rules/no-url-pathname-for-fs.cjs +107 -0
- package/eslint/rules/path-function-rule-factory.cjs +216 -0
- package/eslint/rules/prefer-startswith-over-regex.cjs +111 -0
- package/eslint/rules/require-justified-skip.cjs +348 -0
- package/eslint/rules/safe-import.cjs +141 -0
- package/package.json +65 -7
package/dist/windows-shell.d.ts
CHANGED
|
@@ -10,6 +10,15 @@
|
|
|
10
10
|
* npm-installed CLIs (`claude`, `npm`, `git` shims, …) resolve to exactly such a
|
|
11
11
|
* wrapper on Windows, so any spawn that omits this handling crashes on Windows only.
|
|
12
12
|
*/
|
|
13
|
+
/**
|
|
14
|
+
* A `command` is treated as an explicit path (used verbatim, never PATH-resolved)
|
|
15
|
+
* when it is absolute or contains a path separator. A bare name (`claude`, `npm`,
|
|
16
|
+
* `git`) is resolved on PATH via `which.sync`. This mirrors — and preserves — the
|
|
17
|
+
* behaviour callers relied on before hardening: an explicit `binPath` is spawned
|
|
18
|
+
* as-given (so a nonexistent path still surfaces as an async `'error'` event, not a
|
|
19
|
+
* synchronous `which` throw), while a bare command is looked up.
|
|
20
|
+
*/
|
|
21
|
+
export declare function isPathLike(command: string): boolean;
|
|
13
22
|
/**
|
|
14
23
|
* Determine if shell should be used for command execution on Windows.
|
|
15
24
|
*
|
|
@@ -32,13 +41,28 @@
|
|
|
32
41
|
* **Root Cause of Previous ENOENT Issues:** Likely resolved in newer Node.js versions.
|
|
33
42
|
* Current testing (Node 20+) shows shell:false works correctly with absolute path.
|
|
34
43
|
*
|
|
35
|
-
* ## Why This Is Secure
|
|
44
|
+
* ## Why This Is Secure — and where it is only *quoted*, not secure
|
|
45
|
+
*
|
|
46
|
+
* There are two modes, and they have genuinely different properties. Describing them as
|
|
47
|
+
* one ("arguments are always an array", "we never interpolate") would be false for this
|
|
48
|
+
* module's own Windows branch, which does exactly that interpolation.
|
|
36
49
|
*
|
|
37
|
-
* 1. **Minimal Shell Usage:**
|
|
38
|
-
*
|
|
39
|
-
*
|
|
40
|
-
*
|
|
41
|
-
*
|
|
50
|
+
* 1. **Minimal Shell Usage:** the shell is used ONLY when this function returns true —
|
|
51
|
+
* `.cmd` / `.bat` / `.ps1`, where a shell interpreter is required by design.
|
|
52
|
+
* 2. **Path Validation:** command paths are resolved via `which.sync()` before execution.
|
|
53
|
+
* 3. **Two modes:**
|
|
54
|
+
* - **`shell: false` (every other command — the default):** arguments are handed to
|
|
55
|
+
* `spawn`/`spawnSync` as a real argv array. Nothing re-parses them, so there is no
|
|
56
|
+
* metacharacter surface at all. This is the mode that is *secure*.
|
|
57
|
+
* - **`shell: true` (this function returning true):** Node's DEP0190 forbids an args
|
|
58
|
+
* array in shell mode, so {@link buildWindowsShellLine} MUST concatenate the command
|
|
59
|
+
* and its arguments into a single string. Safety there rests entirely on
|
|
60
|
+
* {@link windowsShellQuote}, which *quotes* — it does not neutralize. `%VAR%` still
|
|
61
|
+
* expands inside double quotes, and an argument containing a `"` unavoidably desyncs
|
|
62
|
+
* `cmd.exe`'s quote tracking (that function documents the two-parser trade in full).
|
|
63
|
+
* 4. **Controlled Environment:** commands and arguments come from trusted configuration.
|
|
64
|
+
* On the `shell: true` branch that is a *requirement*, not a nicety — do not route
|
|
65
|
+
* untrusted input through it; spawn with `shell: false` and an args array instead.
|
|
42
66
|
*
|
|
43
67
|
* ## References
|
|
44
68
|
*
|
|
@@ -53,10 +77,86 @@ export declare function shouldUseShell(commandPath: string): boolean;
|
|
|
53
77
|
/**
|
|
54
78
|
* Quote a single argument for a Windows `cmd.exe` command line.
|
|
55
79
|
*
|
|
56
|
-
* Rules
|
|
80
|
+
* Rules (the canonical `CommandLineToArgvW` algorithm — the same one used by .NET's
|
|
81
|
+
* `PasteArguments`, Python's `subprocess.list2cmdline`, and Rust's `Command`):
|
|
57
82
|
* - An arg that's empty, or contains whitespace, quotes, or any of `& | < > ^ ( ) % !`,
|
|
58
83
|
* gets wrapped in double quotes.
|
|
59
|
-
* -
|
|
84
|
+
* - Every run of backslashes that immediately precedes a `"` — an embedded one OR the
|
|
85
|
+
* closing one — is doubled.
|
|
86
|
+
* - Embedded double quotes are escaped as `\"`.
|
|
87
|
+
*
|
|
88
|
+
* ## Why backslash runs must be doubled before EVERY quote, not just the last one
|
|
89
|
+
*
|
|
90
|
+
* `CommandLineToArgvW` — what the launched program uses to split the line back into argv —
|
|
91
|
+
* reads a backslash run only in relation to the character that follows it: `2n`
|
|
92
|
+
* backslashes before a `"` mean `n` literal backslashes plus a *delimiter* quote; `2n+1`
|
|
93
|
+
* mean `n` literal backslashes plus a *literal* quote; a run followed by anything else is
|
|
94
|
+
* literal. So a backslash sitting in front of a quote silently changes what that quote
|
|
95
|
+
* means, and the parse desynchronizes from there — the argument swallows the rest of the
|
|
96
|
+
* line, or a backslash vanishes.
|
|
97
|
+
*
|
|
98
|
+
* The everyday cases are not exotic: `C:\Program Files\` (a directory path with a trailing
|
|
99
|
+
* separator and a space) and `{"dir":"C:\\tools\\"}` (JSON carrying a Windows path). A
|
|
100
|
+
* previous version of this function handled only the *trailing* run and escaped quotes as
|
|
101
|
+
* `""`. Replaying it through the reference parser in `test/windows-shell.test.ts` over every
|
|
102
|
+
* string in `{a, \, ", space, %}` of length ≤ 4 corrupted 85 of 781 inputs, 74 of which
|
|
103
|
+
* swallowed the following argument; every failure contained a backslash-quote sequence. That
|
|
104
|
+
* harness now runs on every commit, and asserts 0 of 781.
|
|
105
|
+
*
|
|
106
|
+
* ## TWO PARSERS IN SERIES: why `\"` and not `""` (the deliberate trade)
|
|
107
|
+
*
|
|
108
|
+
* The string this function emits is consumed by two different parsers, in order:
|
|
109
|
+
*
|
|
110
|
+
* 1. **`cmd.exe`**, which has NO backslash escape at all. It toggles a quote flag on
|
|
111
|
+
* every `"` it sees, and only text inside that flag is protected from `& | < > ^ ( )`.
|
|
112
|
+
* 2. **`CommandLineToArgvW`** (or the CRT's equivalent) in the process the `.cmd`/`.bat`
|
|
113
|
+
* shim ultimately launches. The dominant real case is an npm shim forwarding `%*` to
|
|
114
|
+
* a real `.exe`, and `%*` is the *verbatim* remaining text — so whatever we emit here
|
|
115
|
+
* is exactly what that second parser sees.
|
|
116
|
+
*
|
|
117
|
+
* These two want opposite things, and no single byte sequence satisfies both:
|
|
118
|
+
*
|
|
119
|
+
* - `""` keeps `cmd.exe`'s quote count even, so metacharacters stay protected — but its
|
|
120
|
+
* meaning to the second parser is version- and implementation-dependent. It is absent
|
|
121
|
+
* from `CommandLineToArgvW`'s documented ruleset (which specifies only the backslash
|
|
122
|
+
* rules), and under the widely-mirrored `CommandLineToArgvW` implementation — where a
|
|
123
|
+
* run of three quotes yields one literal quote and *resets* the quote state — the token
|
|
124
|
+
* `"a""b"` leaves the parser back inside quotes, so the following space stops
|
|
125
|
+
* separating arguments and the next argument is swallowed.
|
|
126
|
+
* - `\"` is understood identically by every implementation there is (CommandLineToArgvW,
|
|
127
|
+
* old and new CRT, .NET, Python, Rust, Wine). But `cmd.exe` does not honour the
|
|
128
|
+
* backslash, so each embedded quote flips its quote flag: within an argument that
|
|
129
|
+
* contains a `"`, the span following that quote is *outside* cmd's quotes and a
|
|
130
|
+
* metacharacter there would be re-interpreted.
|
|
131
|
+
*
|
|
132
|
+
* **The trade made here: `\"`.** Delivering the argument to the child process byte-exact is
|
|
133
|
+
* the job of this function, and `\"` is the only form with no parser-variant risk. The cost
|
|
134
|
+
* is bounded and stated plainly: for an argument containing BOTH a `"` and one of
|
|
135
|
+
* `& | < > ^ ( )`, `cmd.exe` may act on that metacharacter. Arguments with no embedded
|
|
136
|
+
* quote — which is every path, and the trailing-backslash case that motivated all of this —
|
|
137
|
+
* emit exactly two quotes and keep cmd's tracking balanced, so they are unaffected.
|
|
138
|
+
*
|
|
139
|
+
* (Rust makes the opposite trade in `append_bat_arg`, emitting `""`. Its target is a batch
|
|
140
|
+
* file reading `%~1` *itself*, where cmd is the only parser and injection is the whole
|
|
141
|
+
* threat model. Ours is a shim forwarding `%*` to an `.exe`, where the second parser is the
|
|
142
|
+
* one that decides what the program actually receives. Same two parsers, different consumer,
|
|
143
|
+
* different answer.)
|
|
144
|
+
*
|
|
145
|
+
* ## These characters are QUOTED, NOT NEUTRALIZED
|
|
146
|
+
*
|
|
147
|
+
* Wrapping in double quotes stops `cmd.exe` from re-interpreting whitespace and the
|
|
148
|
+
* redirection/pipe/grouping metacharacters — but it does **not** stop variable
|
|
149
|
+
* expansion. `cmd.exe` expands `%VAR%` *inside* double quotes, so:
|
|
150
|
+
*
|
|
151
|
+
* - an arg containing `%PATH%` is still substituted before the program sees it, and
|
|
152
|
+
* - a literal `%` in a filename (legal on Windows) can be corrupted by that pass.
|
|
153
|
+
*
|
|
154
|
+
* `%` is in the trigger class only so such args get quoted at all; treat the value as
|
|
155
|
+
* still shell-visible. `!` has the same shape under delayed expansion (`!VAR!`) — that
|
|
156
|
+
* is off by default for `cmd /c`, so it is a caveat rather than a live bug, but a
|
|
157
|
+
* caller that enables `ENABLEDELAYEDEXPANSION` inherits the problem. If an argument
|
|
158
|
+
* must survive verbatim, do not route it through a shell at all: spawn with
|
|
159
|
+
* `shell: false` and a real args array (see {@link shouldUseShell}).
|
|
60
160
|
*
|
|
61
161
|
* This is narrow on purpose: it's only used when we're forced to assemble a shell string
|
|
62
162
|
* for `.cmd` / `.bat` wrappers on Windows (DEP0190 path). Callers still control what's
|
|
@@ -64,12 +164,44 @@ export declare function shouldUseShell(commandPath: string): boolean;
|
|
|
64
164
|
* re-interpreted by the shell.
|
|
65
165
|
*/
|
|
66
166
|
export declare function windowsShellQuote(arg: string): string;
|
|
167
|
+
/**
|
|
168
|
+
* Choose the `commandToken` to hand {@link buildWindowsShellLine} on the Windows shell
|
|
169
|
+
* path. This is the ONE definition of that choice — both spawn wrappers
|
|
170
|
+
* ({@link ./safe-exec.ts} and {@link ./spawn-hardened.ts}) call it, so the two paths are
|
|
171
|
+
* provably identical rather than merely similar. They previously disagreed: the sync path
|
|
172
|
+
* emitted a raw, unquoted command, which silently broke on any explicit path containing a
|
|
173
|
+
* space (and now throws, thanks to the assertion in `buildWindowsShellLine`).
|
|
174
|
+
*
|
|
175
|
+
* Two cases, and they need opposite treatment:
|
|
176
|
+
*
|
|
177
|
+
* - **Bare PATH name** (`claude`, `npm`) — emitted verbatim, NOT quoted and NOT replaced
|
|
178
|
+
* by the resolved path, because `cmd.exe` must be left to re-resolve it through
|
|
179
|
+
* `PATHEXT`. A bare name has no whitespace, so it never trips the assertion.
|
|
180
|
+
* - **Explicit path** (absolute, or containing a separator) — the RESOLVED path, run
|
|
181
|
+
* through {@link windowsShellQuote} so a directory with a space (`C:\Program Files\…`)
|
|
182
|
+
* stays one token, and a trailing backslash cannot escape the closing quote and
|
|
183
|
+
* swallow the next argument.
|
|
184
|
+
*
|
|
185
|
+
* @param command - The command as the caller supplied it (decides bare vs. explicit).
|
|
186
|
+
* @param resolvedPath - The path actually being launched (what gets quoted, when quoted).
|
|
187
|
+
*/
|
|
188
|
+
export declare function resolveShellCommandToken(command: string, resolvedPath: string): string;
|
|
67
189
|
/**
|
|
68
190
|
* Build the single `cmd.exe` command line used when a Windows shim forces shell mode
|
|
69
191
|
* (Node's DEP0190 rejects `shell: true` with a separate args array, so command + args
|
|
70
192
|
* must be one string). `commandToken` is the already-resolved command — a bare PATH
|
|
71
193
|
* name that `cmd.exe` re-resolves via PATHEXT, or a pre-quoted explicit path — and each
|
|
72
194
|
* arg is quoted via {@link windowsShellQuote} so shell metacharacters can't re-interpret.
|
|
195
|
+
*
|
|
196
|
+
* `commandToken` is emitted verbatim (quoting it here would defeat the PATHEXT lookup a
|
|
197
|
+
* bare name relies on), so quoting it is the caller's job — see
|
|
198
|
+
* {@link resolveShellCommandToken}, which is how every caller should produce it. An
|
|
199
|
+
* unquoted path containing a space — `C:\Program Files\tool.cmd` — would otherwise produce
|
|
200
|
+
* a silently broken line where `cmd.exe` tries to run `C:\Program` with `Files\tool.cmd` as
|
|
201
|
+
* its first argument. Those cases throw rather than emitting the broken line.
|
|
202
|
+
*
|
|
203
|
+
* @throws {Error} if `commandToken` is not a single `cmd.exe` token (see
|
|
204
|
+
* {@link isSingleShellToken}). Pass it through {@link windowsShellQuote} first.
|
|
73
205
|
*/
|
|
74
206
|
export declare function buildWindowsShellLine(commandToken: string, args: string[]): string;
|
|
75
207
|
//# sourceMappingURL=windows-shell.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"windows-shell.d.ts","sourceRoot":"","sources":["../src/windows-shell.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;
|
|
1
|
+
{"version":3,"file":"windows-shell.d.ts","sourceRoot":"","sources":["../src/windows-shell.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAIH;;;;;;;GAOG;AACH,wBAAgB,UAAU,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAEnD;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAqDG;AACH,wBAAgB,cAAc,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAiB3D;AAUD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAwFG;AACH,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CA2BrD;AAED;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,wBAAgB,wBAAwB,CAAC,OAAO,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,GAAG,MAAM,CAEtF;AAsCD;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,qBAAqB,CAAC,YAAY,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,GAAG,MAAM,CAWlF"}
|
package/dist/windows-shell.js
CHANGED
|
@@ -10,6 +10,18 @@
|
|
|
10
10
|
* npm-installed CLIs (`claude`, `npm`, `git` shims, …) resolve to exactly such a
|
|
11
11
|
* wrapper on Windows, so any spawn that omits this handling crashes on Windows only.
|
|
12
12
|
*/
|
|
13
|
+
import { isAbsoluteAnyPlatform } from './path-core.js';
|
|
14
|
+
/**
|
|
15
|
+
* A `command` is treated as an explicit path (used verbatim, never PATH-resolved)
|
|
16
|
+
* when it is absolute or contains a path separator. A bare name (`claude`, `npm`,
|
|
17
|
+
* `git`) is resolved on PATH via `which.sync`. This mirrors — and preserves — the
|
|
18
|
+
* behaviour callers relied on before hardening: an explicit `binPath` is spawned
|
|
19
|
+
* as-given (so a nonexistent path still surfaces as an async `'error'` event, not a
|
|
20
|
+
* synchronous `which` throw), while a bare command is looked up.
|
|
21
|
+
*/
|
|
22
|
+
export function isPathLike(command) {
|
|
23
|
+
return isAbsoluteAnyPlatform(command) || command.includes('/') || command.includes('\\');
|
|
24
|
+
}
|
|
13
25
|
/**
|
|
14
26
|
* Determine if shell should be used for command execution on Windows.
|
|
15
27
|
*
|
|
@@ -32,13 +44,28 @@
|
|
|
32
44
|
* **Root Cause of Previous ENOENT Issues:** Likely resolved in newer Node.js versions.
|
|
33
45
|
* Current testing (Node 20+) shows shell:false works correctly with absolute path.
|
|
34
46
|
*
|
|
35
|
-
* ## Why This Is Secure
|
|
47
|
+
* ## Why This Is Secure — and where it is only *quoted*, not secure
|
|
36
48
|
*
|
|
37
|
-
*
|
|
38
|
-
*
|
|
39
|
-
*
|
|
40
|
-
*
|
|
41
|
-
*
|
|
49
|
+
* There are two modes, and they have genuinely different properties. Describing them as
|
|
50
|
+
* one ("arguments are always an array", "we never interpolate") would be false for this
|
|
51
|
+
* module's own Windows branch, which does exactly that interpolation.
|
|
52
|
+
*
|
|
53
|
+
* 1. **Minimal Shell Usage:** the shell is used ONLY when this function returns true —
|
|
54
|
+
* `.cmd` / `.bat` / `.ps1`, where a shell interpreter is required by design.
|
|
55
|
+
* 2. **Path Validation:** command paths are resolved via `which.sync()` before execution.
|
|
56
|
+
* 3. **Two modes:**
|
|
57
|
+
* - **`shell: false` (every other command — the default):** arguments are handed to
|
|
58
|
+
* `spawn`/`spawnSync` as a real argv array. Nothing re-parses them, so there is no
|
|
59
|
+
* metacharacter surface at all. This is the mode that is *secure*.
|
|
60
|
+
* - **`shell: true` (this function returning true):** Node's DEP0190 forbids an args
|
|
61
|
+
* array in shell mode, so {@link buildWindowsShellLine} MUST concatenate the command
|
|
62
|
+
* and its arguments into a single string. Safety there rests entirely on
|
|
63
|
+
* {@link windowsShellQuote}, which *quotes* — it does not neutralize. `%VAR%` still
|
|
64
|
+
* expands inside double quotes, and an argument containing a `"` unavoidably desyncs
|
|
65
|
+
* `cmd.exe`'s quote tracking (that function documents the two-parser trade in full).
|
|
66
|
+
* 4. **Controlled Environment:** commands and arguments come from trusted configuration.
|
|
67
|
+
* On the `shell: true` branch that is a *requirement*, not a nicety — do not route
|
|
68
|
+
* untrusted input through it; spawn with `shell: false` and an args array instead.
|
|
42
69
|
*
|
|
43
70
|
* ## References
|
|
44
71
|
*
|
|
@@ -65,13 +92,96 @@ export function shouldUseShell(commandPath) {
|
|
|
65
92
|
const lowerPath = commandPath.toLowerCase();
|
|
66
93
|
return lowerPath.endsWith('.cmd') || lowerPath.endsWith('.bat') || lowerPath.endsWith('.ps1');
|
|
67
94
|
}
|
|
95
|
+
/**
|
|
96
|
+
* Characters that force an argument to be wrapped in double quotes: whitespace, a quote,
|
|
97
|
+
* and `cmd.exe`'s metacharacters. Also the predicate {@link buildWindowsShellLine} uses to
|
|
98
|
+
* decide whether an *unquoted* command token is safe — "a token {@link windowsShellQuote}
|
|
99
|
+
* would have left alone" is exactly the token cmd.exe cannot split or re-interpret.
|
|
100
|
+
*/
|
|
101
|
+
const SHELL_QUOTE_TRIGGER = /["\s&|<>^()%!]/;
|
|
68
102
|
/**
|
|
69
103
|
* Quote a single argument for a Windows `cmd.exe` command line.
|
|
70
104
|
*
|
|
71
|
-
* Rules
|
|
105
|
+
* Rules (the canonical `CommandLineToArgvW` algorithm — the same one used by .NET's
|
|
106
|
+
* `PasteArguments`, Python's `subprocess.list2cmdline`, and Rust's `Command`):
|
|
72
107
|
* - An arg that's empty, or contains whitespace, quotes, or any of `& | < > ^ ( ) % !`,
|
|
73
108
|
* gets wrapped in double quotes.
|
|
74
|
-
* -
|
|
109
|
+
* - Every run of backslashes that immediately precedes a `"` — an embedded one OR the
|
|
110
|
+
* closing one — is doubled.
|
|
111
|
+
* - Embedded double quotes are escaped as `\"`.
|
|
112
|
+
*
|
|
113
|
+
* ## Why backslash runs must be doubled before EVERY quote, not just the last one
|
|
114
|
+
*
|
|
115
|
+
* `CommandLineToArgvW` — what the launched program uses to split the line back into argv —
|
|
116
|
+
* reads a backslash run only in relation to the character that follows it: `2n`
|
|
117
|
+
* backslashes before a `"` mean `n` literal backslashes plus a *delimiter* quote; `2n+1`
|
|
118
|
+
* mean `n` literal backslashes plus a *literal* quote; a run followed by anything else is
|
|
119
|
+
* literal. So a backslash sitting in front of a quote silently changes what that quote
|
|
120
|
+
* means, and the parse desynchronizes from there — the argument swallows the rest of the
|
|
121
|
+
* line, or a backslash vanishes.
|
|
122
|
+
*
|
|
123
|
+
* The everyday cases are not exotic: `C:\Program Files\` (a directory path with a trailing
|
|
124
|
+
* separator and a space) and `{"dir":"C:\\tools\\"}` (JSON carrying a Windows path). A
|
|
125
|
+
* previous version of this function handled only the *trailing* run and escaped quotes as
|
|
126
|
+
* `""`. Replaying it through the reference parser in `test/windows-shell.test.ts` over every
|
|
127
|
+
* string in `{a, \, ", space, %}` of length ≤ 4 corrupted 85 of 781 inputs, 74 of which
|
|
128
|
+
* swallowed the following argument; every failure contained a backslash-quote sequence. That
|
|
129
|
+
* harness now runs on every commit, and asserts 0 of 781.
|
|
130
|
+
*
|
|
131
|
+
* ## TWO PARSERS IN SERIES: why `\"` and not `""` (the deliberate trade)
|
|
132
|
+
*
|
|
133
|
+
* The string this function emits is consumed by two different parsers, in order:
|
|
134
|
+
*
|
|
135
|
+
* 1. **`cmd.exe`**, which has NO backslash escape at all. It toggles a quote flag on
|
|
136
|
+
* every `"` it sees, and only text inside that flag is protected from `& | < > ^ ( )`.
|
|
137
|
+
* 2. **`CommandLineToArgvW`** (or the CRT's equivalent) in the process the `.cmd`/`.bat`
|
|
138
|
+
* shim ultimately launches. The dominant real case is an npm shim forwarding `%*` to
|
|
139
|
+
* a real `.exe`, and `%*` is the *verbatim* remaining text — so whatever we emit here
|
|
140
|
+
* is exactly what that second parser sees.
|
|
141
|
+
*
|
|
142
|
+
* These two want opposite things, and no single byte sequence satisfies both:
|
|
143
|
+
*
|
|
144
|
+
* - `""` keeps `cmd.exe`'s quote count even, so metacharacters stay protected — but its
|
|
145
|
+
* meaning to the second parser is version- and implementation-dependent. It is absent
|
|
146
|
+
* from `CommandLineToArgvW`'s documented ruleset (which specifies only the backslash
|
|
147
|
+
* rules), and under the widely-mirrored `CommandLineToArgvW` implementation — where a
|
|
148
|
+
* run of three quotes yields one literal quote and *resets* the quote state — the token
|
|
149
|
+
* `"a""b"` leaves the parser back inside quotes, so the following space stops
|
|
150
|
+
* separating arguments and the next argument is swallowed.
|
|
151
|
+
* - `\"` is understood identically by every implementation there is (CommandLineToArgvW,
|
|
152
|
+
* old and new CRT, .NET, Python, Rust, Wine). But `cmd.exe` does not honour the
|
|
153
|
+
* backslash, so each embedded quote flips its quote flag: within an argument that
|
|
154
|
+
* contains a `"`, the span following that quote is *outside* cmd's quotes and a
|
|
155
|
+
* metacharacter there would be re-interpreted.
|
|
156
|
+
*
|
|
157
|
+
* **The trade made here: `\"`.** Delivering the argument to the child process byte-exact is
|
|
158
|
+
* the job of this function, and `\"` is the only form with no parser-variant risk. The cost
|
|
159
|
+
* is bounded and stated plainly: for an argument containing BOTH a `"` and one of
|
|
160
|
+
* `& | < > ^ ( )`, `cmd.exe` may act on that metacharacter. Arguments with no embedded
|
|
161
|
+
* quote — which is every path, and the trailing-backslash case that motivated all of this —
|
|
162
|
+
* emit exactly two quotes and keep cmd's tracking balanced, so they are unaffected.
|
|
163
|
+
*
|
|
164
|
+
* (Rust makes the opposite trade in `append_bat_arg`, emitting `""`. Its target is a batch
|
|
165
|
+
* file reading `%~1` *itself*, where cmd is the only parser and injection is the whole
|
|
166
|
+
* threat model. Ours is a shim forwarding `%*` to an `.exe`, where the second parser is the
|
|
167
|
+
* one that decides what the program actually receives. Same two parsers, different consumer,
|
|
168
|
+
* different answer.)
|
|
169
|
+
*
|
|
170
|
+
* ## These characters are QUOTED, NOT NEUTRALIZED
|
|
171
|
+
*
|
|
172
|
+
* Wrapping in double quotes stops `cmd.exe` from re-interpreting whitespace and the
|
|
173
|
+
* redirection/pipe/grouping metacharacters — but it does **not** stop variable
|
|
174
|
+
* expansion. `cmd.exe` expands `%VAR%` *inside* double quotes, so:
|
|
175
|
+
*
|
|
176
|
+
* - an arg containing `%PATH%` is still substituted before the program sees it, and
|
|
177
|
+
* - a literal `%` in a filename (legal on Windows) can be corrupted by that pass.
|
|
178
|
+
*
|
|
179
|
+
* `%` is in the trigger class only so such args get quoted at all; treat the value as
|
|
180
|
+
* still shell-visible. `!` has the same shape under delayed expansion (`!VAR!`) — that
|
|
181
|
+
* is off by default for `cmd /c`, so it is a caveat rather than a live bug, but a
|
|
182
|
+
* caller that enables `ENABLEDELAYEDEXPANSION` inherits the problem. If an argument
|
|
183
|
+
* must survive verbatim, do not route it through a shell at all: spawn with
|
|
184
|
+
* `shell: false` and a real args array (see {@link shouldUseShell}).
|
|
75
185
|
*
|
|
76
186
|
* This is narrow on purpose: it's only used when we're forced to assemble a shell string
|
|
77
187
|
* for `.cmd` / `.bat` wrappers on Windows (DEP0190 path). Callers still control what's
|
|
@@ -79,10 +189,90 @@ export function shouldUseShell(commandPath) {
|
|
|
79
189
|
* re-interpreted by the shell.
|
|
80
190
|
*/
|
|
81
191
|
export function windowsShellQuote(arg) {
|
|
82
|
-
if (arg
|
|
83
|
-
return
|
|
192
|
+
if (arg !== '' && !SHELL_QUOTE_TRIGGER.test(arg)) {
|
|
193
|
+
return arg;
|
|
84
194
|
}
|
|
85
|
-
|
|
195
|
+
// Walk the argument, holding each backslash run until we see what follows it: a run
|
|
196
|
+
// before a quote (embedded, or the closing one) is doubled so the quote keeps its own
|
|
197
|
+
// meaning; a run before anything else is literal and emitted as-is.
|
|
198
|
+
let quoted = '"';
|
|
199
|
+
let backslashes = 0;
|
|
200
|
+
for (const char of arg) {
|
|
201
|
+
if (char === '\\') {
|
|
202
|
+
backslashes += 1;
|
|
203
|
+
continue;
|
|
204
|
+
}
|
|
205
|
+
if (char === '"') {
|
|
206
|
+
// 2n backslashes (now literal) + one more to escape the quote => 2n+1.
|
|
207
|
+
quoted += '\\'.repeat(backslashes * 2 + 1) + '"';
|
|
208
|
+
backslashes = 0;
|
|
209
|
+
continue;
|
|
210
|
+
}
|
|
211
|
+
quoted += '\\'.repeat(backslashes) + char;
|
|
212
|
+
backslashes = 0;
|
|
213
|
+
}
|
|
214
|
+
// Trailing run: doubled, so the closing quote terminates the argument instead of being
|
|
215
|
+
// escaped by it (`"C:\Program Files\"` would otherwise swallow the next argument).
|
|
216
|
+
return `${quoted}${'\\'.repeat(backslashes * 2)}"`;
|
|
217
|
+
}
|
|
218
|
+
/**
|
|
219
|
+
* Choose the `commandToken` to hand {@link buildWindowsShellLine} on the Windows shell
|
|
220
|
+
* path. This is the ONE definition of that choice — both spawn wrappers
|
|
221
|
+
* ({@link ./safe-exec.ts} and {@link ./spawn-hardened.ts}) call it, so the two paths are
|
|
222
|
+
* provably identical rather than merely similar. They previously disagreed: the sync path
|
|
223
|
+
* emitted a raw, unquoted command, which silently broke on any explicit path containing a
|
|
224
|
+
* space (and now throws, thanks to the assertion in `buildWindowsShellLine`).
|
|
225
|
+
*
|
|
226
|
+
* Two cases, and they need opposite treatment:
|
|
227
|
+
*
|
|
228
|
+
* - **Bare PATH name** (`claude`, `npm`) — emitted verbatim, NOT quoted and NOT replaced
|
|
229
|
+
* by the resolved path, because `cmd.exe` must be left to re-resolve it through
|
|
230
|
+
* `PATHEXT`. A bare name has no whitespace, so it never trips the assertion.
|
|
231
|
+
* - **Explicit path** (absolute, or containing a separator) — the RESOLVED path, run
|
|
232
|
+
* through {@link windowsShellQuote} so a directory with a space (`C:\Program Files\…`)
|
|
233
|
+
* stays one token, and a trailing backslash cannot escape the closing quote and
|
|
234
|
+
* swallow the next argument.
|
|
235
|
+
*
|
|
236
|
+
* @param command - The command as the caller supplied it (decides bare vs. explicit).
|
|
237
|
+
* @param resolvedPath - The path actually being launched (what gets quoted, when quoted).
|
|
238
|
+
*/
|
|
239
|
+
export function resolveShellCommandToken(command, resolvedPath) {
|
|
240
|
+
return isPathLike(command) ? windowsShellQuote(resolvedPath) : command;
|
|
241
|
+
}
|
|
242
|
+
/**
|
|
243
|
+
* Is `token` a single `cmd.exe` token — one thing cmd will treat as the command, with no
|
|
244
|
+
* way for it to split or re-interpret the rest?
|
|
245
|
+
*
|
|
246
|
+
* Two admissible shapes, and nothing else:
|
|
247
|
+
*
|
|
248
|
+
* - **Unquoted:** a token {@link windowsShellQuote} would have left alone — no
|
|
249
|
+
* whitespace, no quote, no metacharacter. A bare PATH name (`claude`, `npm`) or a
|
|
250
|
+
* space-free path.
|
|
251
|
+
* - **Quoted:** exactly ONE balanced outer pair with no further quote inside it, e.g.
|
|
252
|
+
* `"C:\Program Files\tool.cmd"`. Stripping that pair must leave a body cmd cannot
|
|
253
|
+
* escape from.
|
|
254
|
+
*
|
|
255
|
+
* The interior-quote check is the whole point. The previous predicate was "starts with `"`
|
|
256
|
+
* and ends with `"`", which is true of an entire crafted command line —
|
|
257
|
+
* `"a b" && calc "x"` — so a caller could hand over something that runs `calc` and this
|
|
258
|
+
* function would wave it through as "already quoted".
|
|
259
|
+
*
|
|
260
|
+
* The empty string is neither shape, and needs saying because it slipped between both
|
|
261
|
+
* branches: `''` does not start with `"`, and `SHELL_QUOTE_TRIGGER` cannot match a string
|
|
262
|
+
* with no characters, so the metacharacter branch returned true. `buildWindowsShellLine('',
|
|
263
|
+
* ['calc', 'b'])` was accepted and emitted `" calc b"` — a line whose first cmd.exe token
|
|
264
|
+
* is `calc`, i.e. the caller's first *argument* silently promoted into the command
|
|
265
|
+
* position. The explicit length check is what makes "one token" mean *one*, not *at most
|
|
266
|
+
* one*. (Whitespace-only tokens were already rejected — `\s` is in the trigger class.)
|
|
267
|
+
*/
|
|
268
|
+
function isSingleShellToken(token) {
|
|
269
|
+
if (token.length === 0) {
|
|
270
|
+
return false;
|
|
271
|
+
}
|
|
272
|
+
if (token.startsWith('"')) {
|
|
273
|
+
return token.length >= 2 && token.endsWith('"') && !token.slice(1, -1).includes('"');
|
|
274
|
+
}
|
|
275
|
+
return !SHELL_QUOTE_TRIGGER.test(token);
|
|
86
276
|
}
|
|
87
277
|
/**
|
|
88
278
|
* Build the single `cmd.exe` command line used when a Windows shim forces shell mode
|
|
@@ -90,8 +280,25 @@ export function windowsShellQuote(arg) {
|
|
|
90
280
|
* must be one string). `commandToken` is the already-resolved command — a bare PATH
|
|
91
281
|
* name that `cmd.exe` re-resolves via PATHEXT, or a pre-quoted explicit path — and each
|
|
92
282
|
* arg is quoted via {@link windowsShellQuote} so shell metacharacters can't re-interpret.
|
|
283
|
+
*
|
|
284
|
+
* `commandToken` is emitted verbatim (quoting it here would defeat the PATHEXT lookup a
|
|
285
|
+
* bare name relies on), so quoting it is the caller's job — see
|
|
286
|
+
* {@link resolveShellCommandToken}, which is how every caller should produce it. An
|
|
287
|
+
* unquoted path containing a space — `C:\Program Files\tool.cmd` — would otherwise produce
|
|
288
|
+
* a silently broken line where `cmd.exe` tries to run `C:\Program` with `Files\tool.cmd` as
|
|
289
|
+
* its first argument. Those cases throw rather than emitting the broken line.
|
|
290
|
+
*
|
|
291
|
+
* @throws {Error} if `commandToken` is not a single `cmd.exe` token (see
|
|
292
|
+
* {@link isSingleShellToken}). Pass it through {@link windowsShellQuote} first.
|
|
93
293
|
*/
|
|
94
294
|
export function buildWindowsShellLine(commandToken, args) {
|
|
295
|
+
if (!isSingleShellToken(commandToken)) {
|
|
296
|
+
throw new Error(`buildWindowsShellLine: commandToken is not a single cmd.exe token: ${commandToken}. ` +
|
|
297
|
+
'It must be either a bare name with no whitespace/quote/metacharacter, or one ' +
|
|
298
|
+
'balanced double-quoted token with no quote inside it. Wrap it with ' +
|
|
299
|
+
'windowsShellQuote() first — cmd.exe would otherwise split it or run part of it ' +
|
|
300
|
+
'as a separate command.');
|
|
301
|
+
}
|
|
95
302
|
return `${commandToken} ${args.map(windowsShellQuote).join(' ')}`;
|
|
96
303
|
}
|
|
97
304
|
//# sourceMappingURL=windows-shell.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"windows-shell.js","sourceRoot":"","sources":["../src/windows-shell.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH
|
|
1
|
+
{"version":3,"file":"windows-shell.js","sourceRoot":"","sources":["../src/windows-shell.ts"],"names":[],"mappings":"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"}
|
package/dist/yaml.d.ts
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @vibe-agent-toolkit/utils/yaml
|
|
3
|
+
*
|
|
4
|
+
* Byte-surgical YAML value updates that replace or insert without reflowing
|
|
5
|
+
* the surrounding document. Pure with respect to Node builtins, but it pulls
|
|
6
|
+
* in `yaml` — roughly 271KB bundled. Quarantined behind its own subpath.
|
|
7
|
+
*/
|
|
8
|
+
export * from './yaml/surgical-yaml.js';
|
|
9
|
+
//# sourceMappingURL=yaml.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"yaml.d.ts","sourceRoot":"","sources":["../src/yaml.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,cAAc,yBAAyB,CAAC"}
|
package/dist/yaml.js
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @vibe-agent-toolkit/utils/yaml
|
|
3
|
+
*
|
|
4
|
+
* Byte-surgical YAML value updates that replace or insert without reflowing
|
|
5
|
+
* the surrounding document. Pure with respect to Node builtins, but it pulls
|
|
6
|
+
* in `yaml` — roughly 271KB bundled. Quarantined behind its own subpath.
|
|
7
|
+
*/
|
|
8
|
+
export * from './yaml/surgical-yaml.js';
|
|
9
|
+
//# sourceMappingURL=yaml.js.map
|
package/dist/yaml.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"yaml.js","sourceRoot":"","sources":["../src/yaml.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,cAAc,yBAAyB,CAAC"}
|
package/dist/zod.d.ts
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @vibe-agent-toolkit/utils/zod
|
|
3
|
+
*
|
|
4
|
+
* Version-agnostic Zod type introspection via duck typing on `_def.typeName`,
|
|
5
|
+
* so it works across Zod v3 and v4 even when library and consumer versions
|
|
6
|
+
* differ. Pure — reaches no Node builtin. Isolating it here means consumers
|
|
7
|
+
* who never touch Zod are not coupled to the `zod` peer range.
|
|
8
|
+
*/
|
|
9
|
+
export * from './zod-introspection.js';
|
|
10
|
+
//# sourceMappingURL=zod.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"zod.d.ts","sourceRoot":"","sources":["../src/zod.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,cAAc,wBAAwB,CAAC"}
|
package/dist/zod.js
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @vibe-agent-toolkit/utils/zod
|
|
3
|
+
*
|
|
4
|
+
* Version-agnostic Zod type introspection via duck typing on `_def.typeName`,
|
|
5
|
+
* so it works across Zod v3 and v4 even when library and consumer versions
|
|
6
|
+
* differ. Pure — reaches no Node builtin. Isolating it here means consumers
|
|
7
|
+
* who never touch Zod are not coupled to the `zod` peer range.
|
|
8
|
+
*/
|
|
9
|
+
export * from './zod-introspection.js';
|
|
10
|
+
//# sourceMappingURL=zod.js.map
|
package/dist/zod.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"zod.js","sourceRoot":"","sources":["../src/zod.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,cAAc,wBAAwB,CAAC"}
|