@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
package/build/agent/index.js
CHANGED
|
@@ -3288,7 +3288,26 @@ var init_error_types = __esm({
|
|
|
3288
3288
|
|
|
3289
3289
|
// src/utils/path-validation.js
|
|
3290
3290
|
import path4 from "path";
|
|
3291
|
-
import { promises as fs5 } from "fs";
|
|
3291
|
+
import { promises as fs5, realpathSync } from "fs";
|
|
3292
|
+
function safeRealpath(inputPath) {
|
|
3293
|
+
try {
|
|
3294
|
+
return realpathSync(inputPath);
|
|
3295
|
+
} catch (error) {
|
|
3296
|
+
const normalized = path4.normalize(inputPath);
|
|
3297
|
+
const parts = normalized.split(path4.sep);
|
|
3298
|
+
for (let i = parts.length - 1; i >= 0; i--) {
|
|
3299
|
+
const ancestorPath = parts.slice(0, i).join(path4.sep) || path4.sep;
|
|
3300
|
+
try {
|
|
3301
|
+
const resolvedAncestor = realpathSync(ancestorPath);
|
|
3302
|
+
const remainingParts = parts.slice(i);
|
|
3303
|
+
return path4.join(resolvedAncestor, ...remainingParts);
|
|
3304
|
+
} catch (ancestorError) {
|
|
3305
|
+
continue;
|
|
3306
|
+
}
|
|
3307
|
+
}
|
|
3308
|
+
return normalized;
|
|
3309
|
+
}
|
|
3310
|
+
}
|
|
3292
3311
|
async function validateCwdPath(inputPath, defaultPath = process.cwd()) {
|
|
3293
3312
|
const targetPath = inputPath || defaultPath;
|
|
3294
3313
|
const normalizedPath = path4.normalize(path4.resolve(targetPath));
|
|
@@ -3321,6 +3340,53 @@ async function validateCwdPath(inputPath, defaultPath = process.cwd()) {
|
|
|
3321
3340
|
}
|
|
3322
3341
|
return normalizedPath;
|
|
3323
3342
|
}
|
|
3343
|
+
function getCommonPrefix(folders) {
|
|
3344
|
+
if (!folders || folders.length === 0) {
|
|
3345
|
+
return process.cwd();
|
|
3346
|
+
}
|
|
3347
|
+
if (folders.length === 1) {
|
|
3348
|
+
return safeRealpath(folders[0]);
|
|
3349
|
+
}
|
|
3350
|
+
const normalized = folders.map((f) => safeRealpath(f));
|
|
3351
|
+
const segments = normalized.map((f) => f.split(path4.sep));
|
|
3352
|
+
const minLen = Math.min(...segments.map((s) => s.length));
|
|
3353
|
+
const commonSegments = [];
|
|
3354
|
+
for (let i = 0; i < minLen; i++) {
|
|
3355
|
+
const segment = segments[0][i];
|
|
3356
|
+
if (segments.every((s) => s[i] === segment)) {
|
|
3357
|
+
commonSegments.push(segment);
|
|
3358
|
+
} else {
|
|
3359
|
+
break;
|
|
3360
|
+
}
|
|
3361
|
+
}
|
|
3362
|
+
if (commonSegments.length === 0) {
|
|
3363
|
+
return normalized[0];
|
|
3364
|
+
}
|
|
3365
|
+
if (commonSegments.length === 1 && /^[a-zA-Z]:$/.test(commonSegments[0])) {
|
|
3366
|
+
return normalized[0];
|
|
3367
|
+
}
|
|
3368
|
+
if (commonSegments.length === 1 && commonSegments[0] === "") {
|
|
3369
|
+
return normalized[0];
|
|
3370
|
+
}
|
|
3371
|
+
return commonSegments.join(path4.sep);
|
|
3372
|
+
}
|
|
3373
|
+
function toRelativePath(absolutePath, workspaceRoot) {
|
|
3374
|
+
if (!absolutePath || !workspaceRoot) {
|
|
3375
|
+
return absolutePath;
|
|
3376
|
+
}
|
|
3377
|
+
let normalized = safeRealpath(absolutePath);
|
|
3378
|
+
let normalizedRoot = safeRealpath(workspaceRoot);
|
|
3379
|
+
while (normalizedRoot.length > 1 && normalizedRoot.endsWith(path4.sep)) {
|
|
3380
|
+
normalizedRoot = normalizedRoot.slice(0, -1);
|
|
3381
|
+
}
|
|
3382
|
+
if (normalized === normalizedRoot) {
|
|
3383
|
+
return ".";
|
|
3384
|
+
}
|
|
3385
|
+
if (normalized.startsWith(normalizedRoot + path4.sep)) {
|
|
3386
|
+
return path4.relative(normalizedRoot, normalized);
|
|
3387
|
+
}
|
|
3388
|
+
return absolutePath;
|
|
3389
|
+
}
|
|
3324
3390
|
var init_path_validation = __esm({
|
|
3325
3391
|
"src/utils/path-validation.js"() {
|
|
3326
3392
|
"use strict";
|
|
@@ -4515,6 +4581,7 @@ async function analyzeAll(options) {
|
|
|
4515
4581
|
sessionId,
|
|
4516
4582
|
debug = false,
|
|
4517
4583
|
cwd,
|
|
4584
|
+
workspaceRoot,
|
|
4518
4585
|
allowedFolders,
|
|
4519
4586
|
provider,
|
|
4520
4587
|
model,
|
|
@@ -4527,10 +4594,11 @@ async function analyzeAll(options) {
|
|
|
4527
4594
|
if (!question) {
|
|
4528
4595
|
throw new Error('The "question" parameter is required.');
|
|
4529
4596
|
}
|
|
4597
|
+
const effectiveWorkspaceRoot = workspaceRoot || cwd || allowedFolders?.[0] || path9;
|
|
4530
4598
|
const delegateOptions = {
|
|
4531
4599
|
debug,
|
|
4532
4600
|
sessionId,
|
|
4533
|
-
path:
|
|
4601
|
+
path: effectiveWorkspaceRoot,
|
|
4534
4602
|
allowedFolders,
|
|
4535
4603
|
provider,
|
|
4536
4604
|
model,
|
|
@@ -8747,29 +8815,33 @@ import { dirname, resolve, isAbsolute, sep } from "path";
|
|
|
8747
8815
|
import { existsSync } from "fs";
|
|
8748
8816
|
function isPathAllowed(filePath, allowedFolders) {
|
|
8749
8817
|
if (!allowedFolders || allowedFolders.length === 0) {
|
|
8750
|
-
const resolvedPath2 =
|
|
8751
|
-
const cwd =
|
|
8818
|
+
const resolvedPath2 = safeRealpath(filePath);
|
|
8819
|
+
const cwd = safeRealpath(process.cwd());
|
|
8752
8820
|
return resolvedPath2 === cwd || resolvedPath2.startsWith(cwd + sep);
|
|
8753
8821
|
}
|
|
8754
|
-
const resolvedPath =
|
|
8822
|
+
const resolvedPath = safeRealpath(filePath);
|
|
8755
8823
|
return allowedFolders.some((folder) => {
|
|
8756
|
-
const allowedPath =
|
|
8824
|
+
const allowedPath = safeRealpath(folder);
|
|
8757
8825
|
return resolvedPath === allowedPath || resolvedPath.startsWith(allowedPath + sep);
|
|
8758
8826
|
});
|
|
8759
8827
|
}
|
|
8760
8828
|
function parseFileToolOptions(options = {}) {
|
|
8829
|
+
const allowedFolders = options.allowedFolders || [];
|
|
8761
8830
|
return {
|
|
8762
8831
|
debug: options.debug || false,
|
|
8763
|
-
allowedFolders
|
|
8764
|
-
cwd: options.cwd
|
|
8832
|
+
allowedFolders,
|
|
8833
|
+
cwd: options.cwd,
|
|
8834
|
+
// Consistent fallback chain: workspaceRoot > cwd > allowedFolders[0] > process.cwd()
|
|
8835
|
+
workspaceRoot: options.workspaceRoot || options.cwd || allowedFolders.length > 0 && allowedFolders[0] || process.cwd()
|
|
8765
8836
|
};
|
|
8766
8837
|
}
|
|
8767
8838
|
var editTool, createTool, editSchema, createSchema, editDescription, createDescription, editToolDefinition, createToolDefinition;
|
|
8768
8839
|
var init_edit = __esm({
|
|
8769
8840
|
"src/tools/edit.js"() {
|
|
8770
8841
|
"use strict";
|
|
8842
|
+
init_path_validation();
|
|
8771
8843
|
editTool = (options = {}) => {
|
|
8772
|
-
const { debug, allowedFolders, cwd } = parseFileToolOptions(options);
|
|
8844
|
+
const { debug, allowedFolders, cwd, workspaceRoot } = parseFileToolOptions(options);
|
|
8773
8845
|
return tool({
|
|
8774
8846
|
name: "edit",
|
|
8775
8847
|
description: `Edit files using exact string replacement (Claude Code style).
|
|
@@ -8825,7 +8897,8 @@ Important:
|
|
|
8825
8897
|
console.error(`[Edit] Attempting to edit file: ${resolvedPath}`);
|
|
8826
8898
|
}
|
|
8827
8899
|
if (!isPathAllowed(resolvedPath, allowedFolders)) {
|
|
8828
|
-
|
|
8900
|
+
const relativePath = toRelativePath(resolvedPath, workspaceRoot);
|
|
8901
|
+
return `Error editing file: Permission denied - ${relativePath} is outside allowed directories`;
|
|
8829
8902
|
}
|
|
8830
8903
|
if (!existsSync(resolvedPath)) {
|
|
8831
8904
|
return `Error editing file: File not found - ${file_path}`;
|
|
@@ -8861,7 +8934,7 @@ Important:
|
|
|
8861
8934
|
});
|
|
8862
8935
|
};
|
|
8863
8936
|
createTool = (options = {}) => {
|
|
8864
|
-
const { debug, allowedFolders, cwd } = parseFileToolOptions(options);
|
|
8937
|
+
const { debug, allowedFolders, cwd, workspaceRoot } = parseFileToolOptions(options);
|
|
8865
8938
|
return tool({
|
|
8866
8939
|
name: "create",
|
|
8867
8940
|
description: `Create new files with specified content.
|
|
@@ -8909,7 +8982,8 @@ Important:
|
|
|
8909
8982
|
console.error(`[Create] Attempting to create file: ${resolvedPath}`);
|
|
8910
8983
|
}
|
|
8911
8984
|
if (!isPathAllowed(resolvedPath, allowedFolders)) {
|
|
8912
|
-
|
|
8985
|
+
const relativePath = toRelativePath(resolvedPath, workspaceRoot);
|
|
8986
|
+
return `Error creating file: Permission denied - ${relativePath} is outside allowed directories`;
|
|
8913
8987
|
}
|
|
8914
8988
|
if (existsSync(resolvedPath) && !overwrite) {
|
|
8915
8989
|
return `Error creating file: File already exists - ${file_path}. Use overwrite: true to replace it.`;
|
|
@@ -10471,7 +10545,7 @@ var init_vercel = __esm({
|
|
|
10471
10545
|
});
|
|
10472
10546
|
};
|
|
10473
10547
|
delegateTool = (options = {}) => {
|
|
10474
|
-
const { debug = false, timeout = 300, cwd, allowedFolders, enableBash = false, bashConfig, architectureFileName, enableMcp = false, mcpConfig = null, mcpConfigPath = null, delegationManager = null } = options;
|
|
10548
|
+
const { debug = false, timeout = 300, cwd, allowedFolders, workspaceRoot, enableBash = false, bashConfig, architectureFileName, enableMcp = false, mcpConfig = null, mcpConfigPath = null, delegationManager = null } = options;
|
|
10475
10549
|
return tool2({
|
|
10476
10550
|
name: "delegate",
|
|
10477
10551
|
description: delegateDescription,
|
|
@@ -10504,8 +10578,8 @@ var init_vercel = __esm({
|
|
|
10504
10578
|
if (searchDelegate !== void 0 && typeof searchDelegate !== "boolean") {
|
|
10505
10579
|
throw new TypeError("searchDelegate must be a boolean if provided");
|
|
10506
10580
|
}
|
|
10507
|
-
const
|
|
10508
|
-
const effectivePath = path9 ||
|
|
10581
|
+
const effectiveWorkspaceRoot = workspaceRoot || allowedFolders && allowedFolders[0];
|
|
10582
|
+
const effectivePath = path9 || effectiveWorkspaceRoot || cwd;
|
|
10509
10583
|
if (debug) {
|
|
10510
10584
|
console.error(`Executing delegate with task: "${task.substring(0, 100)}${task.length > 100 ? "..." : ""}"`);
|
|
10511
10585
|
if (parentSessionId) {
|
|
@@ -10542,7 +10616,7 @@ var init_vercel = __esm({
|
|
|
10542
10616
|
});
|
|
10543
10617
|
};
|
|
10544
10618
|
analyzeAllTool = (options = {}) => {
|
|
10545
|
-
const { sessionId, debug = false, delegationManager = null } = options;
|
|
10619
|
+
const { sessionId, debug = false, delegationManager = null, workspaceRoot } = options;
|
|
10546
10620
|
return tool2({
|
|
10547
10621
|
name: "analyze_all",
|
|
10548
10622
|
description: analyzeAllDescription,
|
|
@@ -10561,12 +10635,14 @@ var init_vercel = __esm({
|
|
|
10561
10635
|
console.error(`[analyze_all] Question: ${question}`);
|
|
10562
10636
|
console.error(`[analyze_all] Path: ${searchPath}`);
|
|
10563
10637
|
}
|
|
10638
|
+
const effectiveWorkspaceRoot = workspaceRoot || options.cwd || options.allowedFolders && options.allowedFolders[0];
|
|
10564
10639
|
const result = await analyzeAll({
|
|
10565
10640
|
question,
|
|
10566
10641
|
path: searchPath,
|
|
10567
10642
|
sessionId,
|
|
10568
10643
|
debug,
|
|
10569
10644
|
cwd: options.cwd,
|
|
10645
|
+
workspaceRoot: effectiveWorkspaceRoot,
|
|
10570
10646
|
allowedFolders: options.allowedFolders,
|
|
10571
10647
|
provider: options.provider,
|
|
10572
10648
|
model: options.model,
|
|
@@ -12042,14 +12118,17 @@ var init_bash = __esm({
|
|
|
12042
12118
|
"use strict";
|
|
12043
12119
|
init_bashPermissions();
|
|
12044
12120
|
init_bashExecutor();
|
|
12121
|
+
init_path_validation();
|
|
12045
12122
|
bashTool = (options = {}) => {
|
|
12046
12123
|
const {
|
|
12047
12124
|
bashConfig = {},
|
|
12048
12125
|
debug = false,
|
|
12049
12126
|
cwd,
|
|
12050
12127
|
allowedFolders = [],
|
|
12128
|
+
workspaceRoot: providedWorkspaceRoot,
|
|
12051
12129
|
tracer = null
|
|
12052
12130
|
} = options;
|
|
12131
|
+
const workspaceRoot = providedWorkspaceRoot || cwd || allowedFolders.length > 0 && allowedFolders[0] || process.cwd();
|
|
12053
12132
|
const permissionChecker = new BashPermissionChecker({
|
|
12054
12133
|
allow: bashConfig.allow,
|
|
12055
12134
|
deny: bashConfig.deny,
|
|
@@ -12065,6 +12144,9 @@ var init_bash = __esm({
|
|
|
12065
12144
|
if (cwd) {
|
|
12066
12145
|
return cwd;
|
|
12067
12146
|
}
|
|
12147
|
+
if (workspaceRoot) {
|
|
12148
|
+
return workspaceRoot;
|
|
12149
|
+
}
|
|
12068
12150
|
if (allowedFolders && allowedFolders.length > 0) {
|
|
12069
12151
|
return allowedFolders[0];
|
|
12070
12152
|
}
|
|
@@ -12152,13 +12234,15 @@ For code exploration, try these safe alternatives:
|
|
|
12152
12234
|
const defaultDir = getDefaultWorkingDirectory();
|
|
12153
12235
|
const workingDir = workingDirectory ? isAbsolute3(workingDirectory) ? resolve4(workingDirectory) : resolve4(defaultDir, workingDirectory) : defaultDir;
|
|
12154
12236
|
if (allowedFolders && allowedFolders.length > 0) {
|
|
12155
|
-
const resolvedWorkingDir =
|
|
12237
|
+
const resolvedWorkingDir = safeRealpath(workingDir);
|
|
12156
12238
|
const isAllowed = allowedFolders.some((folder) => {
|
|
12157
|
-
const resolvedFolder =
|
|
12239
|
+
const resolvedFolder = safeRealpath(folder);
|
|
12158
12240
|
return resolvedWorkingDir === resolvedFolder || resolvedWorkingDir.startsWith(resolvedFolder + sep2);
|
|
12159
12241
|
});
|
|
12160
12242
|
if (!isAllowed) {
|
|
12161
|
-
|
|
12243
|
+
const relativeDir = toRelativePath(workingDir, workspaceRoot);
|
|
12244
|
+
const relativeAllowed = allowedFolders.map((f) => toRelativePath(f, workspaceRoot));
|
|
12245
|
+
return `Error: Working directory "${relativeDir}" is not within allowed folders: ${relativeAllowed.join(", ")}`;
|
|
12162
12246
|
}
|
|
12163
12247
|
}
|
|
12164
12248
|
const executionOptions = {
|
|
@@ -16765,18 +16849,18 @@ import { fileURLToPath as fileURLToPath4 } from "node:url";
|
|
|
16765
16849
|
import { lstatSync, readdir as readdirCB, readdirSync, readlinkSync, realpathSync as rps } from "fs";
|
|
16766
16850
|
import * as actualFS from "node:fs";
|
|
16767
16851
|
import { lstat, readdir, readlink, realpath } from "node:fs/promises";
|
|
16768
|
-
var
|
|
16852
|
+
var realpathSync2, defaultFS, fsFromOption, uncDriveRegexp, uncToDrive, eitherSep, UNKNOWN, IFIFO, IFCHR, IFDIR, IFBLK, IFREG, IFLNK, IFSOCK, IFMT, IFMT_UNKNOWN, READDIR_CALLED, LSTAT_CALLED, ENOTDIR, ENOENT, ENOREADLINK, ENOREALPATH, ENOCHILD, TYPEMASK, entToType, normalizeCache, normalize, normalizeNocaseCache, normalizeNocase, ResolveCache, ChildrenCache, setAsCwd, PathBase, PathWin32, PathPosix, PathScurryBase, PathScurryWin32, PathScurryPosix, PathScurryDarwin, Path, PathScurry;
|
|
16769
16853
|
var init_esm4 = __esm({
|
|
16770
16854
|
"node_modules/path-scurry/dist/esm/index.js"() {
|
|
16771
16855
|
init_esm2();
|
|
16772
16856
|
init_esm3();
|
|
16773
|
-
|
|
16857
|
+
realpathSync2 = rps.native;
|
|
16774
16858
|
defaultFS = {
|
|
16775
16859
|
lstatSync,
|
|
16776
16860
|
readdir: readdirCB,
|
|
16777
16861
|
readdirSync,
|
|
16778
16862
|
readlinkSync,
|
|
16779
|
-
realpathSync,
|
|
16863
|
+
realpathSync: realpathSync2,
|
|
16780
16864
|
promises: {
|
|
16781
16865
|
lstat,
|
|
16782
16866
|
readdir,
|
|
@@ -70171,6 +70255,7 @@ var init_ProbeAgent = __esm({
|
|
|
70171
70255
|
init_FallbackManager();
|
|
70172
70256
|
init_contextCompactor();
|
|
70173
70257
|
init_error_types();
|
|
70258
|
+
init_path_validation();
|
|
70174
70259
|
init_outputTruncator();
|
|
70175
70260
|
init_delegate();
|
|
70176
70261
|
init_tasks();
|
|
@@ -70288,7 +70373,8 @@ var init_ProbeAgent = __esm({
|
|
|
70288
70373
|
} else {
|
|
70289
70374
|
this.allowedFolders = [process.cwd()];
|
|
70290
70375
|
}
|
|
70291
|
-
this.
|
|
70376
|
+
this.workspaceRoot = getCommonPrefix(this.allowedFolders);
|
|
70377
|
+
this.cwd = options.cwd || this.workspaceRoot;
|
|
70292
70378
|
this.clientApiProvider = options.provider || null;
|
|
70293
70379
|
this.clientApiModel = options.model || null;
|
|
70294
70380
|
this.clientApiKey = null;
|
|
@@ -70300,6 +70386,8 @@ var init_ProbeAgent = __esm({
|
|
|
70300
70386
|
console.log(`[DEBUG] Maximum tool iterations configured: ${MAX_TOOL_ITERATIONS}`);
|
|
70301
70387
|
console.log(`[DEBUG] Allow Edit (implement tool): ${this.allowEdit}`);
|
|
70302
70388
|
console.log(`[DEBUG] Search delegation enabled: ${this.searchDelegate}`);
|
|
70389
|
+
console.log(`[DEBUG] Workspace root: ${this.workspaceRoot}`);
|
|
70390
|
+
console.log(`[DEBUG] Working directory (cwd): ${this.cwd}`);
|
|
70303
70391
|
}
|
|
70304
70392
|
this.initializeTools();
|
|
70305
70393
|
this.history = [];
|
|
@@ -70647,8 +70735,9 @@ var init_ProbeAgent = __esm({
|
|
|
70647
70735
|
const configOptions = {
|
|
70648
70736
|
sessionId: this.sessionId,
|
|
70649
70737
|
debug: this.debug,
|
|
70650
|
-
// Use
|
|
70651
|
-
cwd: this.cwd
|
|
70738
|
+
// Use cwd (which defaults to workspaceRoot in constructor)
|
|
70739
|
+
cwd: this.cwd,
|
|
70740
|
+
workspaceRoot: this.workspaceRoot,
|
|
70652
70741
|
allowedFolders: this.allowedFolders,
|
|
70653
70742
|
outline: this.outline,
|
|
70654
70743
|
searchDelegate: this.searchDelegate,
|
|
@@ -71335,16 +71424,16 @@ var init_ProbeAgent = __esm({
|
|
|
71335
71424
|
let absolutePath;
|
|
71336
71425
|
let isPathAllowed2 = false;
|
|
71337
71426
|
if (isAbsolute5(imagePath)) {
|
|
71338
|
-
absolutePath =
|
|
71427
|
+
absolutePath = safeRealpath(resolve6(imagePath));
|
|
71339
71428
|
isPathAllowed2 = allowedDirs.some((dir) => {
|
|
71340
|
-
const
|
|
71341
|
-
return absolutePath ===
|
|
71429
|
+
const resolvedDir = safeRealpath(dir);
|
|
71430
|
+
return absolutePath === resolvedDir || absolutePath.startsWith(resolvedDir + sep5);
|
|
71342
71431
|
});
|
|
71343
71432
|
} else {
|
|
71344
71433
|
for (const dir of allowedDirs) {
|
|
71345
|
-
const
|
|
71346
|
-
const resolvedPath =
|
|
71347
|
-
if (resolvedPath ===
|
|
71434
|
+
const resolvedDir = safeRealpath(dir);
|
|
71435
|
+
const resolvedPath = safeRealpath(resolve6(dir, imagePath));
|
|
71436
|
+
if (resolvedPath === resolvedDir || resolvedPath.startsWith(resolvedDir + sep5)) {
|
|
71348
71437
|
absolutePath = resolvedPath;
|
|
71349
71438
|
isPathAllowed2 = true;
|
|
71350
71439
|
break;
|
|
@@ -71527,7 +71616,7 @@ var init_ProbeAgent = __esm({
|
|
|
71527
71616
|
if (this._architectureContextLoaded) {
|
|
71528
71617
|
return this.architectureContext;
|
|
71529
71618
|
}
|
|
71530
|
-
const rootDirectory = this.allowedFolders.length > 0 ? this.allowedFolders[0] : process.cwd();
|
|
71619
|
+
const rootDirectory = this.workspaceRoot || (this.allowedFolders.length > 0 ? this.allowedFolders[0] : process.cwd());
|
|
71531
71620
|
const configuredName = typeof this.architectureFileName === "string" ? this.architectureFileName.trim() : "";
|
|
71532
71621
|
const hasConfiguredName = !!configuredName;
|
|
71533
71622
|
let guidanceCandidates = [];
|
|
@@ -71664,6 +71753,9 @@ ${this.architectureContext.content}
|
|
|
71664
71753
|
`;
|
|
71665
71754
|
}
|
|
71666
71755
|
_getSkillsRepoRoot() {
|
|
71756
|
+
if (this.workspaceRoot) {
|
|
71757
|
+
return resolve6(this.workspaceRoot);
|
|
71758
|
+
}
|
|
71667
71759
|
if (this.allowedFolders && this.allowedFolders.length > 0) {
|
|
71668
71760
|
return resolve6(this.allowedFolders[0]);
|
|
71669
71761
|
}
|
|
@@ -71731,7 +71823,7 @@ Workspace: ${this.allowedFolders.join(", ")}`;
|
|
|
71731
71823
|
|
|
71732
71824
|
# Repository Structure
|
|
71733
71825
|
`;
|
|
71734
|
-
systemPrompt += `You are working with a repository located at: ${this.
|
|
71826
|
+
systemPrompt += `You are working with a repository located at: ${this.workspaceRoot}
|
|
71735
71827
|
|
|
71736
71828
|
`;
|
|
71737
71829
|
systemPrompt += `Here's an overview of the repository structure (showing up to 100 most relevant files):
|
|
@@ -71784,7 +71876,7 @@ Workspace: ${this.allowedFolders.join(", ")}`;
|
|
|
71784
71876
|
|
|
71785
71877
|
# Repository Structure
|
|
71786
71878
|
`;
|
|
71787
|
-
systemPrompt += `You are working with a repository located at: ${this.
|
|
71879
|
+
systemPrompt += `You are working with a repository located at: ${this.workspaceRoot}
|
|
71788
71880
|
|
|
71789
71881
|
`;
|
|
71790
71882
|
systemPrompt += `Here's an overview of the repository structure (showing up to 100 most relevant files):
|
|
@@ -72083,21 +72175,34 @@ For MCP tools, use JSON format within the params tag, e.g.:
|
|
|
72083
72175
|
`;
|
|
72084
72176
|
}
|
|
72085
72177
|
}
|
|
72086
|
-
const searchDirectory = this.
|
|
72178
|
+
const searchDirectory = this.workspaceRoot;
|
|
72087
72179
|
if (this.debug) {
|
|
72088
|
-
console.log(`[DEBUG] Generating file list for
|
|
72180
|
+
console.log(`[DEBUG] Generating file list for workspace root: ${searchDirectory}...`);
|
|
72181
|
+
}
|
|
72182
|
+
const relativeWorkspaces = this.allowedFolders.map((f) => {
|
|
72183
|
+
const rel = toRelativePath(f, this.workspaceRoot);
|
|
72184
|
+
if (rel && rel !== "." && !rel.startsWith(".") && !rel.startsWith("/")) {
|
|
72185
|
+
return "./" + rel;
|
|
72186
|
+
}
|
|
72187
|
+
return rel;
|
|
72188
|
+
}).filter((f) => f && f !== ".");
|
|
72189
|
+
let workspaceDesc;
|
|
72190
|
+
if (relativeWorkspaces.length === 0) {
|
|
72191
|
+
workspaceDesc = ". (current directory)";
|
|
72192
|
+
} else {
|
|
72193
|
+
workspaceDesc = relativeWorkspaces.join(", ");
|
|
72089
72194
|
}
|
|
72090
72195
|
try {
|
|
72091
72196
|
const files = await listFilesByLevel({
|
|
72092
72197
|
directory: searchDirectory,
|
|
72093
72198
|
maxFiles: 100,
|
|
72094
72199
|
respectGitignore: !process.env.PROBE_NO_GITIGNORE || process.env.PROBE_NO_GITIGNORE === "",
|
|
72095
|
-
cwd:
|
|
72200
|
+
cwd: this.workspaceRoot
|
|
72096
72201
|
});
|
|
72097
72202
|
systemMessage += `
|
|
72098
72203
|
# Repository Structure
|
|
72099
72204
|
|
|
72100
|
-
You are working with a
|
|
72205
|
+
You are working with a workspace. Available paths: ${workspaceDesc}
|
|
72101
72206
|
|
|
72102
72207
|
Here's an overview of the repository structure (showing up to 100 most relevant files):
|
|
72103
72208
|
|
|
@@ -72113,15 +72218,22 @@ ${files}
|
|
|
72113
72218
|
systemMessage += `
|
|
72114
72219
|
# Repository Structure
|
|
72115
72220
|
|
|
72116
|
-
You are working with a
|
|
72221
|
+
You are working with a workspace. Available paths: ${workspaceDesc}
|
|
72117
72222
|
|
|
72118
72223
|
`;
|
|
72119
72224
|
}
|
|
72120
72225
|
await this.loadArchitectureContext();
|
|
72121
72226
|
systemMessage += this.getArchitectureSection();
|
|
72122
72227
|
if (this.allowedFolders.length > 0) {
|
|
72228
|
+
const relativeAllowed = this.allowedFolders.map((f) => {
|
|
72229
|
+
const rel = toRelativePath(f, this.workspaceRoot);
|
|
72230
|
+
if (rel && rel !== "." && !rel.startsWith(".") && !rel.startsWith("/")) {
|
|
72231
|
+
return "./" + rel;
|
|
72232
|
+
}
|
|
72233
|
+
return rel;
|
|
72234
|
+
});
|
|
72123
72235
|
systemMessage += `
|
|
72124
|
-
**Important**: For security reasons, you can only
|
|
72236
|
+
**Important**: For security reasons, you can only access these paths: ${relativeAllowed.join(", ")}
|
|
72125
72237
|
|
|
72126
72238
|
`;
|
|
72127
72239
|
}
|
|
@@ -72635,6 +72747,7 @@ You are working with a repository located at: ${searchDirectory}
|
|
|
72635
72747
|
console.error(`[DEBUG] ========================================
|
|
72636
72748
|
`);
|
|
72637
72749
|
}
|
|
72750
|
+
currentMessages.push({ role: "assistant", content: assistantResponseContent });
|
|
72638
72751
|
currentMessages.push({ role: "user", content: `<tool_result>
|
|
72639
72752
|
${toolResultContent}
|
|
72640
72753
|
</tool_result>` });
|
|
@@ -72655,17 +72768,18 @@ ${toolResultContent}
|
|
|
72655
72768
|
`);
|
|
72656
72769
|
}
|
|
72657
72770
|
const errorXml = formatErrorForAI(error);
|
|
72771
|
+
currentMessages.push({ role: "assistant", content: assistantResponseContent });
|
|
72658
72772
|
currentMessages.push({ role: "user", content: `<tool_result>
|
|
72659
72773
|
${errorXml}
|
|
72660
72774
|
</tool_result>` });
|
|
72661
72775
|
}
|
|
72662
72776
|
} else if (this.toolImplementations[toolName]) {
|
|
72663
72777
|
try {
|
|
72664
|
-
let resolvedWorkingDirectory = this.cwd || this.allowedFolders && this.allowedFolders[0] || process.cwd();
|
|
72778
|
+
let resolvedWorkingDirectory = this.workspaceRoot || this.cwd || this.allowedFolders && this.allowedFolders[0] || process.cwd();
|
|
72665
72779
|
if (params.workingDirectory) {
|
|
72666
|
-
const requestedDir = isAbsolute5(params.workingDirectory) ? resolve6(params.workingDirectory) : resolve6(resolvedWorkingDirectory, params.workingDirectory);
|
|
72780
|
+
const requestedDir = safeRealpath(isAbsolute5(params.workingDirectory) ? resolve6(params.workingDirectory) : resolve6(resolvedWorkingDirectory, params.workingDirectory));
|
|
72667
72781
|
const isWithinAllowed = !this.allowedFolders || this.allowedFolders.length === 0 || this.allowedFolders.some((folder) => {
|
|
72668
|
-
const resolvedFolder =
|
|
72782
|
+
const resolvedFolder = safeRealpath(folder);
|
|
72669
72783
|
return requestedDir === resolvedFolder || requestedDir.startsWith(resolvedFolder + sep5);
|
|
72670
72784
|
});
|
|
72671
72785
|
if (isWithinAllowed) {
|
|
@@ -73134,7 +73248,7 @@ Convert your previous response content into actual JSON data that follows this s
|
|
|
73134
73248
|
}
|
|
73135
73249
|
const mermaidValidation = await validateAndFixMermaidResponse(finalResult, {
|
|
73136
73250
|
debug: this.debug,
|
|
73137
|
-
path: this.allowedFolders[0],
|
|
73251
|
+
path: this.workspaceRoot || this.allowedFolders[0],
|
|
73138
73252
|
provider: this.clientApiProvider,
|
|
73139
73253
|
model: this.model,
|
|
73140
73254
|
tracer: this.tracer
|
|
@@ -73207,7 +73321,7 @@ Convert your previous response content into actual JSON data that follows this s
|
|
|
73207
73321
|
}
|
|
73208
73322
|
const { JsonFixingAgent: JsonFixingAgent2 } = await Promise.resolve().then(() => (init_schemaUtils(), schemaUtils_exports));
|
|
73209
73323
|
const jsonFixer = new JsonFixingAgent2({
|
|
73210
|
-
path: this.allowedFolders[0],
|
|
73324
|
+
path: this.workspaceRoot || this.allowedFolders[0],
|
|
73211
73325
|
provider: this.clientApiProvider,
|
|
73212
73326
|
model: this.model,
|
|
73213
73327
|
debug: this.debug,
|
|
@@ -73276,7 +73390,7 @@ Convert your previous response content into actual JSON data that follows this s
|
|
|
73276
73390
|
}
|
|
73277
73391
|
const mermaidValidation = await validateAndFixMermaidResponse(finalResult, {
|
|
73278
73392
|
debug: this.debug,
|
|
73279
|
-
path: this.allowedFolders[0],
|
|
73393
|
+
path: this.workspaceRoot || this.allowedFolders[0],
|
|
73280
73394
|
provider: this.clientApiProvider,
|
|
73281
73395
|
model: this.model,
|
|
73282
73396
|
tracer: this.tracer
|
|
@@ -73408,7 +73522,7 @@ Convert your previous response content into actual JSON data that follows this s
|
|
|
73408
73522
|
}
|
|
73409
73523
|
const finalMermaidValidation = await validateAndFixMermaidResponse(finalResult, {
|
|
73410
73524
|
debug: this.debug,
|
|
73411
|
-
path: this.allowedFolders[0],
|
|
73525
|
+
path: this.workspaceRoot || this.allowedFolders[0],
|
|
73412
73526
|
provider: this.clientApiProvider,
|
|
73413
73527
|
model: this.model,
|
|
73414
73528
|
tracer: this.tracer
|
|
@@ -73558,8 +73672,7 @@ Convert your previous response content into actual JSON data that follows this s
|
|
|
73558
73672
|
allowEdit: this.allowEdit,
|
|
73559
73673
|
enableDelegate: this.enableDelegate,
|
|
73560
73674
|
architectureFileName: this.architectureFileName,
|
|
73561
|
-
|
|
73562
|
-
// Use first allowed folder as primary path
|
|
73675
|
+
// Pass allowedFolders which will recompute workspaceRoot correctly
|
|
73563
73676
|
allowedFolders: [...this.allowedFolders],
|
|
73564
73677
|
cwd: this.cwd,
|
|
73565
73678
|
// Preserve explicit working directory
|
|
@@ -514,6 +514,7 @@ export async function analyzeAll(options) {
|
|
|
514
514
|
sessionId,
|
|
515
515
|
debug = false,
|
|
516
516
|
cwd,
|
|
517
|
+
workspaceRoot,
|
|
517
518
|
allowedFolders,
|
|
518
519
|
provider,
|
|
519
520
|
model,
|
|
@@ -527,10 +528,14 @@ export async function analyzeAll(options) {
|
|
|
527
528
|
throw new Error('The "question" parameter is required.');
|
|
528
529
|
}
|
|
529
530
|
|
|
531
|
+
// Use workspaceRoot (computed common prefix) for consistent path handling
|
|
532
|
+
// Consistent fallback chain: workspaceRoot > cwd > allowedFolders[0] > path
|
|
533
|
+
const effectiveWorkspaceRoot = workspaceRoot || cwd || allowedFolders?.[0] || path;
|
|
534
|
+
|
|
530
535
|
const delegateOptions = {
|
|
531
536
|
debug,
|
|
532
537
|
sessionId,
|
|
533
|
-
path:
|
|
538
|
+
path: effectiveWorkspaceRoot,
|
|
534
539
|
allowedFolders,
|
|
535
540
|
provider,
|
|
536
541
|
model,
|
package/build/tools/bash.js
CHANGED
|
@@ -7,6 +7,7 @@ import { tool } from 'ai';
|
|
|
7
7
|
import { resolve, isAbsolute, sep } from 'path';
|
|
8
8
|
import { BashPermissionChecker } from '../agent/bashPermissions.js';
|
|
9
9
|
import { executeBashCommand, formatExecutionResult, validateExecutionOptions } from '../agent/bashExecutor.js';
|
|
10
|
+
import { toRelativePath, safeRealpath } from '../utils/path-validation.js';
|
|
10
11
|
|
|
11
12
|
/**
|
|
12
13
|
* Bash tool generator
|
|
@@ -32,9 +33,14 @@ export const bashTool = (options = {}) => {
|
|
|
32
33
|
debug = false,
|
|
33
34
|
cwd,
|
|
34
35
|
allowedFolders = [],
|
|
36
|
+
workspaceRoot: providedWorkspaceRoot,
|
|
35
37
|
tracer = null
|
|
36
38
|
} = options;
|
|
37
39
|
|
|
40
|
+
// Compute workspaceRoot with proper fallback chain
|
|
41
|
+
// Priority: explicit workspaceRoot > cwd > allowedFolders[0] > process.cwd()
|
|
42
|
+
const workspaceRoot = providedWorkspaceRoot || cwd || (allowedFolders.length > 0 && allowedFolders[0]) || process.cwd();
|
|
43
|
+
|
|
38
44
|
// Create permission checker with tracer for telemetry
|
|
39
45
|
const permissionChecker = new BashPermissionChecker({
|
|
40
46
|
allow: bashConfig.allow,
|
|
@@ -46,6 +52,7 @@ export const bashTool = (options = {}) => {
|
|
|
46
52
|
});
|
|
47
53
|
|
|
48
54
|
// Determine default working directory
|
|
55
|
+
// Priority: explicit bashConfig.workingDirectory > cwd (which defaults to workspaceRoot) > fallback
|
|
49
56
|
const getDefaultWorkingDirectory = () => {
|
|
50
57
|
if (bashConfig.workingDirectory) {
|
|
51
58
|
return bashConfig.workingDirectory;
|
|
@@ -53,6 +60,10 @@ export const bashTool = (options = {}) => {
|
|
|
53
60
|
if (cwd) {
|
|
54
61
|
return cwd;
|
|
55
62
|
}
|
|
63
|
+
// Use workspaceRoot (computed common prefix) for consistency with other tools
|
|
64
|
+
if (workspaceRoot) {
|
|
65
|
+
return workspaceRoot;
|
|
66
|
+
}
|
|
56
67
|
if (allowedFolders && allowedFolders.length > 0) {
|
|
57
68
|
return allowedFolders[0];
|
|
58
69
|
}
|
|
@@ -154,16 +165,20 @@ For code exploration, try these safe alternatives:
|
|
|
154
165
|
|
|
155
166
|
// Validate working directory is within allowed folders if specified
|
|
156
167
|
if (allowedFolders && allowedFolders.length > 0) {
|
|
157
|
-
|
|
168
|
+
// Use safeRealpath to resolve symlinks for security
|
|
169
|
+
// This prevents symlink bypass attacks (e.g., /tmp -> /private/tmp on macOS)
|
|
170
|
+
const resolvedWorkingDir = safeRealpath(workingDir);
|
|
158
171
|
const isAllowed = allowedFolders.some(folder => {
|
|
159
|
-
const resolvedFolder =
|
|
172
|
+
const resolvedFolder = safeRealpath(folder);
|
|
160
173
|
// Use exact match OR startsWith with separator to prevent bypass attacks
|
|
161
174
|
// e.g., '/tmp-malicious' should NOT match allowed folder '/tmp'
|
|
162
175
|
return resolvedWorkingDir === resolvedFolder || resolvedWorkingDir.startsWith(resolvedFolder + sep);
|
|
163
176
|
});
|
|
164
177
|
|
|
165
178
|
if (!isAllowed) {
|
|
166
|
-
|
|
179
|
+
const relativeDir = toRelativePath(workingDir, workspaceRoot);
|
|
180
|
+
const relativeAllowed = allowedFolders.map(f => toRelativePath(f, workspaceRoot));
|
|
181
|
+
return `Error: Working directory "${relativeDir}" is not within allowed folders: ${relativeAllowed.join(', ')}`;
|
|
167
182
|
}
|
|
168
183
|
}
|
|
169
184
|
|