@probelabs/probe 0.6.0-rc209 → 0.6.0-rc210
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-rc210-aarch64-apple-darwin.tar.gz +0 -0
- package/bin/binaries/probe-v0.6.0-rc210-aarch64-unknown-linux-musl.tar.gz +0 -0
- package/bin/binaries/probe-v0.6.0-rc210-x86_64-apple-darwin.tar.gz +0 -0
- package/bin/binaries/probe-v0.6.0-rc210-x86_64-pc-windows-msvc.zip +0 -0
- package/bin/binaries/probe-v0.6.0-rc210-x86_64-unknown-linux-musl.tar.gz +0 -0
- package/build/agent/ProbeAgent.js +85 -4
- package/build/agent/bashCommandUtils.js +98 -12
- package/build/agent/bashPermissions.js +207 -1
- package/build/agent/index.js +275 -15
- package/build/delegate.js +11 -2
- package/build/tools/vercel.js +5 -2
- package/cjs/agent/ProbeAgent.cjs +275 -15
- package/cjs/index.cjs +275 -15
- package/package.json +1 -1
- package/src/agent/ProbeAgent.js +85 -4
- package/src/agent/bashCommandUtils.js +98 -12
- package/src/agent/bashPermissions.js +207 -1
- package/src/delegate.js +11 -2
- package/src/tools/vercel.js +5 -2
- package/bin/binaries/probe-v0.6.0-rc209-aarch64-apple-darwin.tar.gz +0 -0
- package/bin/binaries/probe-v0.6.0-rc209-aarch64-unknown-linux-musl.tar.gz +0 -0
- package/bin/binaries/probe-v0.6.0-rc209-x86_64-apple-darwin.tar.gz +0 -0
- package/bin/binaries/probe-v0.6.0-rc209-x86_64-pc-windows-msvc.zip +0 -0
- package/bin/binaries/probe-v0.6.0-rc209-x86_64-unknown-linux-musl.tar.gz +0 -0
|
@@ -272,8 +272,119 @@ export class BashPermissionChecker {
|
|
|
272
272
|
return result;
|
|
273
273
|
}
|
|
274
274
|
|
|
275
|
+
/**
|
|
276
|
+
* Split a complex command into component commands by operators
|
|
277
|
+
*
|
|
278
|
+
* ## Escape Handling (Security-Critical)
|
|
279
|
+
*
|
|
280
|
+
* This function intentionally PRESERVES escape sequences (both backslash AND
|
|
281
|
+
* escaped character) in the output. This is step 1 of a 2-step parsing process:
|
|
282
|
+
*
|
|
283
|
+
* 1. _splitComplexCommand: Splits by operators, PRESERVES escapes → `echo "test\" && b"`
|
|
284
|
+
* 2. parseCommand: Interprets escapes in each component → args: ['test" && b']
|
|
285
|
+
*
|
|
286
|
+
* This differs from stripQuotedContent() in bashCommandUtils.js which REMOVES
|
|
287
|
+
* escapes entirely (for operator detection only).
|
|
288
|
+
*
|
|
289
|
+
* The security rationale: if we stripped escapes here, `\"` would become `"`,
|
|
290
|
+
* potentially causing incorrect quote boundary detection and allowing operator
|
|
291
|
+
* injection. By preserving escapes, parseCommand() can correctly interpret them.
|
|
292
|
+
*
|
|
293
|
+
* See bashCommandUtils.js module header for the full escape handling architecture.
|
|
294
|
+
*
|
|
295
|
+
* @private
|
|
296
|
+
* @param {string} command - Complex command to split
|
|
297
|
+
* @returns {string[]} Array of component commands (with escapes preserved)
|
|
298
|
+
*/
|
|
299
|
+
_splitComplexCommand(command) {
|
|
300
|
+
// Split by &&, ||, and | operators while respecting quotes and escape sequences
|
|
301
|
+
// IMPORTANT: Preserves backslashes so parseCommand() can interpret them correctly
|
|
302
|
+
const components = [];
|
|
303
|
+
let current = '';
|
|
304
|
+
let inQuotes = false;
|
|
305
|
+
let quoteChar = '';
|
|
306
|
+
let i = 0;
|
|
307
|
+
|
|
308
|
+
while (i < command.length) {
|
|
309
|
+
const char = command[i];
|
|
310
|
+
const nextChar = command[i + 1] || '';
|
|
311
|
+
|
|
312
|
+
// Handle escape sequences outside quotes
|
|
313
|
+
if (!inQuotes && char === '\\') {
|
|
314
|
+
// Keep the backslash and the next character
|
|
315
|
+
current += char;
|
|
316
|
+
if (nextChar) {
|
|
317
|
+
current += nextChar;
|
|
318
|
+
i += 2;
|
|
319
|
+
} else {
|
|
320
|
+
i++;
|
|
321
|
+
}
|
|
322
|
+
continue;
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
// Handle escape sequences inside double quotes (single quotes don't support escaping)
|
|
326
|
+
if (inQuotes && quoteChar === '"' && char === '\\' && nextChar) {
|
|
327
|
+
// Keep both the backslash and the escaped character
|
|
328
|
+
current += char + nextChar;
|
|
329
|
+
i += 2;
|
|
330
|
+
continue;
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
// Start of quoted section
|
|
334
|
+
if (!inQuotes && (char === '"' || char === "'")) {
|
|
335
|
+
inQuotes = true;
|
|
336
|
+
quoteChar = char;
|
|
337
|
+
current += char;
|
|
338
|
+
i++;
|
|
339
|
+
continue;
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
// End of quoted section
|
|
343
|
+
if (inQuotes && char === quoteChar) {
|
|
344
|
+
inQuotes = false;
|
|
345
|
+
quoteChar = '';
|
|
346
|
+
current += char;
|
|
347
|
+
i++;
|
|
348
|
+
continue;
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
// Check for operators only outside quotes
|
|
352
|
+
if (!inQuotes) {
|
|
353
|
+
// Check for && or ||
|
|
354
|
+
if ((char === '&' && nextChar === '&') || (char === '|' && nextChar === '|')) {
|
|
355
|
+
if (current.trim()) {
|
|
356
|
+
components.push(current.trim());
|
|
357
|
+
}
|
|
358
|
+
current = '';
|
|
359
|
+
i += 2; // Skip both characters
|
|
360
|
+
continue;
|
|
361
|
+
}
|
|
362
|
+
// Check for single pipe |
|
|
363
|
+
if (char === '|') {
|
|
364
|
+
if (current.trim()) {
|
|
365
|
+
components.push(current.trim());
|
|
366
|
+
}
|
|
367
|
+
current = '';
|
|
368
|
+
i++;
|
|
369
|
+
continue;
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
current += char;
|
|
374
|
+
i++;
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
// Add the last component
|
|
378
|
+
if (current.trim()) {
|
|
379
|
+
components.push(current.trim());
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
return components;
|
|
383
|
+
}
|
|
384
|
+
|
|
275
385
|
/**
|
|
276
386
|
* Check a complex command against complex patterns in allow/deny lists
|
|
387
|
+
* Also supports auto-allowing commands where all components are individually allowed
|
|
277
388
|
* @private
|
|
278
389
|
* @param {string} command - Complex command to check
|
|
279
390
|
* @returns {Object} Permission result
|
|
@@ -336,7 +447,102 @@ export class BashPermissionChecker {
|
|
|
336
447
|
}
|
|
337
448
|
}
|
|
338
449
|
|
|
339
|
-
// No
|
|
450
|
+
// No explicit complex pattern matched - try component-based evaluation
|
|
451
|
+
// Split the command by &&, ||, and | operators and check each component
|
|
452
|
+
const components = this._splitComplexCommand(command);
|
|
453
|
+
|
|
454
|
+
if (this.debug) {
|
|
455
|
+
console.log(`[BashPermissions] Checking ${components.length} command components: ${JSON.stringify(components)}`);
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
if (components.length > 1) {
|
|
459
|
+
// Check each component individually
|
|
460
|
+
const componentResults = [];
|
|
461
|
+
let allAllowed = true;
|
|
462
|
+
let deniedComponent = null;
|
|
463
|
+
let deniedReason = null;
|
|
464
|
+
|
|
465
|
+
for (const component of components) {
|
|
466
|
+
// Parse the component as a simple command
|
|
467
|
+
const parsed = parseCommand(component);
|
|
468
|
+
|
|
469
|
+
if (parsed.error || parsed.isComplex) {
|
|
470
|
+
// Component itself is complex or has an error - can't auto-allow
|
|
471
|
+
if (this.debug) {
|
|
472
|
+
console.log(`[BashPermissions] Component "${component}" is complex or has error: ${parsed.error}`);
|
|
473
|
+
}
|
|
474
|
+
allAllowed = false;
|
|
475
|
+
deniedComponent = component;
|
|
476
|
+
deniedReason = parsed.error || 'Component contains nested complex constructs';
|
|
477
|
+
break;
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
// Check against deny patterns
|
|
481
|
+
if (matchesAnyPattern(parsed, this.denyPatterns)) {
|
|
482
|
+
if (this.debug) {
|
|
483
|
+
console.log(`[BashPermissions] Component "${component}" matches deny pattern`);
|
|
484
|
+
}
|
|
485
|
+
allAllowed = false;
|
|
486
|
+
deniedComponent = component;
|
|
487
|
+
deniedReason = 'Component matches deny pattern';
|
|
488
|
+
break;
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
// Check against allow patterns
|
|
492
|
+
if (!matchesAnyPattern(parsed, this.allowPatterns)) {
|
|
493
|
+
if (this.debug) {
|
|
494
|
+
console.log(`[BashPermissions] Component "${component}" not in allow list`);
|
|
495
|
+
}
|
|
496
|
+
allAllowed = false;
|
|
497
|
+
deniedComponent = component;
|
|
498
|
+
deniedReason = 'Component not in allow list';
|
|
499
|
+
break;
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
componentResults.push({ component, parsed, allowed: true });
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
if (allAllowed) {
|
|
506
|
+
if (this.debug) {
|
|
507
|
+
console.log(`[BashPermissions] ALLOWED - all ${components.length} components passed individual checks`);
|
|
508
|
+
}
|
|
509
|
+
const result = {
|
|
510
|
+
allowed: true,
|
|
511
|
+
command: command,
|
|
512
|
+
isComplex: true,
|
|
513
|
+
allowedByComponents: true,
|
|
514
|
+
components: componentResults
|
|
515
|
+
};
|
|
516
|
+
this.recordBashEvent('permission.allowed', {
|
|
517
|
+
command,
|
|
518
|
+
isComplex: true,
|
|
519
|
+
allowedByComponents: true,
|
|
520
|
+
componentCount: components.length
|
|
521
|
+
});
|
|
522
|
+
return result;
|
|
523
|
+
} else {
|
|
524
|
+
if (this.debug) {
|
|
525
|
+
console.log(`[BashPermissions] DENIED - component "${deniedComponent}" failed: ${deniedReason}`);
|
|
526
|
+
}
|
|
527
|
+
const result = {
|
|
528
|
+
allowed: false,
|
|
529
|
+
reason: `Component "${deniedComponent}" not allowed: ${deniedReason}`,
|
|
530
|
+
command: command,
|
|
531
|
+
isComplex: true,
|
|
532
|
+
failedComponent: deniedComponent
|
|
533
|
+
};
|
|
534
|
+
this.recordBashEvent('permission.denied', {
|
|
535
|
+
command,
|
|
536
|
+
reason: 'component_not_allowed',
|
|
537
|
+
failedComponent: deniedComponent,
|
|
538
|
+
componentReason: deniedReason,
|
|
539
|
+
isComplex: true
|
|
540
|
+
});
|
|
541
|
+
return result;
|
|
542
|
+
}
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
// No matching complex pattern found and couldn't split into components - reject
|
|
340
546
|
if (this.debug) {
|
|
341
547
|
console.log(`[BashPermissions] DENIED - no matching complex pattern found`);
|
|
342
548
|
}
|
package/src/delegate.js
CHANGED
|
@@ -186,6 +186,9 @@ const delegationManager = new DelegationManager();
|
|
|
186
186
|
* @param {boolean} [options.searchDelegate] - Use delegated search in the subagent
|
|
187
187
|
* @param {Object|string} [options.schema] - Optional JSON schema to enforce response format
|
|
188
188
|
* @param {boolean} [options.enableTasks=false] - Enable task management for the subagent (isolated instance)
|
|
189
|
+
* @param {boolean} [options.enableMcp=false] - Enable MCP tool integration (inherited from parent)
|
|
190
|
+
* @param {Object} [options.mcpConfig] - MCP configuration object (inherited from parent)
|
|
191
|
+
* @param {string} [options.mcpConfigPath] - Path to MCP configuration file (inherited from parent)
|
|
189
192
|
* @returns {Promise<string>} The response from the delegate agent
|
|
190
193
|
*/
|
|
191
194
|
export async function delegate({
|
|
@@ -208,7 +211,10 @@ export async function delegate({
|
|
|
208
211
|
disableTools = false,
|
|
209
212
|
searchDelegate = undefined,
|
|
210
213
|
schema = null,
|
|
211
|
-
enableTasks = false
|
|
214
|
+
enableTasks = false,
|
|
215
|
+
enableMcp = false,
|
|
216
|
+
mcpConfig = null,
|
|
217
|
+
mcpConfigPath = null
|
|
212
218
|
}) {
|
|
213
219
|
if (!task || typeof task !== 'string') {
|
|
214
220
|
throw new Error('Task parameter is required and must be a string');
|
|
@@ -270,7 +276,10 @@ export async function delegate({
|
|
|
270
276
|
allowedTools,
|
|
271
277
|
disableTools,
|
|
272
278
|
searchDelegate,
|
|
273
|
-
enableTasks // Inherit from parent (subagent gets isolated TaskManager)
|
|
279
|
+
enableTasks, // Inherit from parent (subagent gets isolated TaskManager)
|
|
280
|
+
enableMcp, // Inherit from parent (subagent creates own MCPXmlBridge)
|
|
281
|
+
mcpConfig, // Inherit from parent
|
|
282
|
+
mcpConfigPath // Inherit from parent
|
|
274
283
|
});
|
|
275
284
|
|
|
276
285
|
if (debug) {
|
package/src/tools/vercel.js
CHANGED
|
@@ -469,7 +469,7 @@ export const extractTool = (options = {}) => {
|
|
|
469
469
|
* @returns {Object} Configured delegate tool
|
|
470
470
|
*/
|
|
471
471
|
export const delegateTool = (options = {}) => {
|
|
472
|
-
const { debug = false, timeout = 300, cwd, allowedFolders, enableBash = false, bashConfig, architectureFileName } = options;
|
|
472
|
+
const { debug = false, timeout = 300, cwd, allowedFolders, enableBash = false, bashConfig, architectureFileName, enableMcp = false, mcpConfig = null, mcpConfigPath = null } = options;
|
|
473
473
|
|
|
474
474
|
return tool({
|
|
475
475
|
name: 'delegate',
|
|
@@ -558,7 +558,10 @@ export const delegateTool = (options = {}) => {
|
|
|
558
558
|
enableBash,
|
|
559
559
|
bashConfig,
|
|
560
560
|
architectureFileName,
|
|
561
|
-
searchDelegate
|
|
561
|
+
searchDelegate,
|
|
562
|
+
enableMcp,
|
|
563
|
+
mcpConfig,
|
|
564
|
+
mcpConfigPath
|
|
562
565
|
});
|
|
563
566
|
|
|
564
567
|
return result;
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|