Skip to content

Latest commit

 

History

History
318 lines (218 loc) · 29.5 KB

File metadata and controls

318 lines (218 loc) · 29.5 KB

optimus:permissions

Claude Code's built-in sandboxing provides OS-level isolation on macOS (Seatbelt) and Linux/WSL2 (bubblewrap) — but on native Windows, sandboxing is not yet available. For reducing prompts without isolation, the options are constant permission prompts (safe but slow), --dangerously-skip-permissions (fast but unsafe), or auto mode — a server-side classifier that reviews each tool call (safer, but a research preview gated by model, provider, and admin settings).

/optimus:permissions provides a deterministic complement: allow/deny rules that eliminate routine prompts, plus a PreToolUse hook that enforces a tiered security model — writes outside the project require approval, deletes outside the project are blocked. Not OS-level isolation, but significantly safer than no guardrails at all — and because its deny rules are evaluated before auto mode's classifier and its hook runs in every permission mode, it strengthens auto mode rather than competing with it (see Relationship with auto mode).

Quick Start

This skill is part of the optimus plugin. See the main README for installation instructions.

Run: Type /optimus:permissions in any project directory.

Where This Fits

Claude Code has multiple layers for managing agent autonomy. The right choice depends on your platform and risk tolerance:

Approach Prompts Safety macOS Linux / WSL2 Native Windows Setup Autonomous Loops
Default mode Every tool call Safe Yes Yes Yes None No (blocks)
--dangerously-skip-permissions None Unsafe — no guardrails Yes Yes Yes None Yes (no guardrails)
Auto mode Minimal (classifier-gated) Classifier-based — probabilistic, research preview Yes Yes Yes Minimal (recent model; admin/provider gating) Partial (classifier can still block)
Built-in sandboxing None (sandboxed) OS-level isolation Yes Yes Planned Minimal Yes
Devcontainers None (sandboxed) Container-level isolation Yes Yes Yes Moderate Yes
This skill Minimal Defense-in-depth — tiered rules + hook Yes Yes Yes Minimal No (prompts block)

Gold standard: Built-in sandboxing or devcontainers provide true isolation. Use them when you can.

Auto mode reduces prompts with a server-side classifier, but is gated by Claude Code version (v2.1.83+), model (Anthropic API: Opus 4.6+ or Sonnet 4.6; Bedrock/Vertex/Foundry: Opus 4.7/4.8 with CLAUDE_CODE_ENABLE_AUTO_MODE), and admin enablement on Team/Enterprise — though it is available on all plans. This skill works everywhere and layers underneath it — see Relationship with auto mode.

Where this skill shines:

  • Native Windows — where sandboxing is not yet available and devcontainers add complexity
  • Lightweight setups — when you want fewer prompts without the overhead of containers
  • Complementary layer — even with sandboxing or devcontainers, permission rules reduce noise from non-destructive operations

Recommended for Windows users: For full isolation, run Claude Code inside WSL2 (which supports bubblewrap sandboxing) or a devcontainer. This skill is for when those options are not practical.

Not for unattended autonomous loops: This skill is not suitable for ralph-wiggum-style autonomous loops (where Claude runs unattended in a while true loop). Permission prompts ("ask user") would block the loop, and without OS-level isolation an unattended agent could cause damage. For autonomous loops, use devcontainers or built-in sandboxing.

Relationship with auto mode

Auto mode is a permission mode where a server-side classifier reviews each tool call and blocks irreversible, destructive, or out-of-environment actions — reducing prompts without per-call approval. It is configured through an autoMode block (environment / allow / soft_deny / hard_deny) and is currently a research preview.

This skill and auto mode are complementary layers, not alternatives:

  • Deny rules run first and can't be overridden. Claude Code evaluates permissions.deny (and explicit ask) before the classifier. The skill's deny list is the hardest boundary — it blocks rm -rf /, sudo, force-push, publish commands, etc. even in auto mode, and it works on every model and provider, including where auto mode is unavailable.
  • Hooks run in every permission mode. The restrict-paths.sh PreToolUse hook fires under auto mode too, so branch protection and precious-file protection keep working. The hook is deterministic — it knows exactly which branches you protect and which files are precious — while the classifier is probabilistic (it "does not guarantee absolute safety"). Deterministic enforcement backing a probabilistic gate is stronger than either alone.
  • The allow list mainly helps when auto mode is off or unavailable, pre-approving routine tools so you aren't prompted. Note the nuance: on entering auto mode, Claude Code drops broad allow rules that grant arbitrary code execution — including the template's blanket Bash (and Task) entries — and restores them when you leave, letting the classifier govern those calls instead. Reads and in-working-directory edits are auto-approved regardless, so the allow list's pre-approval matters most outside auto mode.

