Bash permission allow-rules: literal '*' inside quoted regex/glob arguments is silently reinterpreted as a positional wildcard on replay

Status Open
Maintainer reply None cached
Activity 2 comments · opened Sep 20, 2026

Summary
When a Bash command containing a literal * inside a quoted argument (e.g. a grep regex like "onclick.*Tab", or a glob like "*.py") is approved via "always allow", the resulting settings.local.json rule stores the * as-is. On replay, Claude Code's permission matcher treats every * in the stored string as its own positional wildcard — regardless of whether it originated inside quotes as a literal/regex character — allowing arbitrary content to be inserted at that position on future matches, as long as the literal suffix after it still occurs later in the command.

Why this matters
* is the universal wildcard/glob character across shells, OSes, and regex engines. Any user approving a one-off command that happens to contain a regex .* or a glob *.ext in a quoted argument unknowingly creates an allow-rule with an unintended insertion point — with no warning, no escaping mechanism, and no distinction between "wildcard meant as permission scope" vs. "literal character that happened to appear in quoted content."

This is a real-world instance of the same class of problem as #51057 (skill-level command normalization not matching the actual matcher semantics) — here at the core permission-matching layer itself.

Repro

  1. Run a Bash command with a quoted regex containing .*, e.g.:

grep -n "onclick.*Tab" somefile.html

  1. Approve it with "always allow".
  2. Inspect the resulting rule in .claude/settings.local.json — the * from the regex is stored raw, at a position that is not at the end of the pattern and not preceded by a space (contradicting the project's own documented safe-wildcard guidance: wildcards should only appear at the end, preceded by a space, per the fewer-permission-prompts skill instructions).
  3. Any future command that reproduces the literal prefix/suffix around that * — with arbitrary content (including shell metacharacters ;, &&, $(), backticks) spliced into the wildcard's position — matches this rule and is auto-approved without a permission prompt.

Suggested fixes (any of these would resolve it)

  • Warn the user at approval time if the literal command contains a * inside a quoted argument, and offer to store the rule with that character escaped/treated as literal.
  • Use a non-colliding placeholder syntax for the permission engine's own wildcard (e.g. a dedicated token) instead of overloading *, which already carries context-dependent meaning in shells and regexes, and provides no logic to distinguish "wildcard meant as a stand-in for a specific word/pattern" from "the engine's own approval-scope wildcard."
  • At minimum, restrict stored allow-rules to only support wildcards in the documented safe position (end of pattern, preceded by a space) and reject/flag any other * occurrence as literal by default.

Environment: Claude Code CLI, discovered on Linux (WSL2), reproduced via the standard "always allow" approval flow — not linked to any specific plugin.

View original on GitHub ↗

3 Comments

nicandr0 · 26 days ago

Follow-up: matching semantics matter for severity, plus a ready-to-run repro

One open question that affects how serious this is: when the matcher encounters the stored *, does it require

(a) the literal suffix after the wildcard (everything following * in the stored pattern) to still occur later in the candidate command — true prefix*suffix glob semantics, or

(b) only the literal prefix before the first * to match, treating everything after as unconstrained?

Under (b), a rule like Bash(grep -n "onclick. would silently approve any command sharing that prefix, regardless of what follows — a much larger blast radius than under (a), where the required suffix (including hardcoded file paths, in the original case) still has to appear verbatim.

Suggested addition to the fix list: regardless of which behavior is current, the matcher should guarantee (a) — full prefix*suffix matching, never prefix-only — for any wildcard that isn't in the documented safe end-of-pattern position. This would at least bound the damage of an accidentally-stored mid-pattern wildcard to commands that still reproduce the exact literal suffix, rather than allowing free-form continuation.

---

How to test this yourself (isolated, takes ~2 minutes):

  1. Create a throwaway directory and a harmless target file:

``
mkdir -p /tmp/wildcard-repro && cd /tmp/wildcard-repro
echo "onclick=\"switchNotifTab('x')\"" > dummy.html
``

  1. Give it its own project-local permission config, deliberately not using auto/bypass mode, so a real permission prompt is expected for anything not covered by the allow-rule below:

``
mkdir -p .claude
cat > .claude/settings.local.json <<'JSON'
{
"permissions": {
"allow": [
"Bash(grep -n \"onclick.*Tab\" /tmp/wildcard-repro/dummy.html)"
],
"defaultMode": "default"
}
}
JSON
``

  1. Start a fresh claude session in that directory (cd /tmp/wildcard-repro && claude).
  2. Try two candidate commands and note whether each triggers a permission prompt or runs silently:
  • Prefix-only test: grep -n "onclick." /tmp/wildcard-repro/dummy.html (matches the prefix before *, but not the full stored suffix Tab" /tmp/wildcard-repro/dummy.html)
  • Prefix+suffix test: grep -n "onclick.XYZ.Tab" /tmp/wildcard-repro/dummy.html (matches both the prefix and reproduces the literal suffix after the wildcard)
  1. If step 4's first command runs without a prompt → behavior is (b) (prefix-only, larger blast radius). If only the second command runs without a prompt → behavior is (a) (full glob semantics).

Happy to update this comment with whichever result gets confirmed, or if someone from the team can just point to the relevant matcher code/behavior directly.

nicandr0 · 26 days ago

Follow-up: a hypothesis on the likely internal mechanism

Reasoning through how a permission-rule system like this would typically be implemented, here's a plausible explanation for the exact symptom in this issue:

Rules are probably classified into distinct types (something like exact-match, prefix-match, and wildcard-match) based on their stored text — likely by scanning for an unescaped * character in the string. If that's the case, and if there's already a documented escape convention for a literal * in this rule format (e.g. \* for "this asterisk is not a wildcard"), then the underlying bug isn't really "wildcards are unsafe" — it's narrower: the code path that turns a user-approved literal command into a stored "exact match" allow-rule probably writes the raw command text straight through, without escaping any * it happens to contain. On the next permission check, the classification step would then see an unescaped * in what was meant to be an exact-match rule, and silently reclassify it as a wildcard rule instead — a mismatch between generation-time intent and parse-time interpretation, not a flaw in the wildcard matcher itself.

If that's accurate, the fix would be narrow and low-risk: apply the same escaping already used for literal wildcards to the raw command text at the point an "exact match" rule gets generated and persisted, rather than touching the matching/classification logic at all.

(Also, updating my earlier (a)/(b) question above: based on how this kind of behavior tends to be implemented end-to-end — full-string matching rather than prefix-only — this would already bound the blast radius more tightly than I'd worried about, which is reassuring.)

samvallad33 · 23 days ago

Bash permission allow-rules where a literal star inside quoted regex or glob arguments is reinterpreted as a positive matcher open an authority bypass. Quoted literals should stay literal or the audit trail lies about what was allowed. Are you treating quoted stars as literals in allow-rule evaluation?