fix(parse-sdk-options): prevent shell-quote from collapsing unquoted Bash(X:*) rules to bare Bash#1350
Open
alexglynn wants to merge 2 commits into
Open
Conversation
…Bash(X:*) rules to bare Bash shell-quote's parse() tokenizes unquoted `(`, `)` as control operators and barewords containing `*` as glob ops, all returned as non-string objects. parseClaudeArgsToExtraArgs filtered those out, so an unquoted `--allowedTools View,Bash(gh:*),Bash(cat:*)` collapsed to bare `Bash` — silently widening scoped permission rules to unrestricted Bash(*). Escape shell control metachars to Unicode private-use placeholders before parse() and restore after; extract .pattern from glob ops. Preserves existing quote/whitespace handling. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Bug
parseClaudeArgsToExtraArgsusesshell-quote'sparse()to tokenize theclaude_argsinput, then filters to string entries only. Butshell-quoteis a full shell-command parser: unquoted(/)are returned as{op: '('}/{op: ')'}control-operator objects, and any bareword containing*is returned as{op: 'glob', pattern: '…'}. All of these were dropped by the string-only filter.Result: an unquoted
--allowedTools Bash(gh:*),Bash(cat:*)collapses to bare"Bash"— which Claude Code interprets asBash(*), i.e. unrestricted shell.Repro
After the existing
.filter(typeof arg === "string")→["--allowedTools", "View,Bash", ",Bash"]→ split/dedup →["View", "Bash"].Before / after (
parseSdkOptions)Input (exact shape the action builds in tag mode — its own quoted
--allowedTools+ user's unquoted YAMLclaude_args):sdkOptions.allowedTools[…, "Bash(git add:*)", "Bash(git commit:*)", "View", "GlobTool", "GrepTool", "Bash"][…, "Bash(git add:*)", "Bash(git commit:*)", "View", "Bash(gh:*)", "Bash(printf:*)", "Bash(cat:*)"]Security impact
This is silent permission widening. Any workflow that passes scoped
Bash(X:*)/Read(X)/WebFetch(X)rules inclaude_argswithout wrapping the value in quotes gets the tool-level wildcard instead of the scoped rule. BecauseBash(*)subsumes everyBash(X:*), the session behaves as intended — the user never observes a denial that would reveal the scoping was lost.Found via Claude Code session telemetry. The same collapse happens on
--disallowedTools(scoped deny → blanket deny), which is a usability bug rather than a widening.Fix
claude_argsis a CLI argument string, not a shell command — we want shell-quote's quote/whitespace handling, not its operator/glob detection. shell-quote has no option to disable operator parsing, and it does not preserve adjacency (so post-hoc reconstruction of{op}runs is lossy). Instead:parse(), replace the seven shell control metachars( ) | & ; < >with Unicode private-use codepoints (U+E000–U+E006). shell-quote then treats them as ordinary word characters and never splits on them.parse(), map glob ops back to their.pattern(the verbatim token text) and restore the placeholders.Quoted values, escaped values, whitespace tokenization, comment stripping, and
--mcp-configJSON handling are all unchanged.Tests
Six regression tests added to
base-action/test/parse-sdk-options.test.tscovering:Bash(X:*)rulesBash(X:*)rulesTool(content)with no glob charsBash(X:*)(no-regression)--disallowedToolspathAll 5 applicable tests fail on
main, pass with this change. Full suite:base-action133 pass / 0 fail, root 706 pass / 0 fail, typecheck clean, prettier clean.Note
bun installonmaincurrently fails because@anthropic-ai/claude-agent-sdk@0.3.150(pinned in 787c5a0) is not yet published to npm; I ran the suite locally against0.3.149. Unrelated to this change.🤖 Generated with Claude Code