This skill does not enable or configure auto mode. By design, an autoMode block is ignored in the shared, checked-in .claude/settings.json this skill manages, and defaultMode: "auto" only takes effect in user settings (~/.claude/settings.json) — both are safeguards against a repository turning auto mode on for itself. To enable auto mode, cycle to it with Shift+Tab, launch with claude --permission-mode auto, or set it in your user settings; validate any custom rules with claude auto-mode config and claude auto-mode critique.

Interaction with Claude Code's native protected paths

Independently of this skill, Claude Code has a built-in protected paths guard: writes to a small set of paths are never auto-approved in any mode except bypassPermissions — you are prompted before editing them even when everything else in the project is silent. This is why you still see a prompt for .claude/CLAUDE.md, .claude/settings.json, a file under .claude/hooks/, or .mcp.json after this skill has quieted ordinary edits. It is not a gap in this skill — the prompt comes from Claude Code itself, not from restrict-paths.sh.

What's protected: the entire in-project .claude/ directory (except .claude/worktrees/), .git/, and named config files anywhere in the tree — .mcp.json, .claude.json, .gitconfig/.gitmodules, shell rc files, and package-manager/tool configs. It is a fixed, native list; this skill neither defines nor can extend it.

Why it exists — and why it complements this skill. It is a privilege-escalation safeguard: an "allow all edits" rule (or acceptEdits) must not let the agent silently rewrite its own permissions, hooks, or steering instructions. That dovetails with a deliberate blind spot in this skill's hook — the hook treats in-project writes as trusted, so it does not gate the very <project>/.claude/settings.json that registers it (only the out-of-project global ~/.claude/settings.json hits the hook's ask). Claude Code's native guard covers exactly that in-project config surface, so between the two layers the self-modification surface stays protected.

You can't suppress it with an allow rule. Per the docs, permissions.allow entries (even Edit(.claude/**)) do not pre-approve protected-path writes, and a PreToolUse hook returning allow/ask/silent cannot bypass the guard. To proceed you either approve the prompt each time, choose "Yes, and allow Claude to edit its own settings for this session" (approves further .claude/ writes for that session only), or run in bypassPermissions mode — which also disables this skill's hook and every other guard, so it is an explicit opt-out of all safety. This skill does not enable, configure, or override the protected-paths guard. (Introduced in Claude Code v2.1.126.)

What It Does

/optimus:permissions generates two files that work together to provide four layers of protection:

1. Allow List — Eliminate Routine Prompts

Auto-approves 13 built-in tools so Claude Code can work without interruption on standard operations:

Bash, Read, Edit, MultiEdit, Write, NotebookEdit, Glob, Grep, WebFetch, WebSearch, Task, TodoWrite, Skill

If your project uses MCP servers (.mcp.json), the skill auto-detects them and adds mcp__<server> entries to the allow list.

2. Deny List — Block Dangerous Patterns

Blocks 30 dangerous Bash patterns using Claude Code's permission pattern matching, which operates on the tool invocation before execution:

Category Blocked Patterns
Git (always blocked) git push --all, git push --mirror, git clean, git stash drop, *git push --force*, *git push -f *, *git push -f, *git reset --hard*
System destructive rm -rf /, rm -rf ~, sudo
Remote code execution curl | bash, curl | sh, wget | bash, wget | sh
Infrastructure destructive docker system prune, docker compose down -v, kubectl delete
Package publishing npm publish, cargo publish, twine upload, gem push, dotnet nuget push, mvn deploy, mvnw deploy, ./mvnw deploy, gradle publish, ./gradlew publish, npx npm-cli-login
Data exfiltration (limited) curl -d @file (best-effort; trivially bypassable via alternative syntax)

3. PreToolUse Hook — Tiered Path Enforcement

A PreToolUse hook that inspects every Edit, Write, MultiEdit, NotebookEdit, and Bash call at runtime:

