A tool to check and enforce permissions for Claude Code.
- Bash syntax aware command analysis
- Glob based git path trust classification
- Tilde aware Read, Grep, and Glob path auto-allowing
- Implemented as a PreToolUse hook for Claude Code
- Reads JSON from stdin
- Evaluates all matching rules
- Returns the highest-priority decision: Deny > Ask > Allow
- Or, passes through to the default permission system.
Allowed and excluded paths are defined in settings.yaml using .gitignore style glob patterns.
Refer to the glob syntax guide.
Allow Read-Only Commands
Allow read-only commands:
base64basenamecatcolumncommandcutdirnameechofilefmtheadjqlesslsreadlinkrealpathrgstattailtrtreetypeuniqwcwhichxxd
Allow without in-place flags (-i, --in-place):
sort(without-o,--output)yq
Allow fd without exec flags (-x, --exec, -X, --exec-batch).
Allow read-only git subcommands:
git check-ignoregit describegit diffgit fetchgit loggit ls-treegit merge-basegit mvgit rev-parsegit rmgit showgit status
Allow bare or with read-only flags only (e.g. -a, --list, -v, --contains, --merged):
git branchgit taggit remote
Deny Unnecessary Destructive Commands
Deny all forms of rm. Suggests git rm -f or git clean -f <file> instead.
Deny find -delete and find -exec rm / find -execdir rm.
Deny fd -x rm / fd --exec rm / fd -X rm / fd --exec-batch rm.
Deny Destructive Git Operations
Deny destructive git operations:
git reset --hardgit stash popgit stash dropgit stash cleargit clean -d(any flag combo containing-d)git checkout --(discarding changes)
Prefer Modern Alternatives
Deny traditional tools and suggest modern alternatives:
find- usefdinsteadgrep- userginsteadsed- usesdinstead
Trusted Git Paths
Handle git -C <path> by combining path trust classification with subcommand analysis.
- Destructive subcommands are denied regardless of path trust.
- Safe subcommands are allowed only in trusted paths.
- Deny
cd <path>chained withgitvia any operator (&&,||,;). - Suggests using
git -C <path>instead.
Chained push
Deny git push when part of a compound command. Requires it to be run standalone.
GitHub CLI
Allow read-only gh commands:
gh run listgh run viewgh release listgh api(without data flags or write methods)
Ask for approval on write operations:
gh pr commentgh apiwith data flags (-d,-f,-F,--input)gh apiwith write methods (-X POST/PUT/PATCH/DELETE)gh api graphqlwith mutations
Python
Deny inline Python (-c or heredoc) exceeding 1000 characters or 20 lines.
Insta review
Deny cargo insta review with heredoc input to prevent faking interactive input.
brew install StudioLE/tap/hook-rscargo install --path .Enable the PreToolUse hooks in ~/.claude/settings.json
{
"hooks": {
"PreToolUse": [
{
"matcher": "Bash",
"hooks": [{ "type": "command", "command": "hook-rs bash" }]
},
{
"matcher": "Glob",
"hooks": [{ "type": "command", "command": "hook-rs glob" }]
},
{
"matcher": "Grep",
"hooks": [{ "type": "command", "command": "hook-rs grep" }]
},
{
"matcher": "Read",
"hooks": [{ "type": "command", "command": "hook-rs read" }]
}
]
}
}Refer to the glob syntax guide.
Settings are optional. If missing, defaults to empty.
Create a ~/.config/hook-rs/settings.yaml file with your settings:
git:
paths:
# Trust all repos in ~/repos
- ~/repos/**
# Exclude all repos in ~/repos/forked
#
- !~/repos/forked/**
# Trust all repos in ~/repos/forked/my-fork
- ~/repos/forked/my-fork/**
read:
paths:
# Allow reading the cargo registry
- ~/.cargo/registry/src/**
# Allow reading the rustup toolchain
- ~/.rustup/toolchains/**
# Allow reading any file in /path/to/repos
- /path/to/repos/**
# Allow reading any README.md
- README.md
# Exclude .env
- !.env
- !.env.*Note
While technically this is YAML tag syntax:
- !.envA pre-processor automatically converts it to a YAML string:
- "!.env"- Last match wins
!prefix excludes*matches zero or more characters except/?matches any single character except/**recursively matches directories{a,b}matchesaorbwhereaandbare arbitrary glob patterns (Nesting{...}is not currently allowed)[ab]matchesaorbwhereaandbare characters. Use [!ab] to match any character except for a and b.- Metacharacters such as
*and?can be escaped with character class notation. e.g.,[*]matches*.
Note
** recursively matches directories but are only legal in three situations:
- If the glob starts with
**/, then it matches all directories.
For example, **/foo matches foo and bar/foo but not foo/bar.
- If the glob ends with
/**, then it matches all sub-entries.
For example, foo/** matches foo/a and foo/a/b, but not foo.
- If the glob contains
/**/anywhere within the pattern, then it matches zero or more directories.
Using ** anywhere else is illegal
The glob ** is allowed and means "match everything".