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).
This skill is part of the optimus plugin. See the main README for installation instructions.
Run: Type /optimus:permissions in any project directory.
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 trueloop). 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.
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 explicitask) before the classifier. The skill's deny list is the hardest boundary — it blocksrm -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.shPreToolUse 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(andTask) 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.
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.)
/optimus:permissions generates two files that work together to provide four layers of protection:
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.
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) |
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.
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.
This skill operates on the principle that operations inside the project directory are trusted. Before using it, understand what this means in practice.
Any Bash command that does not match the 30 deny patterns executes without asking, including:
- Database operations —
psql -c "DROP TABLE users",redis-cli FLUSHALL,sqlite3 db.sqlite "DELETE FROM orders" - File deletion —
rm data.csv,rm -rf uploads/,rm database.sqlite(onlyrm -rf /andrm -rf ~are in the deny list) - Docker operations —
docker stop,docker rm(onlydocker system pruneanddocker compose down -vare denied) - Network requests —
curl,wget, HTTP calls to any endpoint - Process management —
kill,pkill, service restarts
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 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.jsonto add a"preinstall"script, then runsnpm 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.
| 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.
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.
| 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.
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.
| 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 |
The hook automatically protects well-known sensitive and irreplaceable files that are not tracked by git. No configuration needed.
| 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 |
| 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 |
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.
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.
- Basename matching only — the hook matches the file's name, not its path. A file named
secrets.txtin 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
.envfive times produces five prompts. Consider runninggit addon frequently-edited unversioned files to suppress prompts. - Fails open — If no git repository can be found for a given file (or
gitis 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.
To understand or modify how the skill works, start with SKILL.md. Key customization points:
- Allow list: Edit
templates/settings.json→permissions.allowto add or remove auto-approved tools - Deny list: Edit
templates/settings.json→permissions.denyto add more blocked patterns (e.g.,"Bash(docker *)") or remove overly-strict ones - Protected branches: Edit the
PROTECTED_BRANCHESarray intemplates/hooks/restrict-paths.shto add or remove protected branch names (default: master, main, develop, dev, development, staging, stage, prod, production, release) - Hook behavior: Edit
templates/hooks/restrict-paths.shto change what operations are blocked, asked, or allowed - MCP servers: If your project uses MCP servers (
.mcp.json), the skill auto-detects them and addsmcp__<server>entries to the allow list - Precious patterns: Edit
is_precious()intemplates/hooks/restrict-paths.shto add or remove protected file patterns, or re-run/optimus:permissionsto scan for project-specific files
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:
- Structured tool validation is reliable — Edit/Write
file_pathinputs cannot be obfuscated - The deny list and branch protection block the most common destructive patterns — rm -rf /, sudo, npm publish, plus git operations on protected branches
- Any write outside the project requires explicit user approval — unknown operations default to asking, not allowing
- Precious files are always protected — well-known sensitive files (.env, *.key, *.sqlite, etc.) are automatically guarded when not tracked by git
Official documentation and resources that informed this skill's design:
- Anthropic — Claude Code Permissions: allow/deny rules, permission patterns, settings.json format
- Anthropic — Auto Mode Configuration: classifier-based permission mode, the
autoModeblock,claude auto-modeCLI - Anthropic — Permission Modes: default, acceptEdits, plan, auto, and bypassPermissions modes
- Anthropic — Claude Code Hooks: PreToolUse/PostToolUse hook system, matchers, JSON protocol
- Anthropic — Hooks Guide: practical examples of hook implementations
- Anthropic — Sandboxing: OS-level isolation via Seatbelt (macOS) and bubblewrap (Linux/WSL2), platform support matrix
- Anthropic — Claude Code Sandboxing (Engineering Blog): design philosophy behind sandboxing
- Anthropic — Development Containers: container-based isolation that works on all platforms
- Anthropic — Best Practices: recommended patterns for Claude Code usage
- Anthropic — Reference Devcontainer: official devcontainer configuration
- Anthropic — Ralph Wiggum Plugin: autonomous loop plugin (requires true isolation, not this skill)
- Trail of Bits — Claude Code Devcontainer: security-hardened devcontainer for audits and untrusted code review
- Claude Code 1.0.33+ (plugin support)
- Git
- Bash (available by default on macOS/Linux; on Windows via Git Bash or WSL)