Operation Inside Project Outside Project
Read / Search Allow (no prompt) Allow (no prompt)
Write / Edit Allow (no prompt) Ask user permission
Write / Edit precious unversioned file Ask user permission Ask user permission
Write / Edit / Delete Claude's own memory store (~/.claude/projects/.../memory/) Allow (no prompt)
Write / Edit / Delete Claude's session scratchpad (<temp>/claude/.../scratchpad/) Allow (no prompt)
Delete (rm/rmdir) Allow (no prompt) BLOCKED (hard deny)
Delete precious unversioned file BLOCKED (hard deny) BLOCKED (hard deny)

Memory-store exemption. Claude Code keeps a per-project auto-memory store under ~/.claude/projects/<project>/memory/. That path is outside the project, so without special handling every memory write would hit the "outside project root → ask" rule and prompt — repeatedly, since hooks don't cache. Because the store is Claude's own scratchpad (plain markdown, recoverable, designed to be written and pruned by Claude), the hook treats writes — and deletes — there like in-project operations and allows them without a prompt. The match is deliberately scoped to a single-segment memory/ subtree only: the rest of ~/.claude — most importantly settings.json, which defines the agent's own permissions — is not exempt and still prompts on an out-of-project write (and stays hard-blocked on an out-of-project delete), so the agent can't silently rewrite its own rules. A .. traversal out of the store (e.g. memory/../../settings.json) is rejected, so the exemption can't be used to escape it. The memory store and the session scratchpad (below) are the only out-of-project locations the hook auto-allows; the exemption spans every project's store, since all are Claude's own recoverable scratchpad.

Session-scratchpad exemption. The harness gives each session a scratchpad under <temp>/claude/<project>/<session>/scratchpad/ (the temp root comes from TMPDIR/TEMP/TMP, falling back to /tmp). Like the memory store it is Claude's own throwaway working area for the session, so writes and deletes there are allowed without a prompt. The match requires the full <temp>/claude/<project>/<session>/scratchpad shape — both <project> and <session> are single path segments — so it can't stretch to an unrelated scratchpad directory elsewhere under the temp root, and the same ..-traversal guard applies. If the temp root can't be resolved from the environment, the path falls back to the normal out-of-project prompt (fail-closed).

For structured tools (Edit/Write/NotebookEdit), the hook validates the file_path parameter directly — this is a structured field that cannot be obfuscated, making this the most reliable enforcement layer.

For Bash rm/rmdir, the hook does best-effort command parsing. Other Bash write commands (cp, mv, echo >) are not intercepted.

4. Branch Protection — Git Operations on Protected Branches

The hook enforces branch-aware git protection: operations that modify branch history are allowed on feature branches but blocked on protected branches (master, main, develop, dev, development, staging, stage, prod, production, release).

Git Operation Feature Branch Protected Branch
git commit Allow BLOCKED
git push Allow BLOCKED
git push --force BLOCKED (deny list) BLOCKED (deny list + hook)
git reset --hard BLOCKED (deny list) BLOCKED (deny list + hook)
git rebase Allow BLOCKED
git merge Allow BLOCKED
git checkout -- / git checkout . Allow BLOCKED
git restore Allow BLOCKED
git branch -D <branch> Allow (non-protected) BLOCKED (protected name)
git checkout -b / git switch -c Allow Allow (creates new branch)
git checkout -B / git switch -C Allow (non-protected) BLOCKED (protected name)
git push --all / git push --mirror BLOCKED BLOCKED

This enables a feature-branch workflow: Claude Code creates branches, commits, and pushes freely — but cannot modify protected branches directly. Use pull requests for code review before merging.

Customization: Edit the PROTECTED_BRANCHES array in .claude/hooks/restrict-paths.sh to add or remove protected branch names.

Trust Model and Assumptions

This skill operates on the principle that operations inside the project directory are trusted. Before using it, understand what this means in practice.

What runs WITHOUT prompts inside the project

Any Bash command that does not match the 30 deny patterns executes without asking, including:

  • Database operationspsql -c "DROP TABLE users", redis-cli FLUSHALL, sqlite3 db.sqlite "DELETE FROM orders"
  • File deletionrm data.csv, rm -rf uploads/, rm database.sqlite (only rm -rf / and rm -rf ~ are in the deny list)
  • Docker operationsdocker stop, docker rm (only docker system prune and docker compose down -v are denied)
  • Network requestscurl, wget, HTTP calls to any endpoint
  • Process managementkill, pkill, service restarts

Precious unversioned files are protected automatically

Well-known sensitive files (.env, *.key, *.sqlite, etc.) that are not tracked by git are automatically protected: edits prompt for approval, deletions are blocked. See Precious File Protection for the full list.

