@vibe-agent-toolkit/utils 0.1.41 → 0.1.42
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 +217 -0
- package/eslint/index.cjs +161 -0
- package/eslint/index.d.cts +60 -0
- package/eslint/rules/dead-import.cjs +201 -0
- package/eslint/rules/eslint-rule-factory.cjs +410 -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 +161 -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 +417 -0
- package/eslint/rules/prefer-startswith-over-regex.cjs +257 -0
- package/eslint/rules/require-justified-skip.cjs +348 -0
- package/eslint/rules/safe-import.cjs +164 -0
- package/package.json +65 -7
|
@@ -0,0 +1,251 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pure path-string helpers.
|
|
3
|
+
*
|
|
4
|
+
* **This module's only import is `node:path`.** Nothing here touches the
|
|
5
|
+
* filesystem, the OS, or URLs — that is the whole point: the `./path` and
|
|
6
|
+
* `./glob` subpath entries re-export from here so importing them can never
|
|
7
|
+
* pull `node:fs`, `node:os`, or `node:url` into a consumer's graph.
|
|
8
|
+
*
|
|
9
|
+
* Filesystem-touching path helpers (`normalizePath`, `normalizedTmpdir`,
|
|
10
|
+
* `mkdirSyncReal`, `resolveFromImportMeta`, `dynamicImportPath`) live in
|
|
11
|
+
* `./path-utils.ts` and are exposed via the `./fs` entry.
|
|
12
|
+
*/
|
|
13
|
+
import path from 'node:path';
|
|
14
|
+
/**
|
|
15
|
+
* Check if a path is absolute
|
|
16
|
+
*
|
|
17
|
+
* Cross-platform detection of absolute paths:
|
|
18
|
+
* - Unix: /path/to/file
|
|
19
|
+
* - Windows: C:\path\to\file or C:/path/to/file
|
|
20
|
+
*
|
|
21
|
+
* @param p - Path to check
|
|
22
|
+
* @returns True if path is absolute
|
|
23
|
+
*
|
|
24
|
+
* @example
|
|
25
|
+
* isAbsolutePath('/path/to/file') // true
|
|
26
|
+
* isAbsolutePath('./relative') // false
|
|
27
|
+
* isAbsolutePath('C:/Windows') // true (Windows)
|
|
28
|
+
*/
|
|
29
|
+
export function isAbsolutePath(p) {
|
|
30
|
+
return path.isAbsolute(p);
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* True if `p` is absolute on ANY platform — a POSIX root path (`/etc`), a
|
|
34
|
+
* Windows drive-letter path (`C:\…` or `C:/…`), or a UNC path (`\\host\share`).
|
|
35
|
+
*
|
|
36
|
+
* Unlike {@link isAbsolutePath} (host-platform only), this is host-independent,
|
|
37
|
+
* so config-containment checks reject Windows-absolute paths even when run on
|
|
38
|
+
* POSIX CI, and vice versa. Used to keep config-supplied relative paths (skill
|
|
39
|
+
* `files:` dest) from escaping their anchor directory (zip-slip class).
|
|
40
|
+
*
|
|
41
|
+
* @example
|
|
42
|
+
* isAbsoluteAnyPlatform('/etc/passwd') // true (POSIX)
|
|
43
|
+
* isAbsoluteAnyPlatform('C:\\Users') // true (Windows drive)
|
|
44
|
+
* isAbsoluteAnyPlatform('scripts/cli') // false (relative)
|
|
45
|
+
*/
|
|
46
|
+
export function isAbsoluteAnyPlatform(p) {
|
|
47
|
+
return path.posix.isAbsolute(p) || path.win32.isAbsolute(p);
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* True if `p` contains a `..` parent-directory traversal segment.
|
|
51
|
+
*
|
|
52
|
+
* Forward-slash-normalized, then inspects each `/`-delimited segment — so a
|
|
53
|
+
* `..` is caught regardless of the original OS separator. A containment guard
|
|
54
|
+
* for config-supplied relative paths (skill `files:` dest values, glob magic
|
|
55
|
+
* remainders) that must never climb above their anchor directory.
|
|
56
|
+
*
|
|
57
|
+
* @example
|
|
58
|
+
* hasParentTraversalSegment('a/../b') // true
|
|
59
|
+
* hasParentTraversalSegment('a/b/c') // false
|
|
60
|
+
* hasParentTraversalSegment('..\\evil') // true (backslash normalized)
|
|
61
|
+
* hasParentTraversalSegment('a..b/c') // false (".." must be a whole segment)
|
|
62
|
+
*/
|
|
63
|
+
export function hasParentTraversalSegment(p) {
|
|
64
|
+
return toForwardSlash(p).split('/').includes('..');
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Compute a `ValidationIssue.location`: an absolute source file path made
|
|
68
|
+
* relative to the scan/project root, forward-slashed.
|
|
69
|
+
*
|
|
70
|
+
* This is the ONE relativizer every VAT validation lane uses. `location` is
|
|
71
|
+
* contractually project-relative (see `ValidationIssue` in
|
|
72
|
+
* `@vibe-agent-toolkit/agent-schema`), so producers must route through here
|
|
73
|
+
* rather than emitting `skillPath` directly — absolute locations leak the
|
|
74
|
+
* developer's home directory into CI logs and make `validation.allow` globs,
|
|
75
|
+
* which match against `location`, unwritable.
|
|
76
|
+
*
|
|
77
|
+
* `projectRoot` is required precisely because "relative to what?" has no safe
|
|
78
|
+
* default: a caller with no root must decide one (the skill directory, the
|
|
79
|
+
* scan root) rather than silently falling back to an absolute path.
|
|
80
|
+
*
|
|
81
|
+
* @param sourceFilePath - Absolute path to the file the issue was found in.
|
|
82
|
+
* @param projectRoot - Root the location is expressed relative to.
|
|
83
|
+
* @returns Forward-slashed relative location.
|
|
84
|
+
*
|
|
85
|
+
* @example
|
|
86
|
+
* issueLocation('/repo/skills/foo/SKILL.md', '/repo') // 'skills/foo/SKILL.md'
|
|
87
|
+
*/
|
|
88
|
+
export function issueLocation(sourceFilePath, projectRoot) {
|
|
89
|
+
return toForwardSlash(path.relative(projectRoot, sourceFilePath));
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* Convert a relative path to absolute
|
|
93
|
+
*
|
|
94
|
+
* If path is already absolute, returns it normalized.
|
|
95
|
+
* Otherwise resolves relative to baseDir.
|
|
96
|
+
*
|
|
97
|
+
* @param p - Path to convert
|
|
98
|
+
* @param baseDir - Base directory for resolution
|
|
99
|
+
* @returns Absolute path with **forward slashes** (cross-platform safe)
|
|
100
|
+
*
|
|
101
|
+
* @example
|
|
102
|
+
* toAbsolutePath('./docs/README.md', '/project')
|
|
103
|
+
* // Returns: '/project/docs/README.md'
|
|
104
|
+
*
|
|
105
|
+
* toAbsolutePath('/absolute/path.md', '/project')
|
|
106
|
+
* // Returns: '/absolute/path.md'
|
|
107
|
+
*/
|
|
108
|
+
export function toAbsolutePath(p, baseDir) {
|
|
109
|
+
if (path.isAbsolute(p)) {
|
|
110
|
+
return toForwardSlash(path.normalize(p));
|
|
111
|
+
}
|
|
112
|
+
return toForwardSlash(path.resolve(baseDir, p));
|
|
113
|
+
}
|
|
114
|
+
/**
|
|
115
|
+
* Get the relative path from one file to another
|
|
116
|
+
*
|
|
117
|
+
* Useful for generating relative links between markdown files.
|
|
118
|
+
*
|
|
119
|
+
* @param from - Source file path (absolute)
|
|
120
|
+
* @param to - Target file path (absolute)
|
|
121
|
+
* @returns Relative path from source to target with **forward slashes** (cross-platform safe)
|
|
122
|
+
*
|
|
123
|
+
* @example
|
|
124
|
+
* getRelativePath('/project/docs/guide.md', '/project/README.md')
|
|
125
|
+
* // Returns: '../README.md'
|
|
126
|
+
*
|
|
127
|
+
* getRelativePath('/project/README.md', '/project/docs/api.md')
|
|
128
|
+
* // Returns: 'docs/api.md'
|
|
129
|
+
*/
|
|
130
|
+
export function getRelativePath(from, to) {
|
|
131
|
+
// Get directory of source file (not the file itself)
|
|
132
|
+
const fromDir = path.dirname(from);
|
|
133
|
+
// Calculate relative path from source directory to target file
|
|
134
|
+
return toForwardSlash(path.relative(fromDir, to));
|
|
135
|
+
}
|
|
136
|
+
/**
|
|
137
|
+
* Convert a path to forward slashes
|
|
138
|
+
*
|
|
139
|
+
* Windows accepts both forward slashes and backslashes as path separators.
|
|
140
|
+
* This function normalizes all paths to use forward slashes for consistency.
|
|
141
|
+
* Useful for glob pattern matching, cross-platform comparisons, and string operations.
|
|
142
|
+
*
|
|
143
|
+
* @param p - Path to convert
|
|
144
|
+
* @returns Path with forward slashes
|
|
145
|
+
*
|
|
146
|
+
* @example
|
|
147
|
+
* toForwardSlash('C:\\Users\\docs\\README.md')
|
|
148
|
+
* // Returns: 'C:/Users/docs/README.md'
|
|
149
|
+
*
|
|
150
|
+
* toForwardSlash('/project/docs/README.md')
|
|
151
|
+
* // Returns: '/project/docs/README.md' (unchanged)
|
|
152
|
+
*/
|
|
153
|
+
export function toForwardSlash(p) {
|
|
154
|
+
return p.replaceAll('\\', '/');
|
|
155
|
+
}
|
|
156
|
+
/**
|
|
157
|
+
* Cross-platform safe path operations.
|
|
158
|
+
*
|
|
159
|
+
* Wraps Node's `path.join()`, `path.resolve()`, and `path.relative()` to always
|
|
160
|
+
* return forward-slash paths. On Windows, the native `path.*` functions return
|
|
161
|
+
* backslashes, which causes bugs when paths are used as Map keys, compared as
|
|
162
|
+
* strings, or matched with glob patterns.
|
|
163
|
+
*
|
|
164
|
+
* **Use these instead of importing from `node:path` directly.**
|
|
165
|
+
* ESLint rules enforce this — see `no-path-join`, `no-path-resolve`, `no-path-relative`.
|
|
166
|
+
*
|
|
167
|
+
* @example
|
|
168
|
+
* ```typescript
|
|
169
|
+
* import { safePath } from '@vibe-agent-toolkit/utils';
|
|
170
|
+
*
|
|
171
|
+
* // Always forward slashes, even on Windows
|
|
172
|
+
* safePath.join('C:\\Users', 'docs', 'file.md') // → 'C:/Users/docs/file.md'
|
|
173
|
+
* safePath.resolve('/project', './docs') // → '/project/docs'
|
|
174
|
+
* safePath.relative('/project/docs', '/project') // → '..'
|
|
175
|
+
* safePath.joinUnderRoot('/harness', 'skill-abc') // → '/harness/skill-abc'
|
|
176
|
+
* safePath.joinUnderRoot('/harness', '../escape') // throws Error
|
|
177
|
+
* ```
|
|
178
|
+
*/
|
|
179
|
+
export const safePath = {
|
|
180
|
+
/** Like `path.join()` but always returns forward slashes. */
|
|
181
|
+
join(...paths) {
|
|
182
|
+
return toForwardSlash(path.join(...paths));
|
|
183
|
+
},
|
|
184
|
+
/** Like `path.resolve()` but always returns forward slashes. */
|
|
185
|
+
resolve(...paths) {
|
|
186
|
+
return toForwardSlash(path.resolve(...paths));
|
|
187
|
+
},
|
|
188
|
+
/** Like `path.relative()` but always returns forward slashes. */
|
|
189
|
+
relative(from, to) {
|
|
190
|
+
return toForwardSlash(path.relative(from, to));
|
|
191
|
+
},
|
|
192
|
+
/**
|
|
193
|
+
* Join path segments under a security root, throwing if the result would escape.
|
|
194
|
+
*
|
|
195
|
+
* Resolves `root + segments` and verifies the result is strictly inside `root`
|
|
196
|
+
* (or equal to it). Throws when any segment would cause the result to escape:
|
|
197
|
+
*
|
|
198
|
+
* - A `..` traversal that climbs above root
|
|
199
|
+
* - An absolute POSIX path segment (e.g. `/etc/passwd`)
|
|
200
|
+
* - A Windows drive-letter segment (e.g. `C:\Users\evil`)
|
|
201
|
+
*
|
|
202
|
+
* On success returns a forward-slash-normalized absolute path (consistent with
|
|
203
|
+
* the other `safePath` helpers).
|
|
204
|
+
*
|
|
205
|
+
* **Use this instead of `safePath.join(root, segment)` whenever `segment` may
|
|
206
|
+
* contain caller-controlled input** — this is the bug class that the original
|
|
207
|
+
* skill-test staging code was vulnerable to on Windows.
|
|
208
|
+
*
|
|
209
|
+
* @returns Forward-slash absolute path guaranteed to be inside `root`.
|
|
210
|
+
* @throws {Error} If the resolved path would escape `root`.
|
|
211
|
+
*
|
|
212
|
+
* @example
|
|
213
|
+
* ```typescript
|
|
214
|
+
* // ✅ Safe — throws if caller passes '../../../etc'
|
|
215
|
+
* const dest = safePath.joinUnderRoot(harnessRoot, stagedDirName(item.name));
|
|
216
|
+
*
|
|
217
|
+
* // ❌ Unsafe — silently escapes on Windows with absolute segment
|
|
218
|
+
* const dest = safePath.join(harnessRoot, item.name);
|
|
219
|
+
* ```
|
|
220
|
+
*/
|
|
221
|
+
joinUnderRoot(root, ...segments) {
|
|
222
|
+
// Eagerly reject any segment that is absolute (POSIX or Windows drive-letter)
|
|
223
|
+
// BEFORE resolving, so the error message can name the offending segment.
|
|
224
|
+
for (const seg of segments) {
|
|
225
|
+
if (path.isAbsolute(seg)) {
|
|
226
|
+
throw new Error(`safePath.joinUnderRoot: segment "${seg}" is absolute and escapes root "${root}".`);
|
|
227
|
+
}
|
|
228
|
+
// Windows drive-letter check for POSIX hosts (path.isAbsolute won't catch
|
|
229
|
+
// 'C:\...' on POSIX, but node's path.win32.isAbsolute does).
|
|
230
|
+
if (path.win32.isAbsolute(seg)) {
|
|
231
|
+
throw new Error(`safePath.joinUnderRoot: segment "${seg}" contains a Windows drive letter and escapes root "${root}".`);
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
const resolvedRoot = path.resolve(root);
|
|
235
|
+
const resolvedResult = segments.length > 0
|
|
236
|
+
? path.resolve(resolvedRoot, ...segments)
|
|
237
|
+
: resolvedRoot;
|
|
238
|
+
// Containment check: normalize both to forward slashes so the comparison
|
|
239
|
+
// is platform-independent and no path.sep is needed in string operations.
|
|
240
|
+
const fwdRoot = toForwardSlash(resolvedRoot);
|
|
241
|
+
const fwdResult = toForwardSlash(resolvedResult);
|
|
242
|
+
// Result must equal root or start with root + '/' (not just startsWith(root)
|
|
243
|
+
// which would match '/rootEvil' when root is '/root').
|
|
244
|
+
const rootPrefix = fwdRoot.endsWith('/') ? fwdRoot : `${fwdRoot}/`;
|
|
245
|
+
if (fwdResult !== fwdRoot && !fwdResult.startsWith(rootPrefix)) {
|
|
246
|
+
throw new Error(`safePath.joinUnderRoot: result "${fwdResult}" escapes root "${fwdRoot}".`);
|
|
247
|
+
}
|
|
248
|
+
return fwdResult;
|
|
249
|
+
},
|
|
250
|
+
};
|
|
251
|
+
//# sourceMappingURL=path-core.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"path-core.js","sourceRoot":"","sources":["../src/path-core.ts"],"names":[],"mappings":"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"}
|
package/dist/path-utils.d.ts
CHANGED
|
@@ -1,4 +1,16 @@
|
|
|
1
1
|
import { mkdirSync } from 'node:fs';
|
|
2
|
+
/**
|
|
3
|
+
* Re-export every pure path helper so the package barrel keeps exposing the
|
|
4
|
+
* exact same surface it always has. The pure definitions themselves live in
|
|
5
|
+
* `./path-core.ts`, which imports nothing but `node:path` — see that file's
|
|
6
|
+
* header for why the split exists.
|
|
7
|
+
*
|
|
8
|
+
* This preserves the BARREL only. `@vibe-agent-toolkit/utils/fs` no longer
|
|
9
|
+
* re-exports the pure helpers — they moved to `@vibe-agent-toolkit/utils/path`,
|
|
10
|
+
* which is a breaking change for `./fs` consumers (see CHANGELOG and
|
|
11
|
+
* `test/path-fs-subpaths.test.ts`).
|
|
12
|
+
*/
|
|
13
|
+
export * from './path-core.js';
|
|
2
14
|
/**
|
|
3
15
|
* Normalize any path (resolve short names on Windows)
|
|
4
16
|
*
|
|
@@ -90,189 +102,6 @@ export declare function normalizedTmpdir(): string;
|
|
|
90
102
|
* ```
|
|
91
103
|
*/
|
|
92
104
|
export declare function mkdirSyncReal(dirPath: string, options?: Parameters<typeof mkdirSync>[1]): string;
|
|
93
|
-
/**
|
|
94
|
-
* Check if a path is absolute
|
|
95
|
-
*
|
|
96
|
-
* Cross-platform detection of absolute paths:
|
|
97
|
-
* - Unix: /path/to/file
|
|
98
|
-
* - Windows: C:\path\to\file or C:/path/to/file
|
|
99
|
-
*
|
|
100
|
-
* @param p - Path to check
|
|
101
|
-
* @returns True if path is absolute
|
|
102
|
-
*
|
|
103
|
-
* @example
|
|
104
|
-
* isAbsolutePath('/path/to/file') // true
|
|
105
|
-
* isAbsolutePath('./relative') // false
|
|
106
|
-
* isAbsolutePath('C:/Windows') // true (Windows)
|
|
107
|
-
*/
|
|
108
|
-
export declare function isAbsolutePath(p: string): boolean;
|
|
109
|
-
/**
|
|
110
|
-
* True if `p` is absolute on ANY platform — a POSIX root path (`/etc`), a
|
|
111
|
-
* Windows drive-letter path (`C:\…` or `C:/…`), or a UNC path (`\\host\share`).
|
|
112
|
-
*
|
|
113
|
-
* Unlike {@link isAbsolutePath} (host-platform only), this is host-independent,
|
|
114
|
-
* so config-containment checks reject Windows-absolute paths even when run on
|
|
115
|
-
* POSIX CI, and vice versa. Used to keep config-supplied relative paths (skill
|
|
116
|
-
* `files:` dest) from escaping their anchor directory (zip-slip class).
|
|
117
|
-
*
|
|
118
|
-
* @example
|
|
119
|
-
* isAbsoluteAnyPlatform('/etc/passwd') // true (POSIX)
|
|
120
|
-
* isAbsoluteAnyPlatform('C:\\Users') // true (Windows drive)
|
|
121
|
-
* isAbsoluteAnyPlatform('scripts/cli') // false (relative)
|
|
122
|
-
*/
|
|
123
|
-
export declare function isAbsoluteAnyPlatform(p: string): boolean;
|
|
124
|
-
/**
|
|
125
|
-
* True if `p` contains a `..` parent-directory traversal segment.
|
|
126
|
-
*
|
|
127
|
-
* Forward-slash-normalized, then inspects each `/`-delimited segment — so a
|
|
128
|
-
* `..` is caught regardless of the original OS separator. A containment guard
|
|
129
|
-
* for config-supplied relative paths (skill `files:` dest values, glob magic
|
|
130
|
-
* remainders) that must never climb above their anchor directory.
|
|
131
|
-
*
|
|
132
|
-
* @example
|
|
133
|
-
* hasParentTraversalSegment('a/../b') // true
|
|
134
|
-
* hasParentTraversalSegment('a/b/c') // false
|
|
135
|
-
* hasParentTraversalSegment('..\\evil') // true (backslash normalized)
|
|
136
|
-
* hasParentTraversalSegment('a..b/c') // false (".." must be a whole segment)
|
|
137
|
-
*/
|
|
138
|
-
export declare function hasParentTraversalSegment(p: string): boolean;
|
|
139
|
-
/**
|
|
140
|
-
* Compute a `ValidationIssue.location`: an absolute source file path made
|
|
141
|
-
* relative to the scan/project root, forward-slashed.
|
|
142
|
-
*
|
|
143
|
-
* This is the ONE relativizer every VAT validation lane uses. `location` is
|
|
144
|
-
* contractually project-relative (see `ValidationIssue` in
|
|
145
|
-
* `@vibe-agent-toolkit/agent-schema`), so producers must route through here
|
|
146
|
-
* rather than emitting `skillPath` directly — absolute locations leak the
|
|
147
|
-
* developer's home directory into CI logs and make `validation.allow` globs,
|
|
148
|
-
* which match against `location`, unwritable.
|
|
149
|
-
*
|
|
150
|
-
* `projectRoot` is required precisely because "relative to what?" has no safe
|
|
151
|
-
* default: a caller with no root must decide one (the skill directory, the
|
|
152
|
-
* scan root) rather than silently falling back to an absolute path.
|
|
153
|
-
*
|
|
154
|
-
* @param sourceFilePath - Absolute path to the file the issue was found in.
|
|
155
|
-
* @param projectRoot - Root the location is expressed relative to.
|
|
156
|
-
* @returns Forward-slashed relative location.
|
|
157
|
-
*
|
|
158
|
-
* @example
|
|
159
|
-
* issueLocation('/repo/skills/foo/SKILL.md', '/repo') // 'skills/foo/SKILL.md'
|
|
160
|
-
*/
|
|
161
|
-
export declare function issueLocation(sourceFilePath: string, projectRoot: string): string;
|
|
162
|
-
/**
|
|
163
|
-
* Convert a relative path to absolute
|
|
164
|
-
*
|
|
165
|
-
* If path is already absolute, returns it normalized.
|
|
166
|
-
* Otherwise resolves relative to baseDir.
|
|
167
|
-
*
|
|
168
|
-
* @param p - Path to convert
|
|
169
|
-
* @param baseDir - Base directory for resolution
|
|
170
|
-
* @returns Absolute path with **forward slashes** (cross-platform safe)
|
|
171
|
-
*
|
|
172
|
-
* @example
|
|
173
|
-
* toAbsolutePath('./docs/README.md', '/project')
|
|
174
|
-
* // Returns: '/project/docs/README.md'
|
|
175
|
-
*
|
|
176
|
-
* toAbsolutePath('/absolute/path.md', '/project')
|
|
177
|
-
* // Returns: '/absolute/path.md'
|
|
178
|
-
*/
|
|
179
|
-
export declare function toAbsolutePath(p: string, baseDir: string): string;
|
|
180
|
-
/**
|
|
181
|
-
* Get the relative path from one file to another
|
|
182
|
-
*
|
|
183
|
-
* Useful for generating relative links between markdown files.
|
|
184
|
-
*
|
|
185
|
-
* @param from - Source file path (absolute)
|
|
186
|
-
* @param to - Target file path (absolute)
|
|
187
|
-
* @returns Relative path from source to target with **forward slashes** (cross-platform safe)
|
|
188
|
-
*
|
|
189
|
-
* @example
|
|
190
|
-
* getRelativePath('/project/docs/guide.md', '/project/README.md')
|
|
191
|
-
* // Returns: '../README.md'
|
|
192
|
-
*
|
|
193
|
-
* getRelativePath('/project/README.md', '/project/docs/api.md')
|
|
194
|
-
* // Returns: 'docs/api.md'
|
|
195
|
-
*/
|
|
196
|
-
export declare function getRelativePath(from: string, to: string): string;
|
|
197
|
-
/**
|
|
198
|
-
* Convert a path to forward slashes
|
|
199
|
-
*
|
|
200
|
-
* Windows accepts both forward slashes and backslashes as path separators.
|
|
201
|
-
* This function normalizes all paths to use forward slashes for consistency.
|
|
202
|
-
* Useful for glob pattern matching, cross-platform comparisons, and string operations.
|
|
203
|
-
*
|
|
204
|
-
* @param p - Path to convert
|
|
205
|
-
* @returns Path with forward slashes
|
|
206
|
-
*
|
|
207
|
-
* @example
|
|
208
|
-
* toForwardSlash('C:\\Users\\docs\\README.md')
|
|
209
|
-
* // Returns: 'C:/Users/docs/README.md'
|
|
210
|
-
*
|
|
211
|
-
* toForwardSlash('/project/docs/README.md')
|
|
212
|
-
* // Returns: '/project/docs/README.md' (unchanged)
|
|
213
|
-
*/
|
|
214
|
-
export declare function toForwardSlash(p: string): string;
|
|
215
|
-
/**
|
|
216
|
-
* Cross-platform safe path operations.
|
|
217
|
-
*
|
|
218
|
-
* Wraps Node's `path.join()`, `path.resolve()`, and `path.relative()` to always
|
|
219
|
-
* return forward-slash paths. On Windows, the native `path.*` functions return
|
|
220
|
-
* backslashes, which causes bugs when paths are used as Map keys, compared as
|
|
221
|
-
* strings, or matched with glob patterns.
|
|
222
|
-
*
|
|
223
|
-
* **Use these instead of importing from `node:path` directly.**
|
|
224
|
-
* ESLint rules enforce this — see `no-path-join`, `no-path-resolve`, `no-path-relative`.
|
|
225
|
-
*
|
|
226
|
-
* @example
|
|
227
|
-
* ```typescript
|
|
228
|
-
* import { safePath } from '@vibe-agent-toolkit/utils';
|
|
229
|
-
*
|
|
230
|
-
* // Always forward slashes, even on Windows
|
|
231
|
-
* safePath.join('C:\\Users', 'docs', 'file.md') // → 'C:/Users/docs/file.md'
|
|
232
|
-
* safePath.resolve('/project', './docs') // → '/project/docs'
|
|
233
|
-
* safePath.relative('/project/docs', '/project') // → '..'
|
|
234
|
-
* safePath.joinUnderRoot('/harness', 'skill-abc') // → '/harness/skill-abc'
|
|
235
|
-
* safePath.joinUnderRoot('/harness', '../escape') // throws Error
|
|
236
|
-
* ```
|
|
237
|
-
*/
|
|
238
|
-
export declare const safePath: {
|
|
239
|
-
/** Like `path.join()` but always returns forward slashes. */
|
|
240
|
-
readonly join: (...paths: string[]) => string;
|
|
241
|
-
/** Like `path.resolve()` but always returns forward slashes. */
|
|
242
|
-
readonly resolve: (...paths: string[]) => string;
|
|
243
|
-
/** Like `path.relative()` but always returns forward slashes. */
|
|
244
|
-
readonly relative: (from: string, to: string) => string;
|
|
245
|
-
/**
|
|
246
|
-
* Join path segments under a security root, throwing if the result would escape.
|
|
247
|
-
*
|
|
248
|
-
* Resolves `root + segments` and verifies the result is strictly inside `root`
|
|
249
|
-
* (or equal to it). Throws when any segment would cause the result to escape:
|
|
250
|
-
*
|
|
251
|
-
* - A `..` traversal that climbs above root
|
|
252
|
-
* - An absolute POSIX path segment (e.g. `/etc/passwd`)
|
|
253
|
-
* - A Windows drive-letter segment (e.g. `C:\Users\evil`)
|
|
254
|
-
*
|
|
255
|
-
* On success returns a forward-slash-normalized absolute path (consistent with
|
|
256
|
-
* the other `safePath` helpers).
|
|
257
|
-
*
|
|
258
|
-
* **Use this instead of `safePath.join(root, segment)` whenever `segment` may
|
|
259
|
-
* contain caller-controlled input** — this is the bug class that the original
|
|
260
|
-
* skill-test staging code was vulnerable to on Windows.
|
|
261
|
-
*
|
|
262
|
-
* @returns Forward-slash absolute path guaranteed to be inside `root`.
|
|
263
|
-
* @throws {Error} If the resolved path would escape `root`.
|
|
264
|
-
*
|
|
265
|
-
* @example
|
|
266
|
-
* ```typescript
|
|
267
|
-
* // ✅ Safe — throws if caller passes '../../../etc'
|
|
268
|
-
* const dest = safePath.joinUnderRoot(harnessRoot, stagedDirName(item.name));
|
|
269
|
-
*
|
|
270
|
-
* // ❌ Unsafe — silently escapes on Windows with absolute segment
|
|
271
|
-
* const dest = safePath.join(harnessRoot, item.name);
|
|
272
|
-
* ```
|
|
273
|
-
*/
|
|
274
|
-
readonly joinUnderRoot: (root: string, ...segments: string[]) => string;
|
|
275
|
-
};
|
|
276
105
|
/**
|
|
277
106
|
* Resolve an OS-native absolute path from an ESM module's `import.meta.url` and
|
|
278
107
|
* optional relative path segments.
|
package/dist/path-utils.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"path-utils.d.ts","sourceRoot":"","sources":["../src/path-utils.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAgB,MAAM,SAAS,CAAC;
|
|
1
|
+
{"version":3,"file":"path-utils.d.ts","sourceRoot":"","sources":["../src/path-utils.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAgB,MAAM,SAAS,CAAC;AAOlD;;;;;;;;;;GAUG;AACH,cAAc,gBAAgB,CAAC;AAE/B;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AACH,wBAAgB,aAAa,CAAC,GAAG,KAAK,EAAE,MAAM,EAAE,GAAG,MAAM,CA+BxD;AAED;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,wBAAgB,gBAAgB,IAAI,MAAM,CAezC;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AACH,wBAAgB,aAAa,CAC3B,OAAO,EAAE,MAAM,EACf,OAAO,CAAC,EAAE,UAAU,CAAC,OAAO,SAAS,CAAC,CAAC,CAAC,CAAC,GACxC,MAAM,CAiBR;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,qBAAqB,CAAC,aAAa,EAAE,MAAM,EAAE,GAAG,QAAQ,EAAE,MAAM,EAAE,GAAG,MAAM,CAQ1F;AAED;;;;;;;;;;;;GAYG;AACH,wBAAsB,iBAAiB,CAAC,CAAC,GAAG,OAAO,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,CAAC,CAAC,CAGhF"}
|