@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
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -71,6 +71,7 @@ import { RetryManager, createRetryManagerFromEnv } from './RetryManager.js';
|
|
|
71
71
|
import { FallbackManager, createFallbackManagerFromEnv, buildFallbackProvidersFromEnv } from './FallbackManager.js';
|
|
72
72
|
import { handleContextLimitError } from './contextCompactor.js';
|
|
73
73
|
import { formatErrorForAI, ParameterError } from '../utils/error-types.js';
|
|
74
|
+
import { getCommonPrefix, toRelativePath, safeRealpath } from '../utils/path-validation.js';
|
|
74
75
|
import { truncateIfNeeded, getMaxOutputTokens } from './outputTruncator.js';
|
|
75
76
|
import { DelegationManager } from '../delegate.js';
|
|
76
77
|
import {
|
|
@@ -269,8 +270,15 @@ export class ProbeAgent {
|
|
|
269
270
|
this.allowedFolders = [process.cwd()];
|
|
270
271
|
}
|
|
271
272
|
|
|
272
|
-
//
|
|
273
|
-
|
|
273
|
+
// Compute workspace root as common prefix of all allowed folders
|
|
274
|
+
// This provides a single "root" for relative path resolution and default cwd
|
|
275
|
+
// IMPORTANT: workspaceRoot is NOT a security boundary - all security checks
|
|
276
|
+
// must be performed against this.allowedFolders, not workspaceRoot
|
|
277
|
+
this.workspaceRoot = getCommonPrefix(this.allowedFolders);
|
|
278
|
+
|
|
279
|
+
// Working directory for resolving relative paths
|
|
280
|
+
// If not explicitly provided, use workspace root for consistency
|
|
281
|
+
this.cwd = options.cwd || this.workspaceRoot;
|
|
274
282
|
|
|
275
283
|
// API configuration
|
|
276
284
|
this.clientApiProvider = options.provider || null;
|
|
@@ -289,6 +297,8 @@ export class ProbeAgent {
|
|
|
289
297
|
console.log(`[DEBUG] Maximum tool iterations configured: ${MAX_TOOL_ITERATIONS}`);
|
|
290
298
|
console.log(`[DEBUG] Allow Edit (implement tool): ${this.allowEdit}`);
|
|
291
299
|
console.log(`[DEBUG] Search delegation enabled: ${this.searchDelegate}`);
|
|
300
|
+
console.log(`[DEBUG] Workspace root: ${this.workspaceRoot}`);
|
|
301
|
+
console.log(`[DEBUG] Working directory (cwd): ${this.cwd}`);
|
|
292
302
|
}
|
|
293
303
|
|
|
294
304
|
// Initialize tools
|
|
@@ -732,8 +742,9 @@ export class ProbeAgent {
|
|
|
732
742
|
const configOptions = {
|
|
733
743
|
sessionId: this.sessionId,
|
|
734
744
|
debug: this.debug,
|
|
735
|
-
// Use
|
|
736
|
-
cwd: this.cwd
|
|
745
|
+
// Use cwd (which defaults to workspaceRoot in constructor)
|
|
746
|
+
cwd: this.cwd,
|
|
747
|
+
workspaceRoot: this.workspaceRoot,
|
|
737
748
|
allowedFolders: this.allowedFolders,
|
|
738
749
|
outline: this.outline,
|
|
739
750
|
searchDelegate: this.searchDelegate,
|
|
@@ -1612,7 +1623,8 @@ export class ProbeAgent {
|
|
|
1612
1623
|
}
|
|
1613
1624
|
|
|
1614
1625
|
// Security validation: check if path is within any allowed directory
|
|
1615
|
-
// Use
|
|
1626
|
+
// Use safeRealpath() to resolve symlinks and handle path traversal attempts (e.g., '/allowed/../etc/passwd')
|
|
1627
|
+
// This prevents symlink bypass attacks (e.g., /tmp -> /private/tmp on macOS)
|
|
1616
1628
|
const allowedDirs = this.allowedFolders && this.allowedFolders.length > 0 ? this.allowedFolders : [process.cwd()];
|
|
1617
1629
|
|
|
1618
1630
|
let absolutePath;
|
|
@@ -1620,20 +1632,20 @@ export class ProbeAgent {
|
|
|
1620
1632
|
|
|
1621
1633
|
// If absolute path, check if it's within any allowed directory
|
|
1622
1634
|
if (isAbsolute(imagePath)) {
|
|
1623
|
-
//
|
|
1624
|
-
absolutePath =
|
|
1635
|
+
// Use safeRealpath to resolve symlinks for security
|
|
1636
|
+
absolutePath = safeRealpath(resolve(imagePath));
|
|
1625
1637
|
isPathAllowed = allowedDirs.some(dir => {
|
|
1626
|
-
const
|
|
1638
|
+
const resolvedDir = safeRealpath(dir);
|
|
1627
1639
|
// Ensure the path is within the allowed directory (add separator to prevent prefix attacks)
|
|
1628
|
-
return absolutePath ===
|
|
1640
|
+
return absolutePath === resolvedDir || absolutePath.startsWith(resolvedDir + sep);
|
|
1629
1641
|
});
|
|
1630
1642
|
} else {
|
|
1631
1643
|
// For relative paths, try resolving against each allowed directory
|
|
1632
1644
|
for (const dir of allowedDirs) {
|
|
1633
|
-
const
|
|
1634
|
-
const resolvedPath =
|
|
1645
|
+
const resolvedDir = safeRealpath(dir);
|
|
1646
|
+
const resolvedPath = safeRealpath(resolve(dir, imagePath));
|
|
1635
1647
|
// Ensure the resolved path is within the allowed directory
|
|
1636
|
-
if (resolvedPath ===
|
|
1648
|
+
if (resolvedPath === resolvedDir || resolvedPath.startsWith(resolvedDir + sep)) {
|
|
1637
1649
|
absolutePath = resolvedPath;
|
|
1638
1650
|
isPathAllowed = true;
|
|
1639
1651
|
break;
|
|
@@ -1870,7 +1882,8 @@ export class ProbeAgent {
|
|
|
1870
1882
|
return this.architectureContext;
|
|
1871
1883
|
}
|
|
1872
1884
|
|
|
1873
|
-
|
|
1885
|
+
// Use workspaceRoot for consistent path handling
|
|
1886
|
+
const rootDirectory = this.workspaceRoot || (this.allowedFolders.length > 0 ? this.allowedFolders[0] : process.cwd());
|
|
1874
1887
|
const configuredName =
|
|
1875
1888
|
typeof this.architectureFileName === 'string' ? this.architectureFileName.trim() : '';
|
|
1876
1889
|
const hasConfiguredName = !!configuredName;
|
|
@@ -2028,6 +2041,10 @@ export class ProbeAgent {
|
|
|
2028
2041
|
}
|
|
2029
2042
|
|
|
2030
2043
|
_getSkillsRepoRoot() {
|
|
2044
|
+
// Use workspaceRoot for consistent path handling
|
|
2045
|
+
if (this.workspaceRoot) {
|
|
2046
|
+
return resolve(this.workspaceRoot);
|
|
2047
|
+
}
|
|
2031
2048
|
if (this.allowedFolders && this.allowedFolders.length > 0) {
|
|
2032
2049
|
return resolve(this.allowedFolders[0]);
|
|
2033
2050
|
}
|
|
@@ -2108,7 +2125,7 @@ ${extractGuidance}
|
|
|
2108
2125
|
// Add repository structure if available
|
|
2109
2126
|
if (this.fileList) {
|
|
2110
2127
|
systemPrompt += `\n\n# Repository Structure\n`;
|
|
2111
|
-
systemPrompt += `You are working with a repository located at: ${this.
|
|
2128
|
+
systemPrompt += `You are working with a repository located at: ${this.workspaceRoot}\n\n`;
|
|
2112
2129
|
systemPrompt += `Here's an overview of the repository structure (showing up to 100 most relevant files):\n\n`;
|
|
2113
2130
|
systemPrompt += '```\n' + this.fileList + '\n```\n';
|
|
2114
2131
|
}
|
|
@@ -2170,7 +2187,7 @@ ${extractGuidance}
|
|
|
2170
2187
|
// Add repository structure if available
|
|
2171
2188
|
if (this.fileList) {
|
|
2172
2189
|
systemPrompt += `\n\n# Repository Structure\n`;
|
|
2173
|
-
systemPrompt += `You are working with a repository located at: ${this.
|
|
2190
|
+
systemPrompt += `You are working with a repository located at: ${this.workspaceRoot}\n\n`;
|
|
2174
2191
|
systemPrompt += `Here's an overview of the repository structure (showing up to 100 most relevant files):\n\n`;
|
|
2175
2192
|
systemPrompt += '```\n' + this.fileList + '\n```\n';
|
|
2176
2193
|
}
|
|
@@ -2484,10 +2501,29 @@ Follow these instructions carefully:
|
|
|
2484
2501
|
}
|
|
2485
2502
|
}
|
|
2486
2503
|
|
|
2487
|
-
// Add folder information
|
|
2488
|
-
const searchDirectory = this.
|
|
2504
|
+
// Add folder information using workspace root and relative paths
|
|
2505
|
+
const searchDirectory = this.workspaceRoot;
|
|
2489
2506
|
if (this.debug) {
|
|
2490
|
-
console.log(`[DEBUG] Generating file list for
|
|
2507
|
+
console.log(`[DEBUG] Generating file list for workspace root: ${searchDirectory}...`);
|
|
2508
|
+
}
|
|
2509
|
+
|
|
2510
|
+
// Convert allowed folders to relative paths for cleaner AI context
|
|
2511
|
+
// Add ./ prefix to make it clear these are relative paths
|
|
2512
|
+
const relativeWorkspaces = this.allowedFolders.map(f => {
|
|
2513
|
+
const rel = toRelativePath(f, this.workspaceRoot);
|
|
2514
|
+
// Add ./ prefix if not already starting with . and not an absolute path
|
|
2515
|
+
if (rel && rel !== '.' && !rel.startsWith('.') && !rel.startsWith('/')) {
|
|
2516
|
+
return './' + rel;
|
|
2517
|
+
}
|
|
2518
|
+
return rel;
|
|
2519
|
+
}).filter(f => f && f !== '.');
|
|
2520
|
+
|
|
2521
|
+
// Describe available paths in a user-friendly way
|
|
2522
|
+
let workspaceDesc;
|
|
2523
|
+
if (relativeWorkspaces.length === 0) {
|
|
2524
|
+
workspaceDesc = '. (current directory)';
|
|
2525
|
+
} else {
|
|
2526
|
+
workspaceDesc = relativeWorkspaces.join(', ');
|
|
2491
2527
|
}
|
|
2492
2528
|
|
|
2493
2529
|
try {
|
|
@@ -2495,15 +2531,15 @@ Follow these instructions carefully:
|
|
|
2495
2531
|
directory: searchDirectory,
|
|
2496
2532
|
maxFiles: 100,
|
|
2497
2533
|
respectGitignore: !process.env.PROBE_NO_GITIGNORE || process.env.PROBE_NO_GITIGNORE === '',
|
|
2498
|
-
cwd:
|
|
2534
|
+
cwd: this.workspaceRoot
|
|
2499
2535
|
});
|
|
2500
2536
|
|
|
2501
|
-
systemMessage += `\n# Repository Structure\n\nYou are working with a
|
|
2537
|
+
systemMessage += `\n# Repository Structure\n\nYou are working with a workspace. Available paths: ${workspaceDesc}\n\nHere's an overview of the repository structure (showing up to 100 most relevant files):\n\n\`\`\`\n${files}\n\`\`\`\n\n`;
|
|
2502
2538
|
} catch (error) {
|
|
2503
2539
|
if (this.debug) {
|
|
2504
2540
|
console.log(`[DEBUG] Could not generate file list: ${error.message}`);
|
|
2505
2541
|
}
|
|
2506
|
-
systemMessage += `\n# Repository Structure\n\nYou are working with a
|
|
2542
|
+
systemMessage += `\n# Repository Structure\n\nYou are working with a workspace. Available paths: ${workspaceDesc}\n\n`;
|
|
2507
2543
|
}
|
|
2508
2544
|
|
|
2509
2545
|
// Add architecture context if available
|
|
@@ -2511,7 +2547,15 @@ Follow these instructions carefully:
|
|
|
2511
2547
|
systemMessage += this.getArchitectureSection();
|
|
2512
2548
|
|
|
2513
2549
|
if (this.allowedFolders.length > 0) {
|
|
2514
|
-
|
|
2550
|
+
const relativeAllowed = this.allowedFolders.map(f => {
|
|
2551
|
+
const rel = toRelativePath(f, this.workspaceRoot);
|
|
2552
|
+
// Add ./ prefix if not already starting with . and not an absolute path
|
|
2553
|
+
if (rel && rel !== '.' && !rel.startsWith('.') && !rel.startsWith('/')) {
|
|
2554
|
+
return './' + rel;
|
|
2555
|
+
}
|
|
2556
|
+
return rel;
|
|
2557
|
+
});
|
|
2558
|
+
systemMessage += `\n**Important**: For security reasons, you can only access these paths: ${relativeAllowed.join(', ')}\n\n`;
|
|
2515
2559
|
}
|
|
2516
2560
|
|
|
2517
2561
|
return systemMessage;
|
|
@@ -3234,6 +3278,8 @@ Follow these instructions carefully:
|
|
|
3234
3278
|
console.error(`[DEBUG] ========================================\n`);
|
|
3235
3279
|
}
|
|
3236
3280
|
|
|
3281
|
+
// Add assistant message with tool call (matching native tool pattern)
|
|
3282
|
+
currentMessages.push({ role: 'assistant', content: assistantResponseContent });
|
|
3237
3283
|
currentMessages.push({ role: 'user', content: `<tool_result>\n${toolResultContent}\n</tool_result>` });
|
|
3238
3284
|
} catch (error) {
|
|
3239
3285
|
// Record MCP tool end event (failure)
|
|
@@ -3257,24 +3303,27 @@ Follow these instructions carefully:
|
|
|
3257
3303
|
|
|
3258
3304
|
// Format error with structured information for AI
|
|
3259
3305
|
const errorXml = formatErrorForAI(error);
|
|
3306
|
+
// Add assistant message with tool call (matching native tool pattern)
|
|
3307
|
+
currentMessages.push({ role: 'assistant', content: assistantResponseContent });
|
|
3260
3308
|
currentMessages.push({ role: 'user', content: `<tool_result>\n${errorXml}\n</tool_result>` });
|
|
3261
3309
|
}
|
|
3262
3310
|
} else if (this.toolImplementations[toolName]) {
|
|
3263
3311
|
// Execute native tool
|
|
3264
3312
|
try {
|
|
3265
3313
|
// Add sessionId and workingDirectory to params for tool execution
|
|
3266
|
-
// Validate and resolve workingDirectory
|
|
3267
|
-
//
|
|
3268
|
-
let resolvedWorkingDirectory = this.cwd || (this.allowedFolders && this.allowedFolders[0]) || process.cwd();
|
|
3314
|
+
// Validate and resolve workingDirectory using safeRealpath for symlink security
|
|
3315
|
+
// Consistent fallback chain: workspaceRoot > cwd > allowedFolders[0] > process.cwd()
|
|
3316
|
+
let resolvedWorkingDirectory = this.workspaceRoot || this.cwd || (this.allowedFolders && this.allowedFolders[0]) || process.cwd();
|
|
3269
3317
|
if (params.workingDirectory) {
|
|
3270
3318
|
// Resolve relative paths against the current working directory context, not process.cwd()
|
|
3271
|
-
|
|
3319
|
+
// Use safeRealpath to resolve symlinks and prevent bypass attacks
|
|
3320
|
+
const requestedDir = safeRealpath(isAbsolute(params.workingDirectory)
|
|
3272
3321
|
? resolve(params.workingDirectory)
|
|
3273
|
-
: resolve(resolvedWorkingDirectory, params.workingDirectory);
|
|
3322
|
+
: resolve(resolvedWorkingDirectory, params.workingDirectory));
|
|
3274
3323
|
// Check if the requested directory is within allowed folders
|
|
3275
3324
|
const isWithinAllowed = !this.allowedFolders || this.allowedFolders.length === 0 ||
|
|
3276
3325
|
this.allowedFolders.some(folder => {
|
|
3277
|
-
const resolvedFolder =
|
|
3326
|
+
const resolvedFolder = safeRealpath(folder);
|
|
3278
3327
|
return requestedDir === resolvedFolder || requestedDir.startsWith(resolvedFolder + sep);
|
|
3279
3328
|
});
|
|
3280
3329
|
if (isWithinAllowed) {
|
|
@@ -3887,7 +3936,7 @@ Convert your previous response content into actual JSON data that follows this s
|
|
|
3887
3936
|
|
|
3888
3937
|
const mermaidValidation = await validateAndFixMermaidResponse(finalResult, {
|
|
3889
3938
|
debug: this.debug,
|
|
3890
|
-
path: this.allowedFolders[0],
|
|
3939
|
+
path: this.workspaceRoot || this.allowedFolders[0],
|
|
3891
3940
|
provider: this.clientApiProvider,
|
|
3892
3941
|
model: this.model,
|
|
3893
3942
|
tracer: this.tracer
|
|
@@ -3977,7 +4026,7 @@ Convert your previous response content into actual JSON data that follows this s
|
|
|
3977
4026
|
|
|
3978
4027
|
const { JsonFixingAgent } = await import('./schemaUtils.js');
|
|
3979
4028
|
const jsonFixer = new JsonFixingAgent({
|
|
3980
|
-
path: this.allowedFolders[0],
|
|
4029
|
+
path: this.workspaceRoot || this.allowedFolders[0],
|
|
3981
4030
|
provider: this.clientApiProvider,
|
|
3982
4031
|
model: this.model,
|
|
3983
4032
|
debug: this.debug,
|
|
@@ -4065,7 +4114,7 @@ Convert your previous response content into actual JSON data that follows this s
|
|
|
4065
4114
|
|
|
4066
4115
|
const mermaidValidation = await validateAndFixMermaidResponse(finalResult, {
|
|
4067
4116
|
debug: this.debug,
|
|
4068
|
-
path: this.allowedFolders[0],
|
|
4117
|
+
path: this.workspaceRoot || this.allowedFolders[0],
|
|
4069
4118
|
provider: this.clientApiProvider,
|
|
4070
4119
|
model: this.model,
|
|
4071
4120
|
tracer: this.tracer
|
|
@@ -4221,7 +4270,7 @@ Convert your previous response content into actual JSON data that follows this s
|
|
|
4221
4270
|
|
|
4222
4271
|
const finalMermaidValidation = await validateAndFixMermaidResponse(finalResult, {
|
|
4223
4272
|
debug: this.debug,
|
|
4224
|
-
path: this.allowedFolders[0],
|
|
4273
|
+
path: this.workspaceRoot || this.allowedFolders[0],
|
|
4225
4274
|
provider: this.clientApiProvider,
|
|
4226
4275
|
model: this.model,
|
|
4227
4276
|
tracer: this.tracer
|
|
@@ -4419,7 +4468,7 @@ Convert your previous response content into actual JSON data that follows this s
|
|
|
4419
4468
|
allowEdit: this.allowEdit,
|
|
4420
4469
|
enableDelegate: this.enableDelegate,
|
|
4421
4470
|
architectureFileName: this.architectureFileName,
|
|
4422
|
-
|
|
4471
|
+
// Pass allowedFolders which will recompute workspaceRoot correctly
|
|
4423
4472
|
allowedFolders: [...this.allowedFolders],
|
|
4424
4473
|
cwd: this.cwd, // Preserve explicit working directory
|
|
4425
4474
|
provider: this.clientApiProvider,
|