@probelabs/probe 0.6.0-rc256 → 0.6.0-rc258

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.
@@ -79,9 +79,19 @@ function matchesAnyPattern(parsedCommand, patterns) {
79
79
  export class BashPermissionChecker {
80
80
  /**
81
81
  * Create a permission checker
82
+ *
83
+ * Priority order (highest to lowest):
84
+ * 1. Custom deny — always blocks (user explicitly blocked it)
85
+ * 2. Custom allow — overrides default deny (user explicitly allowed it)
86
+ * 3. Default deny — blocks by default
87
+ * 4. Allow list — allows recognized safe commands
88
+ *
89
+ * This means `--bash-allow "git:push"` overrides the default deny for git:push
90
+ * without requiring `--no-default-bash-deny`.
91
+ *
82
92
  * @param {Object} config - Configuration options
83
- * @param {string[]} [config.allow] - Additional allow patterns
84
- * @param {string[]} [config.deny] - Additional deny patterns
93
+ * @param {string[]} [config.allow] - Additional allow patterns (override default deny)
94
+ * @param {string[]} [config.deny] - Additional deny patterns (always win)
85
95
  * @param {boolean} [config.disableDefaultAllow] - Disable default allow list
86
96
  * @param {boolean} [config.disableDefaultDeny] - Disable default deny list
87
97
  * @param {boolean} [config.debug] - Enable debug logging
@@ -90,38 +100,19 @@ export class BashPermissionChecker {
90
100
  constructor(config = {}) {
91
101
  this.debug = config.debug || false;
92
102
  this.tracer = config.tracer || null;
93
-
94
- // Build allow patterns
95
- this.allowPatterns = [];
96
- if (!config.disableDefaultAllow) {
97
- this.allowPatterns.push(...DEFAULT_ALLOW_PATTERNS);
98
- if (this.debug) {
99
- console.log(`[BashPermissions] Added ${DEFAULT_ALLOW_PATTERNS.length} default allow patterns`);
100
- }
101
- }
102
- if (config.allow && Array.isArray(config.allow)) {
103
- this.allowPatterns.push(...config.allow);
104
- if (this.debug) {
105
- console.log(`[BashPermissions] Added ${config.allow.length} custom allow patterns:`, config.allow);
106
- }
107
- }
108
103
 
109
- // Build deny patterns
110
- this.denyPatterns = [];
111
- if (!config.disableDefaultDeny) {
112
- this.denyPatterns.push(...DEFAULT_DENY_PATTERNS);
113
- if (this.debug) {
114
- console.log(`[BashPermissions] Added ${DEFAULT_DENY_PATTERNS.length} default deny patterns`);
115
- }
116
- }
117
- if (config.deny && Array.isArray(config.deny)) {
118
- this.denyPatterns.push(...config.deny);
119
- if (this.debug) {
120
- console.log(`[BashPermissions] Added ${config.deny.length} custom deny patterns:`, config.deny);
121
- }
122
- }
104
+ // Separate default and custom patterns for priority-based resolution
105
+ this.defaultAllowPatterns = config.disableDefaultAllow ? [] : [...DEFAULT_ALLOW_PATTERNS];
106
+ this.customAllowPatterns = (config.allow && Array.isArray(config.allow)) ? [...config.allow] : [];
107
+ this.allowPatterns = [...this.defaultAllowPatterns, ...this.customAllowPatterns];
108
+
109
+ this.defaultDenyPatterns = config.disableDefaultDeny ? [] : [...DEFAULT_DENY_PATTERNS];
110
+ this.customDenyPatterns = (config.deny && Array.isArray(config.deny)) ? [...config.deny] : [];
111
+ this.denyPatterns = [...this.defaultDenyPatterns, ...this.customDenyPatterns];
123
112
 
124
113
  if (this.debug) {
114
+ console.log(`[BashPermissions] Default allow: ${this.defaultAllowPatterns.length}, Custom allow: ${this.customAllowPatterns.length}`);
115
+ console.log(`[BashPermissions] Default deny: ${this.defaultDenyPatterns.length}, Custom deny: ${this.customDenyPatterns.length}`);
125
116
  console.log(`[BashPermissions] Total patterns - Allow: ${this.allowPatterns.length}, Deny: ${this.denyPatterns.length}`);
126
117
  }
127
118
 
@@ -129,8 +120,8 @@ export class BashPermissionChecker {
129
120
  this.recordBashEvent('permissions.initialized', {
130
121
  allowPatternCount: this.allowPatterns.length,
131
122
  denyPatternCount: this.denyPatterns.length,
132
- hasCustomAllowPatterns: !!(config.allow && config.allow.length > 0),
133
- hasCustomDenyPatterns: !!(config.deny && config.deny.length > 0),
123
+ hasCustomAllowPatterns: this.customAllowPatterns.length > 0,
124
+ hasCustomDenyPatterns: this.customDenyPatterns.length > 0,
134
125
  disableDefaultAllow: !!config.disableDefaultAllow,
135
126
  disableDefaultDeny: !!config.disableDefaultDeny
136
127
  });
@@ -212,9 +203,18 @@ export class BashPermissionChecker {
212
203
  console.log(`[BashPermissions] Parsed: ${parsed.command} with args: [${parsed.args.join(', ')}]`);
213
204
  }
214
205
 
215
- // Check deny patterns first (deny takes precedence)
216
- if (matchesAnyPattern(parsed, this.denyPatterns)) {
217
- const matchedPatterns = this.denyPatterns.filter(pattern => matchesPattern(parsed, pattern));
206
+ // Priority-based permission check:
207
+ // 1. Custom deny always wins
208
+ // 2. Custom allow overrides default deny
209
+ // 3. Default deny blocks
210
+ // 4. Allow list permits
211
+
212
+ // Step 1: Custom deny always wins
213
+ if (matchesAnyPattern(parsed, this.customDenyPatterns)) {
214
+ const matchedPatterns = this.customDenyPatterns.filter(pattern => matchesPattern(parsed, pattern));
215
+ if (this.debug) {
216
+ console.log(`[BashPermissions] DENIED - matches custom deny pattern: ${matchedPatterns[0]}`);
217
+ }
218
218
  const result = {
219
219
  allowed: false,
220
220
  reason: `Command matches deny pattern: ${matchedPatterns[0]}`,
@@ -227,12 +227,40 @@ export class BashPermissionChecker {
227
227
  parsedCommand: parsed.command,
228
228
  reason: 'matches_deny_pattern',
229
229
  matchedPattern: matchedPatterns[0],
230
- isComplex: false
230
+ isComplex: false,
231
+ isCustomDeny: true
231
232
  });
232
233
  return result;
233
234
  }
234
235
 
235
- // Check allow patterns
236
+ // Step 2: Custom allow overrides default deny
237
+ const matchesCustomAllow = matchesAnyPattern(parsed, this.customAllowPatterns);
238
+
239
+ // Step 3: Default deny (skipped if custom allow matches)
240
+ if (!matchesCustomAllow && matchesAnyPattern(parsed, this.defaultDenyPatterns)) {
241
+ const matchedPatterns = this.defaultDenyPatterns.filter(pattern => matchesPattern(parsed, pattern));
242
+ if (this.debug) {
243
+ console.log(`[BashPermissions] DENIED - matches default deny pattern: ${matchedPatterns[0]}`);
244
+ }
245
+ const result = {
246
+ allowed: false,
247
+ reason: `Command matches deny pattern: ${matchedPatterns[0]}`,
248
+ command: command,
249
+ parsed: parsed,
250
+ matchedPatterns: matchedPatterns
251
+ };
252
+ this.recordBashEvent('permission.denied', {
253
+ command,
254
+ parsedCommand: parsed.command,
255
+ reason: 'matches_deny_pattern',
256
+ matchedPattern: matchedPatterns[0],
257
+ isComplex: false,
258
+ isCustomDeny: false
259
+ });
260
+ return result;
261
+ }
262
+
263
+ // Step 4: Check allow patterns
236
264
  if (this.allowPatterns.length > 0) {
237
265
  if (!matchesAnyPattern(parsed, this.allowPatterns)) {
238
266
  const result = {
@@ -256,17 +284,23 @@ export class BashPermissionChecker {
256
284
  allowed: true,
257
285
  command: command,
258
286
  parsed: parsed,
259
- isComplex: false
287
+ isComplex: false,
288
+ overriddenDeny: matchesCustomAllow && matchesAnyPattern(parsed, this.defaultDenyPatterns)
260
289
  };
261
290
 
262
291
  if (this.debug) {
263
- console.log(`[BashPermissions] ALLOWED - command passed all checks`);
292
+ if (result.overriddenDeny) {
293
+ console.log(`[BashPermissions] ALLOWED - custom allow overrides default deny`);
294
+ } else {
295
+ console.log(`[BashPermissions] ALLOWED - command passed all checks`);
296
+ }
264
297
  }
265
298
 
266
299
  this.recordBashEvent('permission.allowed', {
267
300
  command,
268
301
  parsedCommand: parsed.command,
269
- isComplex: false
302
+ isComplex: false,
303
+ overriddenDeny: result.overriddenDeny || false
270
304
  });
271
305
 
272
306
  return result;
@@ -477,10 +511,25 @@ export class BashPermissionChecker {
477
511
  break;
478
512
  }
479
513
 
480
- // Check against deny patterns
481
- if (matchesAnyPattern(parsed, this.denyPatterns)) {
514
+ // Check using same priority logic as simple commands:
515
+ // 1. Custom deny always wins
516
+ if (matchesAnyPattern(parsed, this.customDenyPatterns)) {
517
+ if (this.debug) {
518
+ console.log(`[BashPermissions] Component "${component}" matches custom deny pattern`);
519
+ }
520
+ allAllowed = false;
521
+ deniedComponent = component;
522
+ deniedReason = 'Component matches deny pattern';
523
+ break;
524
+ }
525
+
526
+ // 2. Custom allow overrides default deny
527
+ const componentMatchesCustomAllow = matchesAnyPattern(parsed, this.customAllowPatterns);
528
+
529
+ // 3. Default deny (skipped if custom allow matches)
530
+ if (!componentMatchesCustomAllow && matchesAnyPattern(parsed, this.defaultDenyPatterns)) {
482
531
  if (this.debug) {
483
- console.log(`[BashPermissions] Component "${component}" matches deny pattern`);
532
+ console.log(`[BashPermissions] Component "${component}" matches default deny pattern`);
484
533
  }
485
534
  allAllowed = false;
486
535
  deniedComponent = component;
@@ -488,7 +537,7 @@ export class BashPermissionChecker {
488
537
  break;
489
538
  }
490
539
 
491
- // Check against allow patterns
540
+ // 4. Check allow patterns
492
541
  if (!matchesAnyPattern(parsed, this.allowPatterns)) {
493
542
  if (this.debug) {
494
543
  console.log(`[BashPermissions] Component "${component}" not in allow list`);
@@ -567,6 +616,10 @@ export class BashPermissionChecker {
567
616
  return {
568
617
  allowPatterns: this.allowPatterns.length,
569
618
  denyPatterns: this.denyPatterns.length,
619
+ customAllowPatterns: this.customAllowPatterns.length,
620
+ customDenyPatterns: this.customDenyPatterns.length,
621
+ defaultAllowPatterns: this.defaultAllowPatterns.length,
622
+ defaultDenyPatterns: this.defaultDenyPatterns.length,
570
623
  totalPatterns: this.allowPatterns.length + this.denyPatterns.length
571
624
  };
572
625
  }