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 the fewer-permission-prompts skill 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.
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 resultingsettings.local.jsonrule 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*.extin 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
.*, e.g.:grep -n "onclick.*Tab" somefile.html.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).*— 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)
*inside a quoted argument, and offer to store the rule with that character escaped/treated as literal.*, 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."*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.