@probelabs/probe 0.6.0-rc225 → 0.6.0-rc226
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/bin/binaries/probe-v0.6.0-rc226-aarch64-apple-darwin.tar.gz +0 -0
- package/bin/binaries/probe-v0.6.0-rc226-aarch64-unknown-linux-musl.tar.gz +0 -0
- package/bin/binaries/probe-v0.6.0-rc226-x86_64-apple-darwin.tar.gz +0 -0
- package/bin/binaries/probe-v0.6.0-rc226-x86_64-pc-windows-msvc.zip +0 -0
- package/bin/binaries/probe-v0.6.0-rc226-x86_64-unknown-linux-musl.tar.gz +0 -0
- package/build/agent/ProbeAgent.js +82 -33
- package/build/agent/index.js +162 -49
- package/build/tools/analyzeAll.js +6 -1
- package/build/tools/bash.js +18 -3
- package/build/tools/edit.js +19 -10
- package/build/tools/vercel.js +17 -7
- package/build/utils/path-validation.js +148 -1
- package/cjs/agent/ProbeAgent.cjs +161 -48
- package/cjs/index.cjs +161 -48
- package/package.json +1 -1
- package/src/agent/ProbeAgent.js +82 -33
- package/src/tools/analyzeAll.js +6 -1
- package/src/tools/bash.js +18 -3
- package/src/tools/edit.js +19 -10
- package/src/tools/vercel.js +17 -7
- package/src/utils/path-validation.js +148 -1
- package/bin/binaries/probe-v0.6.0-rc225-aarch64-apple-darwin.tar.gz +0 -0
- package/bin/binaries/probe-v0.6.0-rc225-aarch64-unknown-linux-musl.tar.gz +0 -0
- package/bin/binaries/probe-v0.6.0-rc225-x86_64-apple-darwin.tar.gz +0 -0
- package/bin/binaries/probe-v0.6.0-rc225-x86_64-pc-windows-msvc.zip +0 -0
- package/bin/binaries/probe-v0.6.0-rc225-x86_64-unknown-linux-musl.tar.gz +0 -0
|
@@ -4,9 +4,48 @@
|
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
import path from 'path';
|
|
7
|
-
import { promises as fs } from 'fs';
|
|
7
|
+
import { promises as fs, realpathSync } from 'fs';
|
|
8
8
|
import { PathError } from './error-types.js';
|
|
9
9
|
|
|
10
|
+
/**
|
|
11
|
+
* Safely resolve symlinks for a path.
|
|
12
|
+
* Returns the real path if it exists, otherwise finds the nearest existing
|
|
13
|
+
* ancestor directory and resolves it, then appends the remaining path components.
|
|
14
|
+
* This is important for security to prevent symlink bypass attacks.
|
|
15
|
+
*
|
|
16
|
+
* @param {string} inputPath - Path to resolve
|
|
17
|
+
* @returns {string} Resolved real path or best-effort resolved path
|
|
18
|
+
*/
|
|
19
|
+
export function safeRealpath(inputPath) {
|
|
20
|
+
try {
|
|
21
|
+
return realpathSync(inputPath);
|
|
22
|
+
} catch (error) {
|
|
23
|
+
// If path doesn't exist, find the nearest existing ancestor
|
|
24
|
+
// and resolve it, then append the remaining components.
|
|
25
|
+
// This handles cases like non-existent nested paths where an ancestor
|
|
26
|
+
// may be a symlink (e.g., /var -> /private/var on macOS)
|
|
27
|
+
const normalized = path.normalize(inputPath);
|
|
28
|
+
const parts = normalized.split(path.sep);
|
|
29
|
+
|
|
30
|
+
// Try progressively shorter paths until we find one that exists
|
|
31
|
+
for (let i = parts.length - 1; i >= 0; i--) {
|
|
32
|
+
const ancestorPath = parts.slice(0, i).join(path.sep) || path.sep;
|
|
33
|
+
try {
|
|
34
|
+
const resolvedAncestor = realpathSync(ancestorPath);
|
|
35
|
+
// Found an existing ancestor - append remaining components
|
|
36
|
+
const remainingParts = parts.slice(i);
|
|
37
|
+
return path.join(resolvedAncestor, ...remainingParts);
|
|
38
|
+
} catch (ancestorError) {
|
|
39
|
+
// This ancestor doesn't exist either, try the next one up
|
|
40
|
+
continue;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// No existing ancestor found, return normalized path
|
|
45
|
+
return normalized;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
10
49
|
/**
|
|
11
50
|
* Validates and normalizes a path to be used as working directory (cwd).
|
|
12
51
|
*
|
|
@@ -74,3 +113,111 @@ export function normalizePath(inputPath, defaultPath = process.cwd()) {
|
|
|
74
113
|
const targetPath = inputPath || defaultPath;
|
|
75
114
|
return path.normalize(path.resolve(targetPath));
|
|
76
115
|
}
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Compute the common prefix (workspace root) from an array of folder paths.
|
|
119
|
+
* This is useful for finding a single workspace root from multiple allowed folders.
|
|
120
|
+
*
|
|
121
|
+
* IMPORTANT: This function returns a value for DISPLAY and CWD purposes only.
|
|
122
|
+
* It is NOT a security boundary. All security checks should be performed against
|
|
123
|
+
* the original allowedFolders array, not against workspaceRoot.
|
|
124
|
+
*
|
|
125
|
+
* When no common prefix exists (e.g., unrelated paths), returns the first folder.
|
|
126
|
+
* This is intentional - the caller should use allowedFolders for security validation.
|
|
127
|
+
*
|
|
128
|
+
* Examples:
|
|
129
|
+
* - ['/tmp/ws/tyk', '/tmp/ws/tyk-docs'] -> '/tmp/ws'
|
|
130
|
+
* - ['/tmp/ws/tyk'] -> '/tmp/ws/tyk'
|
|
131
|
+
* - ['/a/b', '/c/d'] -> '/a/b' (no common prefix, returns first folder for cwd)
|
|
132
|
+
* - ['C:\\Users\\ws\\tyk', 'C:\\Users\\ws\\docs'] -> 'C:\\Users\\ws' (Windows)
|
|
133
|
+
*
|
|
134
|
+
* @param {string[]} folders - Array of absolute folder paths
|
|
135
|
+
* @returns {string} Common prefix path (for display/cwd, NOT security boundary)
|
|
136
|
+
*/
|
|
137
|
+
export function getCommonPrefix(folders) {
|
|
138
|
+
if (!folders || folders.length === 0) {
|
|
139
|
+
return process.cwd();
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
if (folders.length === 1) {
|
|
143
|
+
// Resolve symlinks for security
|
|
144
|
+
return safeRealpath(folders[0]);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// Resolve symlinks and normalize all paths to handle mixed separators
|
|
148
|
+
// This prevents symlink bypass attacks where a symlink could point outside the workspace
|
|
149
|
+
const normalized = folders.map(f => safeRealpath(f));
|
|
150
|
+
|
|
151
|
+
// Split into segments
|
|
152
|
+
const segments = normalized.map(f => f.split(path.sep));
|
|
153
|
+
|
|
154
|
+
// Find minimum length
|
|
155
|
+
const minLen = Math.min(...segments.map(s => s.length));
|
|
156
|
+
|
|
157
|
+
// Find common prefix segments
|
|
158
|
+
const commonSegments = [];
|
|
159
|
+
for (let i = 0; i < minLen; i++) {
|
|
160
|
+
const segment = segments[0][i];
|
|
161
|
+
if (segments.every(s => s[i] === segment)) {
|
|
162
|
+
commonSegments.push(segment);
|
|
163
|
+
} else {
|
|
164
|
+
break;
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
// Handle edge cases
|
|
169
|
+
if (commonSegments.length === 0) {
|
|
170
|
+
// No common prefix at all, return first folder
|
|
171
|
+
return normalized[0];
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
// Handle Windows drive letters (e.g., 'C:')
|
|
175
|
+
// If only the drive letter is common, return first folder for more useful context
|
|
176
|
+
if (commonSegments.length === 1 && /^[a-zA-Z]:$/.test(commonSegments[0])) {
|
|
177
|
+
return normalized[0];
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
// Handle Unix root (empty string from split)
|
|
181
|
+
// If only the root '/' is common, return first folder for more useful context
|
|
182
|
+
if (commonSegments.length === 1 && commonSegments[0] === '') {
|
|
183
|
+
return normalized[0];
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
return commonSegments.join(path.sep);
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
/**
|
|
190
|
+
* Convert an absolute path to a relative path from the workspace root.
|
|
191
|
+
* Returns the original path if it cannot be made relative (outside workspace).
|
|
192
|
+
*
|
|
193
|
+
* @param {string} absolutePath - Absolute path to convert
|
|
194
|
+
* @param {string} workspaceRoot - Workspace root to compute relative path from
|
|
195
|
+
* @returns {string} Relative path or original if outside workspace
|
|
196
|
+
*/
|
|
197
|
+
export function toRelativePath(absolutePath, workspaceRoot) {
|
|
198
|
+
if (!absolutePath || !workspaceRoot) {
|
|
199
|
+
return absolutePath;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
// Resolve symlinks for security to prevent bypass attacks
|
|
203
|
+
// Use safeRealpath which falls back to normalized path if resolution fails
|
|
204
|
+
let normalized = safeRealpath(absolutePath);
|
|
205
|
+
let normalizedRoot = safeRealpath(workspaceRoot);
|
|
206
|
+
|
|
207
|
+
// Remove trailing separators (path.normalize doesn't always do this)
|
|
208
|
+
while (normalizedRoot.length > 1 && normalizedRoot.endsWith(path.sep)) {
|
|
209
|
+
normalizedRoot = normalizedRoot.slice(0, -1);
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
// Check if path is within workspace (exact match or starts with root + separator)
|
|
213
|
+
if (normalized === normalizedRoot) {
|
|
214
|
+
return '.';
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
if (normalized.startsWith(normalizedRoot + path.sep)) {
|
|
218
|
+
return path.relative(normalizedRoot, normalized);
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
// Path is outside workspace, return as-is
|
|
222
|
+
return absolutePath;
|
|
223
|
+
}
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|