{"id":34856503,"url":"https://github.com/kenryu42/claude-code-safety-net","last_synced_at":"2026-05-07T04:01:41.882Z","repository":{"id":330471312,"uuid":"1122873619","full_name":"kenryu42/claude-code-safety-net","owner":"kenryu42","description":"A coding agent hook that acts as a safety net, catching destructive git and filesystem commands before they execute.","archived":false,"fork":false,"pushed_at":"2026-04-30T18:50:18.000Z","size":1363,"stargazers_count":1294,"open_issues_count":6,"forks_count":60,"subscribers_count":8,"default_branch":"main","last_synced_at":"2026-04-30T19:21:30.564Z","etag":null,"topics":["claude","claude-code","claude-code-plugin","destructive-commands","hook","security"],"latest_commit_sha":null,"homepage":"","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/kenryu42.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":"CONTRIBUTING.md","funding":".github/FUNDING.yml","license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":"AGENTS.md","dco":null,"cla":null},"funding":{"github":["kenryu42"],"patreon":null,"open_collective":null,"ko_fi":null,"tidelift":null,"community_bridge":null,"liberapay":null,"issuehunt":null,"lfx_crowdfunding":null,"polar":null,"buy_me_a_coffee":null,"thanks_dev":null,"custom":null}},"created_at":"2025-12-25T17:52:57.000Z","updated_at":"2026-04-30T18:56:54.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/kenryu42/claude-code-safety-net","commit_stats":null,"previous_names":["kenryu42/claude-code-safety-net"],"tags_count":16,"template":false,"template_full_name":null,"purl":"pkg:github/kenryu42/claude-code-safety-net","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kenryu42%2Fclaude-code-safety-net","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kenryu42%2Fclaude-code-safety-net/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kenryu42%2Fclaude-code-safety-net/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kenryu42%2Fclaude-code-safety-net/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/kenryu42","download_url":"https://codeload.github.com/kenryu42/claude-code-safety-net/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kenryu42%2Fclaude-code-safety-net/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":32722166,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-07T02:14:30.463Z","status":"ssl_error","status_checked_at":"2026-05-07T02:14:29.405Z","response_time":62,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.6:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["claude","claude-code","claude-code-plugin","destructive-commands","hook","security"],"created_at":"2025-12-25T20:01:54.281Z","updated_at":"2026-05-07T04:01:41.874Z","avatar_url":"https://github.com/kenryu42.png","language":"TypeScript","funding_links":["https://github.com/sponsors/kenryu42"],"categories":["TypeScript","🪝 Hooks and Guardrails","Skills \u0026 Plugins","Claude Plugins","Defense \u0026 Security Controls"],"sub_categories":["Anthropic Engineering \u0026 Blog","Agent Runtime Security \u0026 Sandboxing"],"readme":"# Claude Code Safety Net\n\n[![CI](https://github.com/kenryu42/claude-code-safety-net/actions/workflows/ci.yml/badge.svg)](https://github.com/kenryu42/claude-code-safety-net/actions/workflows/ci.yml)\n[![codecov](https://codecov.io/github/kenryu42/claude-code-safety-net/branch/main/graph/badge.svg?token=C9QTION6ZF)](https://codecov.io/github/kenryu42/claude-code-safety-net)\n[![Version](https://img.shields.io/github/v/tag/kenryu42/claude-code-safety-net?label=version\u0026color=blue)](https://github.com/kenryu42/claude-code-safety-net)\n[![Claude Code](https://img.shields.io/badge/Claude%20Code-D27656)](#claude-code-installation)\n[![OpenCode](https://img.shields.io/badge/OpenCode-black)](#opencode-installation)\n[![Gemini CLI](https://img.shields.io/badge/Gemini%20CLI-678AE3)](#gemini-cli-installation)\n[![Copilot CLI](https://img.shields.io/badge/Copilot%20CLI-4EA5C9)](#github-copilot-cli-installation)\n[![Codex](https://img.shields.io/badge/Codex-white)](#codex-installation)\n[![License: MIT](https://img.shields.io/badge/License-MIT-red.svg)](https://opensource.org/licenses/MIT)\n\n\u003cdiv align=\"center\"\u003e\n\n[![CC Safety Net](./.github/assets/cc-safety-net.png)](./.github/assets/cc-safety-net.png)\n\n\u003c/div\u003e\n\nA Claude Code plugin that acts as a safety net, catching destructive git and filesystem commands before they execute.\n\n## Contents\n\n- [Why This Exists](#why-this-exists)\n- [Why Use This Instead of Permission Deny Rules?](#why-use-this-instead-of-permission-deny-rules)\n- [What About Sandboxing?](#what-about-sandboxing)\n- [Prerequisites](#prerequisites)\n- [Quick Start](#quick-start)\n  - [Claude Code Installation](#claude-code-installation)\n  - [OpenCode Installation](#opencode-installation)\n  - [Gemini CLI Installation](#gemini-cli-installation)\n  - [GitHub Copilot CLI Installation](#github-copilot-cli-installation)\n  - [Codex Installation](#codex-installation)\n- [Status Line Integration](#status-line-integration)\n  - [Setup via Slash Command](#setup-via-slash-command)\n  - [Manual Setup](#manual-setup)\n  - [Emoji Mode Indicators](#emoji-mode-indicators)\n- [Diagnostics](#diagnostics)\n- [Explain (Debug Analysis)](#explain-debug-analysis)\n- [Commands Blocked](#commands-blocked)\n- [Commands Allowed](#commands-allowed)\n- [What Happens When Blocked](#what-happens-when-blocked)\n- [Testing the Hook](#testing-the-hook)\n- [Development](#development)\n- [Custom Rules (Experimental)](#custom-rules-experimental)\n  - [Config File Location](#config-file-location)\n  - [Rule Schema](#rule-schema)\n  - [Matching Behavior](#matching-behavior)\n  - [Examples](#examples)\n  - [Error Handling](#error-handling)\n- [Advanced Features](#advanced-features)\n  - [Strict Mode](#strict-mode)\n  - [Paranoid Mode](#paranoid-mode)\n  - [Worktree Mode](#worktree-mode)\n  - [Shell Wrapper Detection](#shell-wrapper-detection)\n  - [Interpreter One-Liner Detection](#interpreter-one-liner-detection)\n  - [Secret Redaction](#secret-redaction)\n  - [Audit Logging](#audit-logging)\n- [License](#license)\n\n## Why This Exists\n\nWe learned the [hard way](https://www.reddit.com/r/ClaudeAI/comments/1pgxckk/claude_cli_deleted_my_entire_home_directory_wiped/) that instructions aren't enough to keep AI agents in check.\nAfter Claude Code silently wiped out hours of progress with a single `rm -rf ~/` or `git checkout --`, it became evident that **soft** rules in an `CLAUDE.md` or `AGENTS.md` file cannot replace **hard** technical constraints.\nThe current approach is to use a dedicated hook to programmatically prevent agents from running destructive commands.\n\n## Why Use This Instead of Permission Deny Rules?\n\nClaude Code's `.claude/settings.json` supports [deny rules](https://code.claude.com/docs/en/iam#tool-specific-permission-rules) with wildcard matching (e.g., `Bash(git reset --hard:*)`). Here's how this plugin differs:\n\n### At a Glance\n\n| | Permission Deny Rules | Safety Net |\n|---|---|---|\n| **Setup** | Manual configuration required | Works out of the box |\n| **Parsing** | Wildcard pattern matching | Semantic command analysis |\n| **Execution order** | Runs second | Runs first (PreToolUse hook) |\n| **Shell wrappers** | Not handled automatically (must match wrapper forms) | Recursively analyzed (5 levels) |\n| **Interpreter one-liners** | Not handled automatically (must match interpreter forms) | Detected and blocked |\n\n### Permission Rules Have Known Bypass Vectors\n\nEven with wildcard matching, Bash permission patterns are intentionally limited and can be bypassed in many ways:\n\n| Bypass Method | Example |\n|---------------|---------|\n| Options before value | `curl -X GET http://evil.com` bypasses `Bash(curl http://evil.com:*)` |\n| Shell variables | `URL=http://evil.com \u0026\u0026 curl $URL` bypasses URL pattern |\n| Flag reordering | `rm -r -f /` bypasses `Bash(rm -rf:*)` |\n| Extra whitespace | `rm  -rf /` (double space) bypasses pattern |\n| Shell wrappers | `sh -c \"rm -rf /\"` bypasses `Bash(rm:*)` entirely |\n\n### Safety Net Handles What Patterns Can't\n\n| Scenario | Permission Rules | Safety Net |\n|----------|------------------|------------|\n| `git checkout -b feature` (safe) | Blocked by `Bash(git checkout:*)` | Allowed |\n| `git checkout -- file` (dangerous) | Blocked by `Bash(git checkout:*)` | Blocked |\n| `rm -rf /tmp/cache` (safe) | Blocked by `Bash(rm -rf:*)` | Allowed |\n| `rm -r -f /` (dangerous) | Allowed (flag order) | Blocked |\n| `bash -c 'git reset --hard'` | Allowed (wrapper) | Blocked |\n| `python -c 'os.system(\"rm -rf /\")'` | Allowed (interpreter) | Blocked |\n\n### Defense in Depth\n\nPreToolUse hooks run [**before**](https://code.claude.com/docs/en/iam#additional-permission-control-with-hooks) the permission system. This means Safety Net inspects every command first, regardless of your permission configuration. Even if you misconfigure deny rules, Safety Net provides a fallback layer of protection.\n\n**Use both together**: Permission deny rules for quick, user-configurable blocks; Safety Net for robust, bypass-resistant protection that works out of the box.\n\n## What About Sandboxing?\n\nClaude Code offers [native sandboxing](https://code.claude.com/docs/en/sandboxing) that provides OS-level filesystem and network isolation. Here's how it compares to Safety Net:\n\n### Different Layers of Protection\n\n| | Sandboxing | Safety Net |\n|---|---|---|\n| **Enforcement** | OS-level (Seatbelt/bubblewrap) | Application-level (PreToolUse hook) |\n| **Approach** | Containment — restricts filesystem + network access | Command analysis — blocks destructive operations |\n| **Filesystem** | Writes restricted (default: cwd); reads are broad by default | Only destructive operations blocked |\n| **Network** | Domain-based proxy filtering | None |\n| **Git awareness** | None | Explicit rules for destructive git operations |\n| **Bypass resistance** | High — OS enforces boundaries | Lower — analyzes command strings only |\n\n### Why Sandboxing Isn't Enough\n\nSandboxing restricts filesystem + network access, but it doesn't understand whether an operation is destructive within those boundaries. These commands are not blocked by the sandbox boundary:\n\n\u003e [!NOTE]\n\u003e Whether they're auto-run or require confirmation depends on your sandbox mode (auto-allow vs regular permissions), and network access still depends on your allowed-domain policy. Claude Code can also retry a command outside the sandbox via `dangerouslyDisableSandbox` (with user permission); this can be disabled with `allowUnsandboxedCommands: false`.\n\n| Command | Sandboxing | Safety Net |\n|---------|------------|------------|\n| `git reset --hard` | Allowed (within cwd) | **Blocked** |\n| `git checkout -- .` | Allowed (within cwd) | **Blocked** |\n| `git stash clear` | Allowed (within cwd) | **Blocked** |\n| `git push --force` | Allowed (if remote domain is allowed) | **Blocked** |\n| `rm -rf .` | Allowed (within cwd) | **Blocked** |\n\nSandboxing sees `git reset --hard` as a safe operation—it only modifies files within the current directory. But you just lost all uncommitted work.\n\n### When to Use Sandboxing Instead\n\nSandboxing is the better choice when your primary concern is:\n\n- **Prompt injection attacks** — Reduces exfiltration risk by restricting outbound domains (depends on your allowed-domain policy)\n- **Malicious dependencies** — Limits filesystem writes and network access by default (subject to your sandbox configuration)\n- **Untrusted code execution** — OS-level containment is stronger than pattern matching\n- **Network control** — Safety Net has no network protection\n\n### Recommended: Use Both\n\nThey protect against different threats:\n\n- **Sandboxing** contains blast radius — even if something goes wrong, damage is limited to cwd and approved network domains\n- **Safety Net** prevents footguns — catches git-specific mistakes that are technically \"safe\" from the sandbox's perspective\n\nRunning both together provides defense-in-depth. Sandboxing handles unknown threats; Safety Net handles known destructive patterns that sandboxing permits.\n\n## Prerequisites\n\n- **Node.js**: Version 18 or higher is required to run this plugin\n\n## Quick Start\n\n### Claude Code Installation\n\n```bash\n/plugin marketplace add kenryu42/cc-marketplace\n/plugin install safety-net@cc-marketplace\n/reload-plugins\n```\n\n### Claude Code Auto-Update\n\n1. Run `/plugin` → Select `Marketplaces` → Choose `cc-marketplace` → Enable auto-update\n\n---\n\n### OpenCode Installation\n\n**Option A: Let an LLM do it**\n\nPaste this into any LLM agent (Claude Code, OpenCode, Cursor, etc.):\n\n```\nInstall the cc-safety-net plugin in `~/.config/opencode/opencode.json` (or `.jsonc`) according to the schema at: https://opencode.ai/config.json\n```\n\n**Option B: Manual setup**\n\n1. **Add the plugin to your config** `~/.config/opencode/opencode.json` (or `.jsonc`):\n\n  ```json\n  {\n    \"plugin\": [\"cc-safety-net\"]\n  }\n  ```\n\n---\n\n### Gemini CLI Installation\n\n```bash\ngemini extensions install https://github.com/kenryu42/gemini-safety-net\n```\n\n---\n\n### GitHub Copilot CLI Installation\n\n```bash\n/plugin install kenryu42/copilot-safety-net\n```\n\n\u003e [!NOTE]\n\u003e After installing the plugin, you need to restart your Copilot CLI for it to take effect.\n\n---\n\n### Codex Installation\n\n1. Enable Codex plugin hooks in `~/.codex/config.toml`:\n\n  ```toml\n  [features]\n  plugin_hooks = true\n  ```\n\n2. Add the marketplace:\n\n  ```bash\n  codex plugin marketplace add kenryu42/cc-marketplace\n  ```\n\n3. Start Codex.\n4. In the TUI, run `/plugins`.\n5. Use arrow keys to select `[cc-marketplace]`.\n6. Press Enter to install the plugin.\n\n---\n\n## Status Line Integration\n\nSafety Net can display its status in Claude Code's status line, showing whether protection is active and which modes are enabled.\n\nAdd the following to your `~/.claude/settings.json`:\n\n**Using Bun (recommended):**\n\n```json\n{\n  \"statusLine\": {\n    \"type\": \"command\",\n    \"command\": \"bunx cc-safety-net --statusline\"\n  }\n}\n```\n\n**Using Claude X:**\n\n```json\n{\n  \"statusLine\": {\n    \"type\": \"command\",\n    \"command\": \"BUN_BE_BUN=1 claude x cc-safety-net --statusline\"\n  }\n}\n```\n\u003e [!NOTE]\n\u003e The `claude x` command is only compatible with the native version of Claude Code. If you installed via npm, please use `npx` or `bunx` instead.\n\n\n\n**Using NPM:**\n\n```json\n{\n  \"statusLine\": {\n    \"type\": \"command\",\n    \"command\": \"npx -y cc-safety-net --statusline\"\n  }\n}\n```\n\n**Piping with existing status line:**\n\nIf you already have a status line command, you can pipe Safety Net at the end:\n\n```json\n{\n  \"statusLine\": {\n    \"type\": \"command\",\n    \"command\": \"your-existing-command | bunx cc-safety-net --statusline\"\n  }\n}\n```\n\nChanges take effect immediately — no restart needed.\n\n### Emoji Mode Indicators\n\nThe status line displays different emojis based on the current configuration:\n\n| Status | Display | Meaning |\n|--------|---------|---------|\n| Plugin disabled | `🛡️ Safety Net ❌` | Safety Net plugin is not enabled |\n| Default mode | `🛡️ Safety Net ✅` | Protection active with default settings |\n| Strict mode | `🛡️ Safety Net 🔒` | `SAFETY_NET_STRICT=1` — fail-closed on unparseable commands |\n| Paranoid mode | `🛡️ Safety Net 👁️` | `SAFETY_NET_PARANOID=1` — all paranoid checks enabled |\n| Paranoid RM only | `🛡️ Safety Net 🗑️` | `SAFETY_NET_PARANOID_RM=1` — blocks `rm -rf` even within cwd |\n| Paranoid interpreters only | `🛡️ Safety Net 🐚` | `SAFETY_NET_PARANOID_INTERPRETERS=1` — blocks interpreter one-liners |\n| Worktree mode | `🛡️ Safety Net 🌳` | `SAFETY_NET_WORKTREE=1` — relax local git discards inside linked worktrees |\n| Strict + Paranoid | `🛡️ Safety Net 🔒👁️` | Both strict and paranoid modes enabled |\n\nMultiple mode emojis are combined when multiple environment variables are set.\n\n## Diagnostics\n\nRun the diagnostic command to verify your installation and troubleshoot issues:\n\n```bash\nnpx cc-safety-net doctor\n# or with bun\nbunx cc-safety-net doctor\n```\n\nThe doctor command checks:\n\n| Check | Description |\n|-------|-------------|\n| Hook Integration | Verifies the plugin is properly configured for each supported platform |\n| Self-Test | Runs sample commands to confirm blocking works correctly |\n| Configuration | Validates custom rules in user and project configs |\n| Environment | Shows status of mode flags (SAFETY_NET_STRICT, SAFETY_NET_PARANOID, etc.) |\n| Recent Activity | Summarizes blocked commands from the last 7 days |\n| System Info | Displays versions of all relevant tools |\n| Update Check | Checks if a newer version is available |\n\n### Options\n\n| Flag | Description |\n|------|-------------|\n| `--json` | Output in JSON format (useful for sharing in bug reports) |\n| `--skip-update-check` | Skip the npm version check |\n\n## Explain (Debug Analysis)\n\nTrace how Safety Net analyzes a command step-by-step. Useful for debugging why a command is blocked or allowed, or when developing custom rules.\n\n```bash\nnpx cc-safety-net explain \"git reset --hard\"\n# or with bun\nbunx cc-safety-net explain \"git reset --hard\"\n```\n\n### Options\n\n| Flag | Description |\n|------|-------------|\n| `--json` | Output analysis as JSON |\n| `--cwd \u003cpath\u003e` | Use custom working directory for analysis |\n\n### Examples\n\n```bash\nnpx cc-safety-net explain \"rm -rf /\"\nnpx cc-safety-net explain --json \"git checkout -- file.txt\"\nnpx cc-safety-net explain --cwd /tmp \"git status\"\n```\n\n## Commands Blocked\n\n| Command Pattern | Why It's Dangerous |\n|-----------------|-------------------|\n| git checkout -- files | Discards uncommitted changes permanently |\n| git checkout \\\u003cref\\\u003e -- \\\u003cpath\\\u003e | Overwrites working tree with ref version |\n| git checkout \\\u003cref\\\u003e \\\u003cpath\\\u003e | May overwrite working tree when Git disambiguates ref vs pathspec |\n| git restore files | Discards uncommitted changes |\n| git restore --worktree | Explicitly discards working tree changes |\n| git reset --hard | Destroys all uncommitted changes |\n| git reset --merge | Can lose uncommitted changes |\n| git clean -f | Removes untracked files permanently |\n| git push --force / -f | Destroys remote history |\n| git branch -D | Force-deletes branch without merge check |\n| git stash drop | Permanently deletes stashed changes |\n| git stash clear | Deletes ALL stashed changes |\n| git worktree remove --force | Force-deletes worktree without checking for changes |\n| rm -rf (paths outside cwd) | Recursive file deletion outside the current directory |\n| rm -rf / or ~ or $HOME | Root/home deletion is extremely dangerous |\n| find ... -delete | Permanently removes files matching criteria |\n| xargs rm -rf | Dynamic input makes targets unpredictable |\n| xargs \\\u003cshell\\\u003e -c | Can execute arbitrary commands |\n| parallel rm -rf | Dynamic input makes targets unpredictable |\n| parallel \\\u003cshell\\\u003e -c | Can execute arbitrary commands |\n\n## Commands Allowed\n\n| Command Pattern | Why It's Safe |\n|-----------------|--------------|\n| git checkout -b branch | Creates new branch |\n| git checkout --orphan | Creates orphan branch |\n| git restore --staged | Only unstages, doesn't discard |\n| git restore --help/--version | Help/version output |\n| git branch -d | Safe delete with merge check |\n| git clean -n / --dry-run | Preview only |\n| git push --force-with-lease | Safe force push |\n| rm -rf /tmp/... | Temp directories are ephemeral |\n| rm -rf /var/tmp/... | System temp directory |\n| rm -rf $TMPDIR/... | User's temp directory |\n| rm -rf ./... (within cwd) | Limited to current working directory |\n| git restore / checkout -- / reset --hard / clean -f (in linked worktree) | Relaxed only when `SAFETY_NET_WORKTREE=1` and cwd is a linked worktree |\n\n## What Happens When Blocked\n\nWhen a destructive command is detected, the plugin blocks the tool execution and provides a reason.\n\nExample output:\n```text\nBLOCKED by Safety Net\n\nReason: git checkout -- discards uncommitted changes permanently. Use 'git stash' first.\n\nCommand: git checkout -- src/main.py\n\nIf this operation is truly needed, ask the user for explicit permission and have them run the command manually.\n```\n\n## Testing the Hook\n\nYou can manually test the hook by attempting to run blocked commands in Claude Code:\n\n```bash\n# This should be blocked\ngit checkout -- README.md\n\n# This should be allowed\ngit checkout -b test-branch\n```\n\n## Development\n\nSee [CONTRIBUTING.md](CONTRIBUTING.md) for details on how to contribute to this project.\n\n## Custom Rules (Experimental)\n\nBeyond the built-in protections, you can define your own blocking rules to enforce team conventions or project-specific safety policies.\n\n\u003e [!TIP]\n\u003e Use the `set-custom-rules` skill to create custom rules interactively with natural language.\n\u003e\n\u003e If your agent does not support skills, prompt it with:\n\u003e ```text\n\u003e run npx cc-safety-net --custom-rules-doc and help me set up custom rules\n\u003e ```\n\n### Quick Example\n\nCreate `.safety-net.json` in your project root:\n\n```json\n{\n  \"version\": 1,\n  \"rules\": [\n    {\n      \"name\": \"block-git-add-all\",\n      \"command\": \"git\",\n      \"subcommand\": \"add\",\n      \"block_args\": [\"-A\", \"--all\", \".\"],\n      \"reason\": \"Use 'git add \u003cspecific-files\u003e' instead of blanket add.\"\n    }\n  ]\n}\n```\n\nNow `git add -A`, `git add --all`, and `git add .` will be blocked with your custom message.\n\n### Config File Location\n\nConfig files are loaded from two scopes and merged:\n\n1. **User scope**: `~/.cc-safety-net/config.json` (always loaded if exists)\n2. **Project scope**: `.safety-net.json` in the current working directory (loaded if exists)\n\n**Merging behavior**:\n- Rules from both scopes are combined\n- If the same rule name exists in both scopes, **project scope wins**\n- Rule name comparison is case-insensitive (`MyRule` and `myrule` are considered duplicates)\n\nThis allows you to define personal defaults in user scope while letting projects override specific rules.\n\nIf no config file is found in either location, only built-in rules apply.\n\n### Config Schema\n\n| Field | Type | Required | Description |\n|-------|------|----------|-------------|\n| `version` | integer | Yes | Schema version (must be `1`) |\n| `rules` | array | No | List of custom blocking rules (defaults to empty) |\n\n### Rule Schema\n\n| Field | Type | Required | Description |\n|-------|------|----------|-------------|\n| `name` | string | Yes | Unique identifier (letters, numbers, hyphens, underscores; max 64 chars) |\n| `command` | string | Yes | Base command to match (e.g., `git`, `npm`, `docker`) |\n| `subcommand` | string | No | Subcommand to match (e.g., `add`, `install`). If omitted, matches any. |\n| `block_args` | array | Yes | Arguments that trigger the block (at least one required) |\n| `reason` | string | Yes | Message shown when blocked (max 256 chars) |\n\n### Matching Behavior\n\n- **Commands** are normalized to basename (`/usr/bin/git` → `git`)\n- **Subcommand** is the first non-option argument after the command\n- **Arguments** are matched literally (no regex, no glob), with short option expansion\n- A command is blocked if **any** argument in `block_args` is present\n- **Short options** are expanded: `-Ap` matches `-A` (bundled flags are unbundled)\n- **Long options** use exact match: `--all-files` does NOT match `--all`\n- Custom rules only add restrictions—they cannot bypass built-in protections\n\n#### Known Limitations\n\n- **Short option expansion**: `-Cfoo` is treated as `-C -f -o -o`, not `-C foo`. Blocking `-f` may false-positive on attached option values.\n\n### Examples\n\n#### Block global npm installs\n\n```json\n{\n  \"version\": 1,\n  \"rules\": [\n    {\n      \"name\": \"block-npm-global\",\n      \"command\": \"npm\",\n      \"subcommand\": \"install\",\n      \"block_args\": [\"-g\", \"--global\"],\n      \"reason\": \"Global npm installs can cause version conflicts. Use npx or local install.\"\n    }\n  ]\n}\n```\n\n#### Block dangerous docker commands\n\n```json\n{\n  \"version\": 1,\n  \"rules\": [\n    {\n      \"name\": \"block-docker-system-prune\",\n      \"command\": \"docker\",\n      \"subcommand\": \"system\",\n      \"block_args\": [\"prune\"],\n      \"reason\": \"docker system prune removes all unused data. Use targeted cleanup instead.\"\n    }\n  ]\n}\n```\n\n#### Multiple rules\n\n```json\n{\n  \"version\": 1,\n  \"rules\": [\n    {\n      \"name\": \"block-git-add-all\",\n      \"command\": \"git\",\n      \"subcommand\": \"add\",\n      \"block_args\": [\"-A\", \"--all\", \".\", \"-u\", \"--update\"],\n      \"reason\": \"Use 'git add \u003cspecific-files\u003e' instead of blanket add.\"\n    },\n    {\n      \"name\": \"block-npm-global\",\n      \"command\": \"npm\",\n      \"subcommand\": \"install\",\n      \"block_args\": [\"-g\", \"--global\"],\n      \"reason\": \"Use npx or local install instead of global.\"\n    }\n  ]\n}\n```\n\n### Error Handling\n\nCustom rules use **silent fallback** error handling. If your config file is invalid, the safety net silently falls back to built-in rules only:\n\n| Scenario | Behavior |\n|----------|----------|\n| Config file not found | Silent — use built-in rules only |\n| Empty config file | Silent — use built-in rules only |\n| Invalid JSON syntax | Silent — use built-in rules only |\n| Missing required field | Silent — use built-in rules only |\n| Invalid field format | Silent — use built-in rules only |\n| Duplicate rule name | Silent — use built-in rules only |\n\n\n\u003e [!IMPORTANT]  \n\u003e If you add or modify custom rules manually, always validate them with `npx -y cc-safety-net --verify-config` or the `verify-custom-rules` skill in your coding agent.\n\n### Block Output Format\n\nWhen a custom rule blocks a command, the output includes the rule name:\n\n```text\nBLOCKED by Safety Net\n\nReason: [block-git-add-all] Use 'git add \u003cspecific-files\u003e' instead of blanket add.\n\nCommand: git add -A\n```\n\n## Advanced Features\n\n### Strict Mode\n\nBy default, unparseable commands are allowed through. Enable strict mode to fail-closed\nwhen the hook input or shell command cannot be safely analyzed (e.g., invalid JSON,\nunterminated quotes, malformed `bash -c` wrappers):\n\n```bash\nexport SAFETY_NET_STRICT=1\n```\n\n### Paranoid Mode\n\nParanoid mode enables stricter safety checks that may be disruptive to normal workflows.\nYou can enable it globally or via focused toggles:\n\n```bash\n# Enable all paranoid checks\nexport SAFETY_NET_PARANOID=1\n\n# Or enable specific paranoid checks\nexport SAFETY_NET_PARANOID_RM=1\nexport SAFETY_NET_PARANOID_INTERPRETERS=1\n```\n\nParanoid behavior:\n\n- **rm**: blocks non-temp `rm -rf` even within the current working directory.\n- **interpreters**: blocks interpreter one-liners like `python -c`, `node -e`, `ruby -e`,\n  and `perl -e` (these can hide destructive commands).\n\n### Worktree Mode\n\nLinked git worktrees are designed as disposable, isolated workspaces — discarding\nchanges inside one doesn't risk the main working tree. Worktree mode relaxes\nlocal-discard rules when (and only when) the command is proven to run inside a\nlinked worktree:\n\n```bash\nexport SAFETY_NET_WORKTREE=1\n```\n\nWhen enabled, these commands are allowed inside a linked worktree:\n\n- `git restore \u003cfile\u003e` and `git restore --worktree \u003cfile\u003e`\n- `git checkout -- \u003cfile\u003e`, `git checkout \u003cref\u003e -- \u003cfile\u003e`, `git checkout --force`,\n  and ambiguous multi-positional checkout forms\n- `git switch --discard-changes` and `git switch -f / --force`\n- `git reset --hard` and `git reset --merge`\n- `git clean -f` (and combined short flags like `-fd`)\n\nThese remain blocked even in linked worktrees because they reach beyond the\nlocal working tree:\n\n- `git push --force` (affects remote)\n- `git branch -D` (affects shared refs)\n- `git stash drop` / `git stash clear` (stash is shared across worktrees)\n- `git worktree remove --force` (could delete another worktree)\n\nDetection is fail-closed and mostly filesystem-based:\n\n- A linked worktree is identified by a `.git` *file* containing `gitdir:` whose\n  resolved git directory contains a `commondir` file. Main worktrees and\n  submodules don't satisfy this and are not relaxed.\n- The cwd walk uses `realpath` so symlinked paths resolve correctly.\n- `git -C \u003cpath\u003e` (including chained `-C` and attached `-Cpath`) is honored;\n  unresolved targets keep the command blocked.\n- Relaxation is disabled if cwd becomes unknown (e.g., after `cd`/`pushd`),\n  if `--git-dir` / `--work-tree` is passed, or if `GIT_DIR` / `GIT_WORK_TREE`\n  / `GIT_COMMON_DIR` is set in the environment.\n- Git may be invoked from a trusted system path to inspect effective config that\n  could make submodule operations recursive.\n\n### Shell Wrapper Detection\n\nThe guard recursively analyzes commands wrapped in shells:\n\n```bash\nbash -c 'git reset --hard'    # Blocked\nsh -lc 'rm -rf /'             # Blocked\n```\n\n### Interpreter One-Liner Detection\n\nDetects destructive commands hidden in Python/Node/Ruby/Perl one-liners:\n\n```bash\npython -c 'import os; os.system(\"rm -rf /\")'  # Blocked\n```\n\n### Secret Redaction\n\nBlock messages automatically redact sensitive data (tokens, passwords, API keys) to prevent leaking secrets in logs.\n\n### Audit Logging\n\nAll blocked commands are logged to `~/.cc-safety-net/logs/\u003csession_id\u003e.jsonl` for audit purposes:\n\n```json\n{\"ts\": \"2025-01-15T10:30:00Z\", \"command\": \"git reset --hard\", \"segment\": \"git reset --hard\", \"reason\": \"...\", \"cwd\": \"/path/to/project\"}\n```\n\nSensitive data in log entries is automatically redacted.\n\n## License\n\nMIT\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fkenryu42%2Fclaude-code-safety-net","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fkenryu42%2Fclaude-code-safety-net","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fkenryu42%2Fclaude-code-safety-net/lists"}