Bash allowlist patterns with wildcards don't match multi-argument rm commands

Status Fixed / completed
Maintainer reply None cached
Activity 4 comments · opened May 18, 2026 · closed May 18, 2026

Summary

Allowlist patterns like Bash(rm -rf "/Users/path/*) in settings.json do not suppress permission prompts when the rm command is called with multiple path arguments.

Steps to reproduce

  1. Add "Bash(rm -rf \"/Users/morganturner/PKA/*)" to the allow list in .claude/settings.json
  2. Run a bash command with multiple arguments:

``
rm -rf "/Users/morganturner/PKA/Team Inbox/alvin/folder-a" "/Users/morganturner/PKA/Team Inbox/alvin/folder-b"
``

  1. Permission prompt still appears, even though both paths match the wildcard pattern.

Expected behavior

Any rm -rf command where all arguments are paths under /Users/morganturner/PKA/ should be auto-allowed without a prompt.

Actual behavior

Multi-argument commands trigger a manual permission prompt even when every argument matches the allowlist pattern.

Workaround

Run each rm as a separate command, or have the user execute the commands directly in the terminal.

🤖 Filed from Claude Code session

View original on GitHub ↗

4 Comments

github-actions[bot] · 4 months ago

Found 3 possible duplicate issues:

  1. https://github.com/anthropics/claude-code/issues/39834
  2. https://github.com/anthropics/claude-code/issues/56871
  3. https://github.com/anthropics/claude-code/issues/60289

This issue will be automatically closed as a duplicate in 3 days.

  • If your issue is a duplicate, please close it and 👍 the existing issue instead
  • To prevent auto-closure, add a comment or 👎 this comment

🤖 Generated with Claude Code

jshaofa-ui · 4 months ago

Root Cause Analysis & Fix Proposal

Root Cause: Allowlist Pattern Matching Only Checks First Argument

The bash allowlist pattern matching logic in claude-code's permission system only matches against the entire command string as a single unit rather than checking each argument individually against the wildcard pattern.

When the allowlist contains:

Bash(rm -rf "/Users/path/*")

The matching logic compares the full command string:

rm -rf "/Users/path/folder-a" "/Users/path/folder-b"

Against the pattern. The wildcard * in the pattern is designed to match a single path argument, but the multi-argument command has additional quoted paths that fall outside the pattern's scope.

Likely code path (src/permissions/bash-allowlist.ts or similar):

// Current (broken):
const pattern = 'rm -rf "/Users/path/*"';
const command = 'rm -rf "/Users/path/folder-a" "/Users/path/folder-b"';
return minimatch(command, pattern); // ❌ Fails - entire string doesn't match

// Should be:
// 1. Parse command into base + arguments
// 2. Check base matches pattern base
// 3. Check EACH argument matches the wildcard portion

Fix Proposal

Fix: Argument-aware allowlist matching (~80 LoC)

// src/permissions/bash-allowlist.ts

interface AllowlistPattern {
  base: string;        // e.g., "rm -rf"
  pathPattern: string; // e.g., "/Users/path/*"
}

function parseAllowlistPattern(pattern: string): AllowlistPattern | null {
  // Match: Bash(cmd args "path/*")
  const match = pattern.match(/^Bash\((.+?)\)$/);
  if (!match) return null;
  
  const full = match[1];
  // Find the last quoted path pattern
  const pathMatch = full.match(/^(.+?)\s+"(.+)"$/);
  if (!pathMatch) return null;
  
  return {
    base: pathMatch[1].trim(),
    pathPattern: pathMatch[2]
  };
}

function matchesAllowlist(command: string, pattern: AllowlistPattern): boolean {
  // Parse command into base + arguments
  const args = parseShellArgs(command);
  if (args.length < 1) return false;
  
  // Check base command matches
  const baseArgs = parseShellArgs(pattern.base);
  if (args.length <= baseArgs.length) return false;
  
  for (let i = 0; i < baseArgs.length; i++) {
    if (args[i] !== baseArgs[i]) return false;
  }
  
  // Check EACH remaining argument matches the path pattern
  for (let i = baseArgs.length; i < args.length; i++) {
    if (!minimatch(args[i], pattern.pathPattern)) {
      return false; // Any non-matching argument = deny
    }
  }
  
  return true; // All arguments match
}

function parseShellArgs(cmd: string): string[] {
  // Handle quoted arguments properly
  const args: string[] = [];
  let current = '';
  let inQuote = false;
  let quoteChar = '';
  
  for (let i = 0; i < cmd.length; i++) {
    const ch = cmd[i];
    if (inQuote) {
      if (ch === quoteChar) {
        inQuote = false;
        args.push(current);
        current = '';
      } else {
        current += ch;
      }
    } else if (ch === '"' || ch === "'") {
      inQuote = true;
      quoteChar = ch;
    } else if (ch === ' ') {
      if (current) args.push(current);
      current = '';
    } else {
      current += ch;
    }
  }
  if (current) args.push(current);
  return args;
}

Test Cases

// Test 1: Single argument (existing behavior)
expect(matchesAllowlist(
  'rm -rf "/Users/path/folder-a"',
  parseAllowlistPattern('Bash(rm -rf "/Users/path/*")')
)).toBe(true);

// Test 2: Multiple arguments (the bug)
expect(matchesAllowlist(
  'rm -rf "/Users/path/folder-a" "/Users/path/folder-b"',
  parseAllowlistPattern('Bash(rm -rf "/Users/path/*")')
)).toBe(true); // Should pass but currently fails

// Test 3: Mixed paths (should deny)
expect(matchesAllowlist(
  'rm -rf "/Users/path/folder-a" "/Users/other/folder-b"',
  parseAllowlistPattern('Bash(rm -rf "/Users/path/*")')
)).toBe(false);

Estimated Effort

  • Fix: ~2-3 hours (argument parsing + pattern matching)
  • Tests: ~1 hour
  • Total: ~3-4 hours

Pricing

| Tier | Price | Scope |
|------|-------|-------|
| Quick Fix | $500 | Root cause + fix patch |
| Standard | $1,000 | + Test cases + PR |
| Premium | $1,500 | + Edge case handling + docs |

Cross-sell

Bundle with other claude-code permission issues (#60311, #60310) for $3,000+

morganb-rav · 4 months ago

Closing — tracking this internally instead.

github-actions[bot] · 3 months ago

This issue has been automatically locked since it was closed and has not had any activity for 7 days. If you're experiencing a similar issue, please file a new issue and reference this one if it's relevant.