Other unversioned files (build output, node_modules/, data exports) are not protected — deletion is permanent and unprompted. If your project has non-standard sensitive files, add patterns to the is_precious() function in the hook script or re-run /optimus:permissions to scan for project-specific files.

The deny list is a blocklist, not an allowlist

The 30 blocked patterns catch common destructive operations, but anything not on the list passes through. The deny list is a safety net for known-dangerous commands, not a comprehensive policy. Branch-specific git commands (commit, push, rebase, etc.) are enforced by the hook's branch protection layer instead — see Branch Protection. You can add project-specific patterns (e.g., "Bash(docker *)") — see Customization.

Critical limitation — build command escalation: Allowing both file edits and Bash execution means any build system can be used for arbitrary code execution. Example: Claude edits package.json to add a "preinstall" script, then runs npm install — the script executes with full user permissions. This is inherent to any permission model that allows both edits and shell access, and cannot be mitigated by deny patterns alone.

Enforcement Reliability

Layer Mechanism Reliability Why
Allow list 13 tools auto-approved High Built-in Claude Code feature
Deny list 30 Bash patterns blocked Medium Pattern matching can be bypassed via chaining (#13371)
Branch protection (hook) Git ops blocked on protected branches Medium Best-effort command parsing; git checkout -b always allowed
PreToolUse hook (Edit/Write) Writes outside project prompt user High Validates structured file_path — cannot be obfuscated
PreToolUse hook (Bash rm/rmdir) Deletes outside project blocked Medium Best-effort command parsing

This is defense-in-depth: multiple independent layers that each catch different classes of risk. No single layer is perfect, but together they cover the most common destructive operations. The branch protection layer enables a feature-branch workflow — Claude Code can commit, push, and work freely on feature branches while protected branches require pull requests.

Hook Fail-Open Behavior

The path-restriction hook is designed to fail open (allow the operation) when it cannot determine safety:

Condition Behavior Why
CLAUDE_PROJECT_DIR not set Allow Cannot determine project root; blocking would break all operations
JSON input parsing fails Allow Malformed input should not block legitimate tool use
file_path extraction fails Allow Some tool invocations may have unexpected structure
No git repo at file's location Allow git ls-files unavailable; assumes tracked

This is a deliberate design choice: a fail-closed hook would block legitimate operations whenever Claude Code changes its JSON format.

What Gets Generated

File Purpose
.claude/settings.json Permission allow/deny rules + PreToolUse hook configuration
.claude/hooks/restrict-paths.sh Path-restriction hook (tiered security logic)

If .claude/settings.json already exists (e.g., from /optimus:init), the skill merges permissions into it — existing hooks, custom rules, and other configuration are preserved. The one exception is git deny patterns: if existing patterns are more restrictive than the template's well-tested set, the skill asks whether to replace them with the template's set or keep all. The hook script (.claude/hooks/restrict-paths.sh) is always replaced with the latest template version. Run either skill first; both share the same file safely.

Complements /optimus:init

This skill is designed as a companion to /optimus:init, which handles documentation, formatter hooks, and code quality agents. The two skills share .claude/settings.json:

Skill Creates Hook Type
/optimus:init PostToolUse hooks (auto-formatting after Edit/MultiEdit/Write) PostToolUse
/optimus:permissions Permission rules + PreToolUse hook (path restriction) PreToolUse

Run either skill first — both merge safely into the same file.

Skill Structure

File Purpose
SKILL.md Skill definition with step-by-step instructions
templates/settings.json Base permission settings (allow/deny lists + PreToolUse hook)
templates/hooks/restrict-paths.sh Path-restriction hook template

Precious File Protection (always-on)

The hook automatically protects well-known sensitive and irreplaceable files that are not tracked by git. No configuration needed.

Protected patterns

Pattern Category
.env* Secrets
appsettings.*.json (not appsettings.json) Secrets
local.settings.json Secrets
credentials.*, secrets.* Secrets
docker-compose.override.yml Local Docker config
*keyfile*.json, *-keyfile.json, *_keyfile.json Service account keys (GCP, etc.)
newrelic.config Monitoring agent config (may contain license keys)
*.key, *.pem, *.pfx, *.p12, *.cert, *.crt, *.jks Certificates / Keys
*.sqlite, *.sqlite3, *.db Database files
*.db-shm, *.db-wal, *.db-journal SQLite companion files (WAL/journal)
*.mdf, *.ldf, *.ndf Database files (SQL Server)
*.bak, *.dump, *.sql.gz Database backups
*.suo, *.user IDE user settings

Behavior

Operation Git-tracked? Precious? Behavior
Edit/Write Yes Any Allow (recoverable via git)
Edit/Write No Yes Ask (changes may be permanent)
Edit/Write No No Allow
rm/rmdir Yes Any Allow (recoverable via git)
rm/rmdir No Yes BLOCKED (deletion denied)
rm/rmdir No No Allow

Why always-on?

Unlike the previous OPTIMUS_PROTECT_UNVERSIONED approach, precious file protection targets only files that are known to be sensitive or irreplaceable. This avoids false positives on regenerable files like node_modules/ or dist/ — only well-known precious patterns trigger protection.

Extending with custom patterns

Re-run /optimus:permissions to scan for project-specific precious files. The skill will detect unversioned files that look sensitive and offer to add custom patterns to is_precious() in .claude/hooks/restrict-paths.sh. You can also edit the function directly.

Note: Re-running the skill replaces the hook with the latest template version. If you've added custom patterns to is_precious() (or edited PROTECTED_BRANCHES), the skill detects the modifications before overwriting and offers to re-apply them on top of the fresh template. For persistent customizations, edit the template in the plugin source.

Limitations

  • Basename matching only — the hook matches the file's name, not its path. A file named secrets.txt in any directory will be protected.
  • Not exhaustive — the list covers common patterns but cannot anticipate every project's sensitive files.
  • Prompts on every edit — Hooks fire on every tool call with no caching. Editing .env five times produces five prompts. Consider running git add on frequently-edited unversioned files to suppress prompts.
  • Fails open — If no git repository can be found for a given file (or git is unavailable), the precious file check is skipped and the operation is allowed. In multi-repo workspaces, the hook resolves git context per-file rather than relying on the project root.

Customization

To understand or modify how the skill works, start with SKILL.md. Key customization points:

  • Allow list: Edit templates/settings.jsonpermissions.allow to add or remove auto-approved tools
  • Deny list: Edit templates/settings.jsonpermissions.deny to add more blocked patterns (e.g., "Bash(docker *)") or remove overly-strict ones
  • Protected branches: Edit the PROTECTED_BRANCHES array in templates/hooks/restrict-paths.sh to add or remove protected branch names (default: master, main, develop, dev, development, staging, stage, prod, production, release)
  • Hook behavior: Edit templates/hooks/restrict-paths.sh to change what operations are blocked, asked, or allowed
  • MCP servers: If your project uses MCP servers (.mcp.json), the skill auto-detects them and adds mcp__<server> entries to the allow list
  • Precious patterns: Edit is_precious() in templates/hooks/restrict-paths.sh to add or remove protected file patterns, or re-run /optimus:permissions to scan for project-specific files

Known Limitations

This skill provides defense-in-depth, not bulletproof isolation. Be aware of:

Limitation Details
Inside project = fully trusted All operations inside the project run without prompts unless they match a deny pattern — except writes to Claude Code's native protected paths (.claude/, .git/, config files), which always prompt. See Trust Model and Assumptions for what this means in practice
Pattern matching bypass Bash deny patterns can be bypassed via command chaining or option insertion (#13371)
Bash writes not caught Only rm/rmdir is intercepted by the hook. Other Bash writes (echo >, cp, mv) outside the project are not blocked
Build command escalation File edits + build commands = arbitrary code execution. See the critical limitation callout above
Data exfiltration Network commands like curl -X POST can upload data to external servers. The deny list blocks curl -d @file but this is trivially bypassable
Deny list is a blocklist Only 30 specific patterns are blocked. Database commands, service management, and many other destructive operations are not covered
Not OS-level sandboxing For full isolation, use sandboxing or devcontainers

Despite these limitations, this approach is significantly safer than --dangerously-skip-permissions:

  1. Structured tool validation is reliable — Edit/Write file_path inputs cannot be obfuscated
  2. The deny list and branch protection block the most common destructive patterns — rm -rf /, sudo, npm publish, plus git operations on protected branches
  3. Any write outside the project requires explicit user approval — unknown operations default to asking, not allowing
  4. Precious files are always protected — well-known sensitive files (.env, *.key, *.sqlite, etc.) are automatically guarded when not tracked by git

References

Official documentation and resources that informed this skill's design:

Requirements

  • Claude Code 1.0.33+ (plugin support)
  • Git
  • Bash (available by default on macOS/Linux; on Windows via Git Bash or WSL)

License

MIT