Deny rules with root-level /** patterns silently match nothing

Resolved 💬 3 comments Opened Apr 22, 2026 by mcmanustfj Closed May 29, 2026

Bug

Permission deny rules where the relative pattern is only /** (after root resolution) silently fail to match any path. This affects patterns like:

  • Read(~/**)
  • Read(//**)
  • Edit(~/**)

These rules appear to deny all reads/edits under ~/ or /, but actually deny nothing.

Root cause

In matchingRuleForInput (src/utils/permissions/filesystem.ts:980-984), patterns ending with /** have that suffix stripped before being passed to the ignore library:

if (adjustedPattern.endsWith('/**')) {
    adjustedPattern = adjustedPattern.slice(0, -3)
}

This optimization works for patterns like foo/**foo (the ignore library treats a directory name as matching the directory and its contents). But for root-level patterns:

  • ~/**patternWithRoot produces relativePattern = "/**", root = homedir
  • /** stripped of /**"" (empty string)
  • The ignore library with an empty pattern matches nothing

Same issue for // prefix: //** → root /, relativePattern /** → stripped to "".

Impact

Users who set "deny": ["Read(~/**)"] expecting to block all reads under their home directory get no protection. The rule is silently accepted and silently ignored. This is a security footgun — users configure deny rules expecting enforcement, get none.

Reproduction

  1. Add "deny": ["Read(~/**)"] to ~/.claude/settings.json
  2. Use the Read tool to read any file under ~/
  3. Expected: hard deny
  4. Actual: read succeeds without prompting

Workaround: use Read(//home/username/**) with the full absolute path (double-slash prefix). This produces a non-empty relative pattern after stripping.

Suggested fix

Handle the degenerate case where stripping /** produces an empty string. For example, use ** as the pattern instead of "", or skip the stripping when the pattern is exactly /**.

View original on GitHub ↗

This issue has 3 comments on GitHub. Read the full discussion on GitHub ↗