@probelabs/probe 0.6.0-rc196 → 0.6.0-rc197

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@probelabs/probe",
3
- "version": "0.6.0-rc196",
3
+ "version": "0.6.0-rc197",
4
4
  "description": "Node.js wrapper for the probe code search tool",
5
5
  "main": "src/index.js",
6
6
  "module": "src/index.js",
@@ -2458,9 +2458,13 @@ Follow these instructions carefully:
2458
2458
  try {
2459
2459
  // Add sessionId and workingDirectory to params for tool execution
2460
2460
  // Validate and resolve workingDirectory
2461
- let resolvedWorkingDirectory = (this.allowedFolders && this.allowedFolders[0]) || process.cwd();
2461
+ // Priority: explicit cwd > first allowed folder > process.cwd()
2462
+ let resolvedWorkingDirectory = this.cwd || (this.allowedFolders && this.allowedFolders[0]) || process.cwd();
2462
2463
  if (params.workingDirectory) {
2463
- const requestedDir = resolve(params.workingDirectory);
2464
+ // Resolve relative paths against the current working directory context, not process.cwd()
2465
+ const requestedDir = isAbsolute(params.workingDirectory)
2466
+ ? resolve(params.workingDirectory)
2467
+ : resolve(resolvedWorkingDirectory, params.workingDirectory);
2464
2468
  // Check if the requested directory is within allowed folders
2465
2469
  const isWithinAllowed = !this.allowedFolders || this.allowedFolders.length === 0 ||
2466
2470
  this.allowedFolders.some(folder => {
package/src/tools/bash.js CHANGED
@@ -4,7 +4,7 @@
4
4
  */
5
5
 
6
6
  import { tool } from 'ai';
7
- import { resolve } from 'path';
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
10
 
@@ -144,14 +144,20 @@ For code exploration, try these safe alternatives:
144
144
  }
145
145
 
146
146
  // Determine working directory
147
- const workingDir = workingDirectory || getDefaultWorkingDirectory();
147
+ const defaultDir = getDefaultWorkingDirectory();
148
+ // Resolve relative paths against the default working directory context, not process.cwd()
149
+ const workingDir = workingDirectory
150
+ ? (isAbsolute(workingDirectory) ? resolve(workingDirectory) : resolve(defaultDir, workingDirectory))
151
+ : defaultDir;
148
152
 
149
153
  // Validate working directory is within allowed folders if specified
150
154
  if (allowedFolders && allowedFolders.length > 0) {
151
155
  const resolvedWorkingDir = resolve(workingDir);
152
156
  const isAllowed = allowedFolders.some(folder => {
153
157
  const resolvedFolder = resolve(folder);
154
- return resolvedWorkingDir.startsWith(resolvedFolder);
158
+ // Use exact match OR startsWith with separator to prevent bypass attacks
159
+ // e.g., '/tmp-malicious' should NOT match allowed folder '/tmp'
160
+ return resolvedWorkingDir === resolvedFolder || resolvedWorkingDir.startsWith(resolvedFolder + sep);
155
161
  });
156
162
 
157
163
  if (!isAllowed) {
@@ -4,6 +4,7 @@
4
4
  */
5
5
 
6
6
  import { z } from 'zod';
7
+ import { resolve, isAbsolute } from 'path';
7
8
 
8
9
  // Common schemas for tool parameters (used for internal execution after XML parsing)
9
10
  export const searchSchema = z.object({
@@ -516,9 +517,9 @@ export function createMessagePreview(message, charsPerSide = 200) {
516
517
 
517
518
  /**
518
519
  * Parse targets string into array of file specifications
519
- * Handles space-separated targets for extract tool
520
+ * Handles both space-separated and comma-separated targets for extract tool
520
521
  *
521
- * @param {string} targets - Space-separated file targets (e.g., "file1.rs:10-20 file2.rs#symbol")
522
+ * @param {string} targets - Space or comma-separated file targets (e.g., "file1.rs:10-20, file2.rs#symbol")
522
523
  * @returns {string[]} Array of individual file specifications
523
524
  *
524
525
  * @example
@@ -526,6 +527,10 @@ export function createMessagePreview(message, charsPerSide = 200) {
526
527
  * // Returns: ["file1.rs:10-20", "file2.rs:30-40"]
527
528
  *
528
529
  * @example
530
+ * parseTargets("file1.rs:10-20, file2.rs:30-40")
531
+ * // Returns: ["file1.rs:10-20", "file2.rs:30-40"]
532
+ *
533
+ * @example
529
534
  * parseTargets("session.rs#AuthService.login auth.rs:2-100 config.rs#DatabaseConfig")
530
535
  * // Returns: ["session.rs#AuthService.login", "auth.rs:2-100", "config.rs#DatabaseConfig"]
531
536
  */
@@ -534,6 +539,68 @@ export function parseTargets(targets) {
534
539
  return [];
535
540
  }
536
541
 
537
- // Split on any whitespace (spaces, tabs, newlines) and filter out empty strings
538
- return targets.split(/\s+/).filter(f => f.length > 0);
542
+ // Split on any whitespace or comma (with optional surrounding whitespace) and filter out empty strings
543
+ return targets.split(/[\s,]+/).filter(f => f.length > 0);
544
+ }
545
+
546
+ /**
547
+ * Parse and resolve paths from a comma-separated string
548
+ * Handles both relative and absolute paths, resolving relative paths against the cwd
549
+ *
550
+ * @param {string} pathStr - Path string, possibly comma-separated
551
+ * @param {string} cwd - Working directory for resolving relative paths
552
+ * @returns {string[]} Array of resolved paths
553
+ */
554
+ export function parseAndResolvePaths(pathStr, cwd) {
555
+ if (!pathStr) return [];
556
+
557
+ // Split on comma and trim whitespace
558
+ const paths = pathStr.split(',').map(p => p.trim()).filter(p => p.length > 0);
559
+
560
+ // Resolve relative paths against cwd
561
+ return paths.map(p => {
562
+ if (isAbsolute(p)) {
563
+ return p;
564
+ }
565
+ // Resolve relative path against cwd
566
+ return cwd ? resolve(cwd, p) : p;
567
+ });
568
+ }
569
+
570
+ /**
571
+ * Resolve a target path that may include line numbers or symbols
572
+ * Handles formats: "file.rs", "file.rs:10", "file.rs:10-20", "file.rs#symbol"
573
+ * On Windows, correctly handles drive letter colons (e.g., "C:\path\file.rs:42")
574
+ *
575
+ * @param {string} target - Target string with optional line number or symbol
576
+ * @param {string} cwd - Working directory for resolving relative paths
577
+ * @returns {string} Resolved target path with suffix preserved
578
+ */
579
+ export function resolveTargetPath(target, cwd) {
580
+ // On Windows, skip the drive letter colon (e.g., "C:" at index 1)
581
+ const searchStart = (target.length > 2 && target[1] === ':' && /[a-zA-Z]/.test(target[0])) ? 2 : 0;
582
+ const colonIdx = target.indexOf(':', searchStart);
583
+ const hashIdx = target.indexOf('#');
584
+ let filePart, suffix;
585
+
586
+ if (colonIdx !== -1 && (hashIdx === -1 || colonIdx < hashIdx)) {
587
+ // Has line number (file.rs:10 or file.rs:10-20)
588
+ filePart = target.substring(0, colonIdx);
589
+ suffix = target.substring(colonIdx);
590
+ } else if (hashIdx !== -1) {
591
+ // Has symbol (file.rs#symbol)
592
+ filePart = target.substring(0, hashIdx);
593
+ suffix = target.substring(hashIdx);
594
+ } else {
595
+ // Just file path
596
+ filePart = target;
597
+ suffix = '';
598
+ }
599
+
600
+ // Resolve relative path
601
+ if (!isAbsolute(filePart) && cwd) {
602
+ filePart = resolve(cwd, filePart);
603
+ }
604
+
605
+ return filePart + suffix;
539
606
  }
@@ -11,7 +11,7 @@ export { editTool, createTool } from './edit.js';
11
11
  // Export LangChain tools
12
12
  export { createSearchTool, createQueryTool, createExtractTool } from './langchain.js';
13
13
 
14
- // Export common schemas
14
+ // Export common schemas and utilities
15
15
  export {
16
16
  searchSchema,
17
17
  querySchema,
@@ -23,7 +23,9 @@ export {
23
23
  bashDescription,
24
24
  bashToolDefinition,
25
25
  attemptCompletionSchema,
26
- attemptCompletionToolDefinition
26
+ attemptCompletionToolDefinition,
27
+ parseAndResolvePaths,
28
+ resolveTargetPath
27
29
  } from './common.js';
28
30
 
29
31
  // Export edit and create schemas
@@ -8,7 +8,7 @@ import { search } from '../search.js';
8
8
  import { query } from '../query.js';
9
9
  import { extract } from '../extract.js';
10
10
  import { delegate } from '../delegate.js';
11
- import { searchSchema, querySchema, extractSchema, delegateSchema, searchDescription, queryDescription, extractDescription, delegateDescription, parseTargets } from './common.js';
11
+ import { searchSchema, querySchema, extractSchema, delegateSchema, searchDescription, queryDescription, extractDescription, delegateDescription, parseTargets, parseAndResolvePaths, resolveTargetPath } from './common.js';
12
12
 
13
13
  /**
14
14
  * Search tool generator
@@ -31,17 +31,20 @@ export const searchTool = (options = {}) => {
31
31
  // Use parameter maxTokens if provided, otherwise use the default
32
32
  const effectiveMaxTokens = paramMaxTokens || maxTokens;
33
33
 
34
- // Use the path from parameters if provided, otherwise use cwd from config
35
- let searchPath = path || options.cwd || '.';
34
+ // Parse and resolve paths (supports comma-separated and relative paths)
35
+ let searchPaths;
36
+ if (path) {
37
+ searchPaths = parseAndResolvePaths(path, options.cwd);
38
+ }
36
39
 
37
- // If path is "." or "./", use the cwd if available
38
- if ((searchPath === "." || searchPath === "./") && options.cwd) {
39
- if (debug) {
40
- console.error(`Using cwd "${options.cwd}" instead of "${searchPath}"`);
41
- }
42
- searchPath = options.cwd;
40
+ // Default to cwd or '.' if no paths provided
41
+ if (!searchPaths || searchPaths.length === 0) {
42
+ searchPaths = [options.cwd || '.'];
43
43
  }
44
44
 
45
+ // Join paths with space for CLI (probe search supports multiple paths)
46
+ const searchPath = searchPaths.join(' ');
47
+
45
48
  if (debug) {
46
49
  console.error(`Executing search with query: "${searchQuery}", path: "${searchPath}", exact: ${exact ? 'true' : 'false'}, language: ${language || 'all'}, session: ${sessionId || 'none'}`);
47
50
  }
@@ -90,17 +93,20 @@ export const queryTool = (options = {}) => {
90
93
  inputSchema: querySchema,
91
94
  execute: async ({ pattern, path, language, allow_tests }) => {
92
95
  try {
93
- // Use the path from parameters if provided, otherwise use cwd from config
94
- let queryPath = path || options.cwd || '.';
96
+ // Parse and resolve paths (supports comma-separated and relative paths)
97
+ let queryPaths;
98
+ if (path) {
99
+ queryPaths = parseAndResolvePaths(path, options.cwd);
100
+ }
95
101
 
96
- // If path is "." or "./", use the cwd if available
97
- if ((queryPath === "." || queryPath === "./") && options.cwd) {
98
- if (debug) {
99
- console.error(`Using cwd "${options.cwd}" instead of "${queryPath}"`);
100
- }
101
- queryPath = options.cwd;
102
+ // Default to cwd or '.' if no paths provided
103
+ if (!queryPaths || queryPaths.length === 0) {
104
+ queryPaths = [options.cwd || '.'];
102
105
  }
103
106
 
107
+ // Join paths with space for CLI (probe query supports multiple paths)
108
+ const queryPath = queryPaths.join(' ');
109
+
104
110
  if (debug) {
105
111
  console.error(`Executing query with pattern: "${pattern}", path: "${queryPath}", language: ${language || 'auto'}`);
106
112
  }
@@ -185,8 +191,11 @@ export const extractTool = (options = {}) => {
185
191
  };
186
192
  } else if (targets) {
187
193
  // Parse targets to handle line numbers and symbol names
188
- // Split on whitespace to support multiple targets in one call
189
- const files = parseTargets(targets);
194
+ // Now supports both whitespace and comma-separated targets
195
+ const parsedTargets = parseTargets(targets);
196
+
197
+ // Resolve relative paths in targets against cwd
198
+ const files = parsedTargets.map(target => resolveTargetPath(target, effectiveCwd));
190
199
 
191
200
  // Apply format mapping for outline-xml to xml
192
201
  let effectiveFormat = format;