Audit tools for Claude Code permission rules.
Claude Code's * wildcard in Bash(...) permission patterns does not match shell operators (&&, ||, |, ;). This means a rule like Bash(git *) does not cover git status && git diff — Claude Code splits compound commands at operators and matches each segment independently.
This toolset helps you:
- See which commands from your session history are not covered by your current allow rules
- Understand how each command was approved — automatically by a rule, manually by you, or denied
- Get pattern suggestions ranked by how many commands they would unblock
- Find allow rules that are redundant (fully covered by another rule)
pip install git+https://github.com/sg4tech/ai-agents-permission-audit.gitOr clone and install in editable mode:
git clone https://github.com/sg4tech/ai-agents-permission-audit.git
cd ai-agents-permission-audit
pip install -e .Run all commands from within your project directory (or any subdirectory). Results are written to audit-output/ at the repo root.
claude-audit-extractScans ~/.claude/projects/<slug>/ for JSONL session files, parses every Bash tool invocation, and writes audit-output/claude_bash_commands.tsv.
Each command is tagged with an approval status inferred from the time delta between the tool request and its result:
| Status | Meaning |
|---|---|
auto |
Delta < 2 s — Claude Code approved instantly via a matching allow rule |
user |
Delta ≥ 2 s — you approved via dialog, or a slow auto-approved command (see note) |
denied |
Result contained the Claude Code permission denial message |
Commands you approved manually (user) are the highest-priority candidates for new allow rules.
Use --threshold N to adjust the auto/user cutoff (default: 2.0 s):
claude-audit-extract --threshold 3.0Note: slow commands auto-approved by a rule (e.g.
npm install) will be classified asuserbecause their execution time pushes the delta above the threshold. This is a known limitation.
Console output includes a status summary:
Extracted 424 unique commands (468 total) -> audit-output/claude_bash_commands.tsv
auto: 293 user: 155 denied: 20
claude-audit-checkReads the TSV and checks each command against allow/deny rules from .claude/settings.local.json and ~/.claude/settings.json.
Output files written to audit-output/:
| File | Contents |
|---|---|
commands_not_allowed.tsv |
Commands not matching any allow rule |
commands_denied.tsv |
Commands matching a deny rule |
commands_compound_not_allowed.tsv |
Compound commands not fully covered |
Example console output:
Commands checked: 312 unique
Not allowed: 47 unique (183 invocations)
simple: 31
compound: 16 (94 invocations)
Denied: 2 unique
claude-audit-suggestAnalyzes commands_not_allowed.tsv and proposes Bash(...) allow rules ranked by coverage:
Suggested allow rules (8 total):
Bash(cat * # 6 cmd(s), 11 invocation(s)
Bash(head * # 7 cmd(s), 9 invocation(s)
Bash(echo * # 5 cmd(s), 9 invocation(s)
Bash(ls * # 5 cmd(s), 7 invocation(s)
...
With these rules: 26/31 uncovered commands would be resolved.
Add --apply to patch .claude/settings.local.json directly:
claude-audit-suggest --applyUse --min-count N to only suggest patterns that cover at least N commands:
claude-audit-suggest --min-count 2claude-audit-redundantReports rules where every command matched by rule A is also matched by rule B — safe to remove.
Example output:
Found 1 potentially redundant rules:
REDUNDANT: Bash(git log --oneline)
COVERED BY: Bash(git log*)
(matched 4 real commands)
* in a Claude Code permission pattern matches any sequence of characters except unquoted shell operators. To allow a compound command, you need either:
- A pattern that explicitly contains the operator:
Bash(cd * && git *) - Individual patterns covering each segment:
Bash(cd *)+Bash(git *)
Operators inside quotes ("a|b") or after backslash (\|) are treated as literals and pass through fine. File-descriptor redirects (2>&1, 2>/dev/null) are not operators.
pytestUnit tests covering the glob matcher, permission checker, approval-status extraction, and behavioral specification.
Apache 2.0