malob/audit-permissions
>- This skill should be used when the user asks to "audit claude permissions", "audit permissions", "review local claude settings", "promote permissions to global", "clean up claude settings", "find permission patterns", or wants to identify project-local Claude Code permissions that should be added to global configuration.
npx skills add https://github.com/malob/nix-config --skill audit-permissions
Scan project-local Claude Code settings files, aggregate permission patterns, and recommend promotions to global configuration.
This audit runs in three phases, each as a separate task. Use TaskCreate at the start to create all three tasks, then work through them sequentially with user input via AskUserQuestion.
readlink -f ~/.claude/settings.json
If the global settings file is a symlink, note the real path. All writes to ~/.claude/settings.json must edit the symlink target, not create a new file that replaces the symlink.
~/.claude/cc-maintenance.local.md (if it exists). See User Preferences section below. Apply any configured defaults (risk tolerance, auto-cleanup preference).~/.claude/CLAUDE.md to identify tool preference policies for Phase 3 policy conflict detection.scripts/discover-settings.sh | xargs scripts/extract-permissions.py
~/.claude/settings.json — compare against the real allow list, not just the static examples in this skill.TaskCreate: "Review and promote permissions to global config"
TaskCreate: "Automated redundancy cleanup"
TaskCreate: "Judgment calls: security, policy, and cruft"
Goal: Identify permissions worth adding to global config and get user approval.
Show a summary table of promotion candidates:
## Promotion Candidates
### Strong Recommendations (safe patterns, multiple projects)
| Permission | Projects | Suggested Global Pattern |
| ---------- | -------- | ------------------------ |
| ... | ... | ... |
### Moderate Recommendations (review carefully)
| Permission | Projects | Notes |
| ---------- | -------- | ----- |
| ... | ... | ... |
### Cross-Project File Patterns
[If any Read/Write/Edit permissions reference paths outside their project directory
and appear in multiple projects, flag them here. Example: multiple projects have
`Write(~/.config/some-tool/config.json)` - might indicate a shared config worth
adding globally.]
Use AskUserQuestion to let the user decide:
Question: "Which permissions should I add to global settings?"
Options:
- "Add all strong recommendations"
- "Add strong + moderate recommendations"
- "Let me pick specific ones" (then list individually)
- "Skip - don't add any"
If user approves additions:
~/.claude/settings.json (or symlink target)Bash(cmd *) not Bash(cmd:*)Goal: Remove local permissions now covered by global config. This is mechanical — the script identifies exact matches; user just sanity-checks the list.
Run the cleanup script in dry-run mode:
scripts/discover-settings.sh | scripts/cleanup-redundant.py
Show what would be removed:
## Redundant Permissions
| File | Permissions to Remove | Remaining |
| --------- | --------------------- | --------- |
| project-a | 5 (ls _, grep _, ...) | 12 |
| project-b | 3 (gh api \*, ...) | 8 |
| ... | ... | ... |
**Total:** X permissions across Y files
The script also normalizes any remaining colon-syntax (Bash(cmd:*)) to space-syntax (Bash(cmd *)) when applying.
If auto_cleanup_redundant: true in user preferences, skip the question and apply directly (still show the summary). Otherwise:
Question: "Should I remove these redundant permissions from local files?"
Options:
- "Yes, clean them up"
- "Show me the full list first"
- "Skip cleanup"
If user approves:
scripts/discover-settings.sh | scripts/cleanup-redundant.py --apply
Mark Phase 2 task as completed.
Goal: Everything that requires real user judgment — security risks, policy conflicts, stale cruft, and cleanup opportunities. Present all categories together.
Flag permissions that match these patterns:
High Risk (recommend removal):
Bash(curl *), Bash(wget *) — network exfiltration riskBash(rm *) — can delete any fileBash(source *) — executes arbitrary scriptsBash(eval *) — arbitrary code executionModerate Risk (review):
Bash(git reset *), Bash(git checkout *) — can discard workBash(pkill *), Bash(kill *) — process terminationBash(python *), Bash(python3 *), Bash(node *) — arbitrary code (flag if user hasn't consciously chosen this)Adjust what counts as "moderate" vs "high" based on the user's risk_tolerance preference if set.
Read the user's global ~/.claude/CLAUDE.md for stated tool preferences. Flag local permissions that conflict with those policies.
How to detect: Look for patterns like "prefer X over Y", "use X instead of Y", "avoid Y". Then scan all local permissions for uses of the deprecated tool. For example:
> If CLAUDE.md says "prefer tool X over built-in Y", flag all Y permissions across local settings as policy conflicts.
Present these as informational — the user may have valid reasons for specific overrides.
Bash(prettier --write /full/path/to/file.md))Bash(done), Bash(for file in *.rs))Bash(cmd:*)) that weren't caught by the cleanup script (e.g., ones without a global equivalent)Read, Write, or Edit permissions for paths outside the projectWrite(~/.config/*) as security concernsmcp__server__*) — convenient but auto-permits any future tools added to that server without reviewAfter cleanup, identify settings files where:
Offer to delete these empty files entirely — they serve no purpose.
## Judgment Calls
### Security Hygiene
#### High Risk — Recommend Removal
| Permission | Project | Risk |
| -------------- | --------- | ----------------- |
| `Bash(curl *)` | project-x | Data exfiltration |
#### Moderate Risk — Review
| Permission | Project | Risk |
| ---------- | ------- | ---- |
| ... | ... | ... |
### Policy Conflicts
| Permission | Projects | Policy |
| --------------- | -------- | --------------------------- |
| `WebFetch(url)` | 3 | CLAUDE.md: prefer Firecrawl |
### One-Off Cruft
| Permission | Project |
| -------------------------------------------------- | --------- |
| `Bash(prettier --write /path/to/specific/file.md)` | project-y |
### External File Access
| Permission | Projects | Notes |
| ------------------------------ | ---------- | ------------------------------- |
| `Write(~/.config/tool/config)` | 3 projects | Shared config — consider global |
| `Edit(/etc/hosts)` | 1 project | System file — review necessity |
### MCP Tool Permissions
| Permission | Projects | Notes |
| -------------------------- | -------- | ------------------------------------------- |
| `mcp__slack__post_message` | 2 | Side effect: sends messages externally |
| `mcp__puppeteer__*` | 1 | Server wildcard — auto-permits future tools |
### Empty Settings Files
| File | Reason |
| ----------------------------------------- | ------------------------------------------ |
| `~/project-z/.claude/settings.local.json` | All permissions removed, no other settings |
Use AskUserQuestion:
Question: "How should I handle the judgment call items?"
Options:
- "Remove all flagged items"
- "Remove high risk + cruft only"
- "Let me review each category"
- "Skip — keep everything"
If user wants to review categories, ask about each separately.
Edit each affected settings.local.json to remove approved items. Delete empty settings files if approved.
Mark Phase 3 task as completed.
After all phases complete, offer to create or update ~/.claude/cc-maintenance.local.md with session learnings. Include:
Location: ~/.claude/cc-maintenance.local.md
Read at audit start. If missing, proceed with defaults. At session end, offer to create/update.
Format: YAML frontmatter for structured preferences + markdown body for freeform notes.
---
# All preferences are optional
risk_tolerance: moderate # conservative | moderate | aggressive
auto_cleanup_redundant: true
---
## Tool Preferences
- Prefer Exa/Firecrawl over built-in WebSearch/WebFetch
## Risk Notes
- sqlite3 and op item get considered moderate risk
- uv run python treated as arbitrary code execution
## Notes
- Always keep python3 permissions in the data-science project
- Don't consolidate Firecrawl tools into a server wildcard
## Session History
### 2026-02-09
- Promoted 13 permissions to global
- Removed ~220 cruft permissions from Assistant project
Behavior:
risk_tolerance adjusts what gets flagged as moderate vs high in Phase 3auto_cleanup_redundant skips the confirmation prompt in Phase 2 (still shows summary)All scripts are in scripts/ relative to this skill directory.
scripts/discover-settings.sh — Finds all .claude/settings.local.json files across ~ using fd with sensible exclusions (Library, node_modules, .git, etc.). Max depth of 5 for performance.scripts/extract-permissions.py — Aggregates permissions from multiple settings files. Outputs JSON with each permission, occurrence count, and list of projects using it. Sorted by count descending.scripts/cleanup-redundant.py — Removes permissions from local files that are covered by global config. Handles cross-syntax matching (colon-syntax vs space-syntax). Normalizes remaining permissions to space-syntax on write. Dry-run by default; use --apply to modify files.Usage examples:
# Extract and aggregate all permissions
scripts/discover-settings.sh | xargs scripts/extract-permissions.py
# Preview redundant permission cleanup (dry-run, default)
scripts/discover-settings.sh | scripts/cleanup-redundant.py
# Actually remove redundant permissions
scripts/discover-settings.sh | scripts/cleanup-redundant.py --apply
Use space-syntax for all permissions: Bash(cmd *) not Bash(cmd:*).
Patterns worth promoting. These operate on local project code or perform read-only operations. Compare against the actual global config, not just this static list — these are examples of the kinds of patterns to look for.
Git Commands (read-only):
git branch *, git diff *, git log *, git show *, git status *
File Inspection (read-only):
cat *, head *, tail *, ls *, find *, grep *, du *
Build and Check Commands:
cargo build *, cargo test *, cargo check *, go build *, go test *,
npm run build *, npm run test *, deno check *, deno lint *, xcodebuild *
System Utilities:
open *, pbcopy, pbpaste, lsof *, ps *
Nix Commands:
nix build *, nix-build *, nix develop *, nix eval *, nix flake *,
nix path-info *, nix-prefetch-url *, nh darwin build *
Homebrew (read-only):
brew info *, brew search *
GitHub CLI:
gh api *, gh issue list *, gh issue view *, gh pr list *, gh pr view *,
gh pr diff *, gh pr checks *, gh search *, gh run list *, gh run view *
Wildcards:
* --help *, * --version
When promoting, generalize cautiously — only for safe patterns:
| Local Pattern | Global Pattern | Notes |
| ----------------------------- | ----------------------- | ---------------------------- |
| Bash(npm run build) | Bash(npm run build *) | Safe — runs project scripts |
| Bash(cargo test --release) | Bash(cargo test *) | Safe — tests local code |
| Bash(nix build .#package) | Bash(nix build *) | Safe — sandboxed builds |
| Bash(python3 script.py) | Keep specific or skip | Risky — arbitrary code |
| WebFetch(domain:github.com) | Keep as-is | Domain patterns don't change |
When adding to ~/.claude/settings.json:
Bash(cmd *) not Bash(cmd:*)Take malob/audit-permissions from the repository into ~/.claude/skills for personal
use, or into .claude/skills inside a project.
The agent identifies a skill by the name field in its header. Two skills with the
same name cannot sit side by side — one of them will be ignored.