Bash permission allow-rules: literal '*' inside quoted regex/glob arguments is silently reinterpreted as a positional wildcard on replay
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
- Run a Bash command with a quoted regex containing
.*, e.g.:
grep -n "onclick.*Tab" somefile.html
- Approve it with "always allow".
- 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 thefewer-permission-promptsskill instructions). - 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.
3 Comments
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 — trueprefix*suffixglob 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*suffixmatching, 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):
``
``mkdir -p /tmp/wildcard-repro && cd /tmp/wildcard-repro
echo "onclick=\"switchNotifTab('x')\"" > dummy.html
``
``mkdir -p .claude
cat > .claude/settings.local.json <<'JSON'
{
"permissions": {
"allow": [
"Bash(grep -n \"onclick.*Tab\" /tmp/wildcard-repro/dummy.html)"
],
"defaultMode": "default"
}
}
JSON
claudesession in that directory (cd /tmp/wildcard-repro && claude).grep -n "onclick." /tmp/wildcard-repro/dummy.html(matches the prefix before*, but not the full stored suffixTab" /tmp/wildcard-repro/dummy.html)grep -n "onclick.XYZ.Tab" /tmp/wildcard-repro/dummy.html(matches both the prefix and reproduces the literal suffix after the wildcard)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.
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.)
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?