Repository navigation
feat(permission): parse shell commands for permission analysis - #250
Merged
Merged
Conversation
Codecov Report❌ Patch coverage is
📢 Thoughts on this report? Let us know! |
hakula139
force-pushed
the
feat/shell-parsing
branch
from
October 16, 2026 09:25
14cf1bd to
5fb4976
Compare
Bash rules matched the raw command string, so an allow rule refused any chained or redirected command and a deny rule saw only text split on separators. Parse the command with tree-sitter-bash into simple commands, with leading assignments and transparent wrappers stripped, output redirections as edit targets, and an opaque flag for anything the parse cannot see into. Allow now needs every simple command to match an allow rule or a shipped known-safe read-only command, and every write to be in the workspace or edit-allowed. Deny matches any simple command, including those inside a literal `sh -c` script or `eval`. Opaque commands can be denied but never allowed. Closes #175
`exec`, `command`, and `builtin` ran the rest of their words unseen, so `exec rm -rf /` escaped deny rules and `exec sh -c` escaped the opaque check. They are now transparent wrappers. `env -C` counts as a directory change, and a cd now also guards relative known-safe reads. A leading assignment drops out of the rule text only for variables that change output alone. Others, like PATH, LD_PRELOAD, or GIT_PAGER, stay in the text and rule out the known-safe set. Assigning a variable the shell reads, or exporting one outside that list, makes the command opaque. sed and grep guards accept GNU long-option abbreviations and BSD `sed -I`, and sed reads count only the files past the script.
hakula139
force-pushed
the
feat/shell-parsing
branch
from
October 16, 2026 09:58
5fb4976 to
3bfe581
Compare
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.
Summary
The
bashgate used to match rules against the raw command string. An allow rule refused any command containing;,|,>, or$(, and a deny rule saw only text split on separators, so"rm" -rf xor/bin/rm -rf xslipped pastbash(rm -rf:*). The gate now parses each command with tree-sitter-bash and judges the simple commands it runs and the files it writes.permission::shellmodule builds aCommandTargetfrom the command. It splits it into simple commands across;,&&,||,&, pipes, line breaks, subshells, and compound statements. The wrappersbuiltin,command,env,exec,nice,nohup,time, andtimeoutare stripped with their options, words are unquoted, and a leading~expands. Output redirections (>,>>,>|,>&,&>,&>>) become edit-class path targets resolved like a path tool's, except/dev/nulland the standard streams.VAR=valuedrops out of the text a rule matches only for a short list of variables that change output alone, such asRUST_LOG,LANG, andNO_COLOR. Others, such asPATH,LD_PRELOAD, orGIT_PAGER, stay in the text and rule out the known-safe set, soGIT_PAGER=sh git logdoesn't matchbash(git log:*). Deny rules still see the program that runs.[[and((tests, a non-literal command name, a shell (sh -c,| sh) or a code-running builtin (eval,source,.,trap,let), an unknown wrapper option, and a non-literal or glob redirect target. The list also covers assigning a variable the shell reads (PATH,HOME,IFS, ...) or exporting one outside the short list, andcd,pushd,popd, orenv -Ccombined with a relative write or known-safe read. Deny rules still apply to an opaque command, including the simple commands inside a literalsh -cscript oreval, but no rule allows it.editallow rule. The known-safe set isecho,false,pwd, andtrue, pluscat,head,ls,tail,wc,findwithout-exec,-delete, or a file-writing action,grepwithout recursion or-d, andsedwithout-i,-I, or-fand with a script that only prints or rewrites text. Long options match by any prefix GNU tools accept, sosed --incounts as--in-place. These need literal, glob-free arguments, and an argument areaddeny rule matches makes the command ask.editdeny rules (so.git/**stays denied) and asks when its target is protected.Rule::for_callnames it without its wrappers, redirections, and output-only assignments, soRUST_LOG=debug cargo test 2>&1offersbash(cargo test).Segmentexposes the text a rule must name and the unquotedwords()of the program that runs, so Configure permissions with per-class deny, ask, and allow tables, plus manual and auto modes #174 can matchexecword patterns against segments without changing the parser.words()drops every assignment, so an allow pattern there still needs the text's kept assignments to count.Ask rules don't exist yet, so the criterion that ask rules match when any segment matches stays open for #174 to settle.
Closes #175
Test plan
cargo fmt --all --checkandcargo clippy --all-targets -- -D warningspassed on macOS.cargo test: 2668 + 4 tests passed on macOS.permission::shelltests cover each separator, assignment stripping and unquoting, tilde expansion, every wrapper with its options (includingexec,command, andbuiltin), unknown wrapper options, assignments kept in the rule text, shell-read and exported variables, each opaque form with non-opaque counterexamples, nestedsh -c/evalscripts, redirect writes and skipped and unplaceable redirects, and thefind,grep, andsedguards.Policy::decidetests cover segment-wise allow, opaque commands that a deny rule still denies, redirect targets zoned as edits, a known-safe read of.envthat asks, and the single-command session grant.cargo deny check licenses bans sourcespassed with the new tree-sitter dependencies.pnpm format,pnpm lint, andpnpm spellcheckpassed.