{"id":46912561,"url":"https://github.com/camjac251/tool-gates","last_synced_at":"2026-04-30T03:02:22.902Z","repository":{"id":328739280,"uuid":"1116518235","full_name":"camjac251/tool-gates","owner":"camjac251","description":"Intelligent tool permission gate for AI coding assistants. Bash AST parsing, file guards, configurable tool blocking","archived":false,"fork":false,"pushed_at":"2026-04-21T20:29:25.000Z","size":1343,"stargazers_count":3,"open_issues_count":3,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-04-21T22:30:00.991Z","etag":null,"topics":["ai-tools","claude-code","cli","gemini-cli","hooks","permissions","rust","security","tree-sitter"],"latest_commit_sha":null,"homepage":"","language":"Rust","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/camjac251.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":null,"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}},"created_at":"2025-12-15T01:53:24.000Z","updated_at":"2026-04-21T20:29:15.000Z","dependencies_parsed_at":"2026-01-23T21:00:28.690Z","dependency_job_id":"78ea428f-7316-4036-8c53-4ffa93f42467","html_url":"https://github.com/camjac251/tool-gates","commit_stats":null,"previous_names":["camjac251/bash-gates","camjac251/tool-gates"],"tags_count":104,"template":false,"template_full_name":null,"purl":"pkg:github/camjac251/tool-gates","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/camjac251%2Ftool-gates","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/camjac251%2Ftool-gates/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/camjac251%2Ftool-gates/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/camjac251%2Ftool-gates/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/camjac251","download_url":"https://codeload.github.com/camjac251/tool-gates/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/camjac251%2Ftool-gates/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":32209895,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-04-24T03:15:14.334Z","status":"ssl_error","status_checked_at":"2026-04-24T03:15:11.608Z","response_time":64,"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":["ai-tools","claude-code","cli","gemini-cli","hooks","permissions","rust","security","tree-sitter"],"created_at":"2026-03-11T02:15:02.911Z","updated_at":"2026-04-30T03:02:22.891Z","avatar_url":"https://github.com/camjac251.png","language":"Rust","funding_links":[],"categories":[],"sub_categories":[],"readme":"\u003cdiv align=\"center\"\u003e\n\n# Tool Gates\n\n*formerly `bash-gates`*\n\n**Intelligent tool permission gate for AI coding assistants**\n\n[![CI](https://github.com/camjac251/tool-gates/actions/workflows/ci.yml/badge.svg)](https://github.com/camjac251/tool-gates/actions/workflows/ci.yml)\n[![Release](https://github.com/camjac251/tool-gates/actions/workflows/release.yml/badge.svg)](https://github.com/camjac251/tool-gates/actions/workflows/release.yml)\n[![Rust](https://img.shields.io/badge/rust-1.86+-orange.svg)](https://www.rust-lang.org/)\n[![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](LICENSE)\n\nA hook for [Claude Code](https://code.claude.com/docs/en/hooks) and [Gemini CLI](https://github.com/google-gemini/gemini-cli) that gates Bash commands, file operations, and tool invocations using AST parsing. Determines whether to allow, ask, or block based on potential impact.\n\n[Installation](#installation) · [Permission Gates](#permission-gates) · [Security](#security-features) · [Testing](#testing)\n\n\u003c/div\u003e\n\n---\n\n## Features\n\n| Feature                  | Description                                                                                            |\n| ------------------------ | ------------------------------------------------------------------------------------------------------ |\n| **Approval Learning**    | Tracks approved commands and saves patterns to settings.json via TUI or CLI                            |\n| **Settings Integration** | Respects your `settings.json` allow/deny/ask rules - won't bypass your explicit permissions            |\n| **Accept Edits Mode**    | Auto-allows file-editing commands (`sd`, `prettier --write`, etc.) when in acceptEdits mode            |\n| **Auto Mode Support**    | Integrates with Claude Code auto mode: deterministic deny floor for dangerous patterns, classifier retry hints |\n| **Modern CLI Hints**     | Suggests modern alternatives (`bat`, `rg`, `fd`, etc.) via `additionalContext` for Claude to learn     |\n| **AST Parsing**          | Uses [tree-sitter-bash](https://github.com/tree-sitter/tree-sitter-bash) for accurate command analysis |\n| **Compound Commands**    | Handles `\u0026\u0026`, `\\|\\|`, `\\|`, `;` chains correctly                                                       |\n| **Security First**       | Catches pipe-to-shell, eval, command injection patterns                                                |\n| **Unknown Protection**   | Unrecognized commands require approval                                                                 |\n| **Claude Code Plugin**   | Install as a plugin with the `/tool-gates:review` skill for interactive approval management            |\n| **400+ Commands**        | 13 specialized gates with comprehensive coverage                                                       |\n| **File Guards**          | Blocks symlinked AI config files (CLAUDE.md, .cursorrules, etc.) to prevent confused reads/edits       |\n| **Security Reminders**   | Scans Write/Edit content for 26 anti-patterns (secrets, XSS, injection, etc.) across 3 tiers |\n| **Head/Tail Pipe Block** | Denies `\\| head` / `\\| tail` pipes so stdout is capped at the source via `rg -m N` / `fd --max-results N` / `bat -r START:END` instead |\n| **Tool Blocking**        | Configurable rules to block tools (Glob, Grep, and firecrawl/ref/exa MCP calls to GitHub) with domain filtering |\n| **Skill Auto-Approval**  | Auto-approve Skill tool calls based on project directory conditions. No external hook scripts needed  |\n| **MCP Accept-Edits Approval** | Auto-approve named MCP tools when the session is in `acceptEdits` mode. Fills the gap Claude Code leaves open (MCP tools ignore permission mode natively) |\n| **Configuration**        | `~/.config/tool-gates/config.toml` for feature toggles, custom block rules, and file guard extensions  |\n| **Health Check**         | `tool-gates doctor` verifies config, hooks, cache files, and flags legacy remnants                     |\n| **Fast**                 | Static native binary, no interpreter overhead                                                          |\n\n---\n\n## How It Works\n\n```mermaid\nflowchart TD\n    CC[Claude Code] --\u003e TOOL{Tool Type}\n    TOOL --\u003e|Bash/Monitor| CMD[Shell Command]\n    TOOL --\u003e|Write/Edit| FILE[File Operation]\n\n    subgraph PTU [PreToolUse Hook]\n        direction TB\n        PTU_CHECK[tool-gates check] --\u003e PTU_DEC{Decision}\n        PTU_DEC --\u003e|dangerous| PTU_DENY[deny]\n        PTU_DEC --\u003e|risky| PTU_ASK[ask + track]\n        PTU_DEC --\u003e|safe| PTU_CTX{Context?}\n        PTU_CTX --\u003e|main session| PTU_ALLOW[allow ✓]\n        PTU_CTX --\u003e|subagent| PTU_IGNORED[ignored by Claude]\n    end\n\n    subgraph PTU_FILE [PreToolUse - File Tools]\n        direction TB\n        FG[Symlink guard] --\u003e FG_DEC{Symlink?}\n        FG_DEC --\u003e|guarded symlink| FG_DENY[deny - use real path]\n        FG_DEC --\u003e|ok| SEC{Content scan}\n        SEC --\u003e|hardcoded secret| SEC_DENY[deny - Tier 1]\n        SEC --\u003e|safe| SEC_PASS[pass through]\n    end\n\n    CMD --\u003e PTU\n    FILE --\u003e PTU_FILE\n\n    PTU_IGNORED --\u003e INTERNAL[Claude internal checks]\n    INTERNAL --\u003e|path outside cwd| PR_HOOK\n\n    subgraph PR_HOOK [PermissionRequest Hook]\n        direction TB\n        PR_CHECK[tool-gates re-check] --\u003e PR_DEC{Decision}\n        PR_DEC --\u003e|safe| PR_ALLOW[allow ✓]\n        PR_DEC --\u003e|dangerous| PR_DENY[deny]\n        PR_DEC --\u003e|risky| PR_PROMPT[show prompt]\n    end\n\n    PTU_ASK --\u003e EXEC[Command Executes]\n    PR_PROMPT --\u003e USER_APPROVE[User Approves] --\u003e EXEC\n    SEC_PASS --\u003e FILE_EXEC[Write Succeeds]\n\n    subgraph POST [PostToolUse Hook]\n        direction TB\n        POST_CHECK[check tracking] --\u003e POST_DEC{Tracked + Success?}\n        POST_DEC --\u003e|yes| PENDING[add to pending queue]\n        POST_DEC --\u003e|no| POST_SKIP[skip]\n        POST_SEC[Security scan] --\u003e POST_SEC_DEC{Anti-pattern?}\n        POST_SEC_DEC --\u003e|yes| NUDGE[inject reminder]\n        POST_SEC_DEC --\u003e|no| POST_SKIP\n    end\n\n    EXEC --\u003e POST\n    FILE_EXEC --\u003e POST_SEC\n    PENDING --\u003e REVIEW[tool-gates review]\n    REVIEW --\u003e SETTINGS[settings.json]\n```\n\n**Why four hooks? (Claude Code)**\n\n- **PreToolUse**: Gates Bash/Monitor commands, blocks secrets in Write/Edit, provides CLI hints\n- **PermissionRequest**: Gates commands for subagents (where PreToolUse's `allow` is ignored)\n- **PermissionDenied**: Fires when the auto-mode classifier denies. If tool-gates would allow the same command, emits a `retry: true` hint so the model gets a second shot\n- **PostToolUse**: Tracks successful Bash/Monitor execution for approval learning; scans Write/Edit content for security anti-patterns and nudges Claude via `additionalContext`\n\n**Gemini CLI** uses two hooks (`BeforeTool`/`AfterTool`) with the same gate engine. The client is auto-detected from `hook_event_name`. Key differences:\n\n- No `PermissionRequest` (Gemini doesn't have subagent permission hooks)\n- No approval tracking (Gemini doesn't provide `tool_use_id`)\n- `\"block\"` instead of `\"deny\"` in output, exit code 2 for blocks\n- Security anti-pattern scanning in AfterTool is not yet supported\n\n\u003e `PermissionRequest` metadata like `blocked_path` and `decision_reason` is optional in Claude Code payloads. tool-gates treats those fields as best-effort context, not required inputs.\n\n**Decision Priority:** `BLOCK \u003e ASK \u003e ALLOW \u003e SKIP`\n\n| Decision  | Wire output                            | Effect                                                                                  |\n| :-------: | -------------------------------------- | --------------------------------------------------------------------------------------- |\n| **deny**  | `permissionDecision: \"deny\"`           | Command blocked with reason                                                             |\n|  **ask**  | `permissionDecision: \"ask\"`            | User prompted (Yes / No, two buttons). Used for hard-deny adjacent patterns and explicit `permissions.ask` matches |\n| **defer** | `permissionDecision` omitted           | Claude Code's resolver runs the tool's own permission check, populating the prefix-suggestion that lights up the third \"Yes, and don't ask again for X\" button. Used for benign gate-engine asks |\n| **allow** | `permissionDecision: \"allow\"`          | Auto-approved                                                                           |\n\n\u003e Unknown commands always require approval. Whether they get the two-button or three-button prompt depends on whether your `permissions.ask` rules in settings.json match -- run `tool-gates rules ask-audit` to surface ask rules that suppress the third button.\n\n### Settings.json Integration\n\ntool-gates reads your Claude Code settings from `~/.claude/settings.json` and `.claude/settings.json` (project) to respect your explicit permission rules:\n\n| settings.json | tool-gates | Result                                                              |\n| ------------- | ---------- | ------------------------------------------------------------------- |\n| `deny` rule   | (any)      | **deny** (respects your explicit deny)                              |\n| `ask` rule    | (any)      | **ask** (respects your explicit ask; two-button prompt)             |\n| `allow` rule  | dangerous  | **deny** (tool-gates still blocks dangerous)                        |\n| `allow`/none  | safe       | **allow**                                                           |\n| none          | unknown    | **defer** (three-button prompt restored via Claude Code's resolver) |\n\nThis ensures tool-gates won't accidentally bypass your explicit deny rules while still providing security against dangerous commands.\n\n**Settings file priority** (highest wins):\n\n| Priority    | Location                                 | Description                   |\n| ----------- | ---------------------------------------- | ----------------------------- |\n| 1 (highest) | `/etc/claude-code/managed-settings.json` | Enterprise managed            |\n| 2           | `.claude/settings.local.json`            | Local project (not committed) |\n| 3           | `.claude/settings.json`                  | Shared project (committed)    |\n| 4 (lowest)  | `~/.claude/settings.json`                | User settings                 |\n\n### Accept Edits Mode\n\nWhen Claude Code is in `acceptEdits` mode, tool-gates auto-allows file-editing commands:\n\n```bash\n# In acceptEdits mode - auto-allowed\nsd 'old' 'new' file.txt           # Text replacement\nprettier --write src/             # Code formatting\nast-grep -p 'old' -r 'new' -U .   # Code refactoring\nsed -i 's/foo/bar/g' file.txt     # In-place sed\nblack src/                        # Python formatting\neslint --fix src/                 # Linting with fix\n```\n\n**Still requires approval (even in acceptEdits):**\n\n- Package managers: `npm install`, `cargo add`\n- Git operations: `git push`, `git commit`\n- Deletions: `rm`, `mv`\n- Blocked commands: `rm -rf /` still denied\n\n**Extending acceptEdits to MCP tools.** Claude Code's `acceptEdits` mode does not extend to MCP tools natively -- every MCP tool's internal permission check returns passthrough regardless of mode. tool-gates fills the gap: declare `[[accept_edits_mcp]]` rules in `config.toml` and the named MCP tools auto-allow only when the session is in `acceptEdits`. See the [MCP Accept-Edits Approval](#mcp-accept-edits-approval) configuration section below.\n\n### Auto Mode\n\n_Requires Claude Code 2.1.89+ for the `PermissionDenied` retry hook. Earlier auto-mode-capable builds still get the deny-promotion, pattern narrowing, and pending queue guard._\n\nWhen Claude Code runs in `auto` permission mode, a server-side classifier decides `ask` calls instead of prompting. tool-gates layers in as a deterministic pre-filter and safety floor:\n\n| tool-gates decision | Behavior under auto mode |\n|---------------------|--------------------------|\n| `allow` (e.g. `git status`, `cargo check`) | Classifier skipped, action executes |\n| `ask` (e.g. `cargo install foo`) | Classifier runs, decides allow/deny |\n| `deny` (e.g. `rm -rf /`, `\\| bash`) | Hard floor, classifier bypassed |\n\n**What changes under auto mode:**\n\n- **Pipe-to-shell and `eval` escalate from ask to deny.** These patterns have no legitimate use case, so they stay in the deterministic floor rather than routing to the classifier.\n- **Pending queue only tracks human approvals.** Under auto mode the classifier decides silently, so nothing goes into `pending.jsonl` -- the review queue stays focused on patterns you explicitly approved.\n- **Classifier denials get retry hints.** If the classifier denies a command tool-gates would allow (e.g. `cargo check`), the `PermissionDenied` hook tells the model it may retry.\n- **Skill auto-approval still fires.** `[[auto_approve_skills]]` rules are explicit trust declarations and aren't revoked by opting into auto mode.\n\nConfigure the Claude Code classifier via `autoMode.{environment,allow,soft_deny}` in settings.json. Inspect the merged config with `claude auto-mode config`.\n\n### Modern CLI Hints\n\n_Requires Claude Code 1.0.20+_\n\nWhen Claude uses legacy commands, tool-gates suggests modern alternatives via `additionalContext`. This helps Claude learn better patterns over time without modifying the command.\n\n```bash\n# Claude runs: cat README.md\n# tool-gates returns (Claude format):\n{\n  \"hookSpecificOutput\": {\n    \"permissionDecision\": \"allow\",\n    \"additionalContext\": \"Tip: Use 'bat README.md' for syntax highlighting and line numbers (Markdown rendering)\"\n  }\n}\n\n# Gemini format (auto-detected):\n{\"decision\":\"allow\",\"hookSpecificOutput\":{\"additionalContext\":\"Tip: Use 'bat README.md' ...\"}}\n```\n\n| Legacy Command                | Modern Alternative | When triggered                       |\n| ----------------------------- | ------------------ | ------------------------------------ |\n| `cat`, `head`, `tail`, `less` | `bat`              | Always (`tail -f` excluded)          |\n| `grep` (code patterns)        | `sg`               | AST-aware code search                |\n| `grep` (text/log/config)      | `rg`               | Any grep usage                       |\n| `find`                        | `fd`               | Always                               |\n| `ls`                          | `eza`              | With `-l` or `-a` flags              |\n| `sed`                         | `sd`               | Substitution patterns (`s/.../.../`) |\n| `awk`                         | `choose`           | Field extraction (`print $`)         |\n| `du`                          | `dust`             | Always                               |\n| `ps`                          | `procs`            | With `aux`, `-e`, `-A` flags         |\n| `curl`, `wget`                | `xh`               | JSON APIs or verbose mode            |\n| `curl`, `wget`, `xh`          | `gh`               | GitHub content URLs (raw/api/blob/gist) |\n| `diff`                        | `delta`            | Two-file comparisons                 |\n| `xxd`, `hexdump`              | `hexyl`            | Always                               |\n| `cloc`                        | `tokei`            | Always                               |\n| `tree`                        | `eza -T`           | Always                               |\n| `man`                         | `tldr`             | Always                               |\n| `wc -l`                       | `rg -c`            | Line counting                        |\n\n**Only suggests installed tools.** Hints are cached (7-day TTL) to avoid repeated `which` calls.\n\n```bash\n# Refresh tool detection cache\ntool-gates --refresh-tools\n\n# Check which tools are detected\ntool-gates --tools-status\n```\n\n### Security Reminders\n\nWhen Claude writes code via Write/Edit, tool-gates scans the content for 26 security anti-patterns organized into three tiers:\n\n| Tier | Hook | Decision | Behavior |\n|------|------|----------|----------|\n| **Tier 1** | PreToolUse | `deny` | Hardcoded secrets always blocked before write |\n| **Tier 2** | PostToolUse | `additionalContext` | Anti-patterns flagged after write. Claude gets a nudge to fix |\n| **Tier 3** | PreToolUse | `allow` + context | Informational warnings injected without blocking |\n\n**Tier 1: Secrets (always denied):**\nAWS access keys (`AKIA...`), private keys (`-----BEGIN * PRIVATE KEY`), GitHub tokens (`ghp_/ghs_/ghu_/gho_/ghr_`), Stripe/Slack/Google API keys, GitHub Actions workflow injection.\n\n**Tier 2: Anti-patterns (post-write nudge, once per file+rule per session):**\n`eval()`, `child_process.exec`, `new Function()`, `os.system()`, `pickle.load`, `dangerouslySetInnerHTML`, `document.write()`, `.innerHTML =`, `yaml.load()` without SafeLoader, SQL f-string interpolation, `subprocess` with `shell=True`, `render_template_string()` (Flask SSTI), `marshal.load`/`shelve.open`, `__import__()`, PHP `unserialize()`.\n\n**Tier 3: Informational (allow with warning, once per session):**\nSSL `verify=False`, `chmod 777`, MD5/SHA1 for security, CORS wildcard `*`, Vue `v-html=`, template `autoescape=False`.\n\n**Why Tier 2 uses PostToolUse:** The write lands without blocking. Claude sees a `\u003csystem-reminder\u003e` with the security warning and can self-correct in its next action. No wasted edits from re-prompting. Deduped per (file, rule) per session so you only see each warning once.\n\nSkips documentation files (.md, .txt, .rst, etc.) for content checks. Tier 1 secret scans always fire.\n\n```toml\n# ~/.config/tool-gates/config.toml\n[features]\nsecurity_reminders = true  # default\n\n[security_reminders]\ndisable_rules = [\"eval_injection\"]  # skip specific rules\n```\n\n### Approval Learning\n\nWhen you approve commands (via Claude Code's permission prompt), tool-gates tracks them and lets you permanently save patterns to settings.json.\n\n```bash\n# After approving some commands, review pending approvals\ntool-gates pending list\n\n# Interactive TUI dashboard\ntool-gates review          # current project only\ntool-gates review --all    # all projects\n\n# Or approve directly via CLI\ntool-gates approve 'npm install*' -s local\ntool-gates approve 'cargo*' -s user\n\n# Manage existing rules\ntool-gates rules list\ntool-gates rules remove 'pattern' -s local\n\n# Audit `permissions.ask` rules that suppress the third prompt button\ntool-gates rules ask-audit            # categorized listing\ntool-gates rules ask-audit --apply    # multi-select TUI checklist\n```\n\n**Why ask-audit?** Whenever a `permissions.ask` rule in settings.json matches a command, Claude Code's resolver shows two buttons (Yes / No) instead of three. The third \"Yes, and don't ask again for X\" button is suppressed because the resolver returns ask without populating the prefix-suggestion. `ask-audit` categorizes each rule by what tool-gates would do without it (gate-covered, safety floor, indeterminate) and offers per-rule removal.\n\n**Scopes:**\n| Scope | File | Use case |\n|-------|------|----------|\n| `local` | `.claude/settings.local.json` | Personal project overrides (not committed) |\n| `user` | `~/.claude/settings.json` | Global personal use |\n| `project` | `.claude/settings.json` | Share with team |\n\n**Review TUI** (`tool-gates review`):\n\nThree-panel dashboard with project sidebar, command list, and detail panel.\n\n- **Sidebar**: Lists projects with pending counts, auto-selects current project. Click or arrow to switch.\n- **Command list**: Full commands with color-coded segments (green=allowed, yellow=ask, red=blocked). Multi-select with Space for batch operations.\n- **Detail panel**: Shows segment breakdown, pattern (cycle with Left/Right), scope (cycle with Left/Right), and action buttons.\n\nCompound commands (`\u0026\u0026`, `||`, `|`) show per-segment patterns so you can approve individual parts.\n\n| Key | Action |\n| --- | ------ |\n| `Tab` | Cycle panel focus (Sidebar -\u003e Commands -\u003e Detail) |\n| `Up`/`Down` or `j`/`k` | Navigate within focused panel |\n| `Left`/`Right` or `h`/`l` | Cycle pattern or scope (in detail panel) |\n| `Space` | Toggle multi-select on command |\n| `Enter` | Approve selected command(s) |\n| `d` | Skip (remove from pending) |\n| `D` | Deny (add to settings.json deny list) |\n| `q` or `Esc` | Quit |\n\n---\n\n## Installation\n\n### Homebrew (Recommended)\n\n```bash\nbrew install camjac251/tap/tool-gates\n```\n\nUpgrades work normally after the initial install:\n\n```bash\nbrew upgrade tool-gates\n```\n\nBottles are built for macOS (arm64, x86_64) and Linux (arm64, x86_64). Formulas are updated automatically when new releases are published.\n\n### Download Binary\n\n```bash\n# Linux x64\ncurl -Lo ~/.local/bin/tool-gates \\\n  https://github.com/camjac251/tool-gates/releases/latest/download/tool-gates-linux-amd64\nchmod +x ~/.local/bin/tool-gates\n\n# Linux ARM64\ncurl -Lo ~/.local/bin/tool-gates \\\n  https://github.com/camjac251/tool-gates/releases/latest/download/tool-gates-linux-arm64\nchmod +x ~/.local/bin/tool-gates\n\n# macOS Apple Silicon\ncurl -Lo ~/.local/bin/tool-gates \\\n  https://github.com/camjac251/tool-gates/releases/latest/download/tool-gates-darwin-arm64\nchmod +x ~/.local/bin/tool-gates\n\n# macOS Intel\ncurl -Lo ~/.local/bin/tool-gates \\\n  https://github.com/camjac251/tool-gates/releases/latest/download/tool-gates-darwin-amd64\nchmod +x ~/.local/bin/tool-gates\n```\n\n### Build from Source\n\n```bash\n# Requires Rust 1.85+\ncargo build --release\n# Binary: ./target/x86_64-unknown-linux-musl/release/tool-gates\n```\n\n### Configure\n\n```bash\n# Claude Code (recommended)\ntool-gates hooks add -s user\n\n# Gemini CLI\ntool-gates hooks add --gemini\n\n# Install to project settings (shared with team)\ntool-gates hooks add -s project\n\n# Check installation status (both clients)\ntool-gates hooks status\n\n# Preview changes without writing\ntool-gates hooks add -s user --dry-run\ntool-gates hooks add --gemini --dry-run\n```\n\n#### Claude Code\n\n| Scope | File | Use case |\n|-------|------|----------|\n| `user` | `~/.claude/settings.json` | Personal use (recommended) |\n| `project` | `.claude/settings.json` | Share with team |\n| `local` | `.claude/settings.local.json` | Personal project overrides |\n\n**All four hooks are installed:**\n\n- `PreToolUse` - Gates Bash/Monitor commands, blocks secrets in Write/Edit, file guards, CLI hints, MCP tool blocking, Skill auto-approval\n- `PermissionRequest` - Gates commands for subagents (where PreToolUse's allow is ignored)\n- `PermissionDenied` - Emits retry hints when the auto-mode classifier denies a command tool-gates would allow\n- `PostToolUse` - Tracks Bash/Monitor execution for approval learning; scans Write/Edit for security anti-patterns\n\n\u003cdetails\u003e\n\u003csummary\u003eManual installation\u003c/summary\u003e\n\nAdd to `~/.claude/settings.json`:\n\n```json\n{\n  \"hooks\": {\n    \"PreToolUse\": [\n      {\n        \"matcher\": \"Bash|Monitor|Read|Write|Edit|Glob|Grep|Skill\",\n        \"hooks\": [{ \"type\": \"command\", \"command\": \"~/.local/bin/tool-gates\", \"timeout\": 10 }]\n      },\n      {\n        \"matcher\": \"mcp__.*\",\n        \"hooks\": [{ \"type\": \"command\", \"command\": \"~/.local/bin/tool-gates\", \"timeout\": 10 }]\n      }\n    ],\n    \"PermissionRequest\": [\n      {\n        \"matcher\": \"Bash|Monitor|Write|Edit\",\n        \"hooks\": [{ \"type\": \"command\", \"command\": \"~/.local/bin/tool-gates\", \"timeout\": 10 }]\n      }\n    ],\n    \"PermissionDenied\": [\n      {\n        \"matcher\": \"Bash|Monitor\",\n        \"hooks\": [{ \"type\": \"command\", \"command\": \"~/.local/bin/tool-gates\", \"timeout\": 10 }]\n      }\n    ],\n    \"PostToolUse\": [\n      {\n        \"matcher\": \"Bash|Monitor|Write|Edit\",\n        \"hooks\": [{ \"type\": \"command\", \"command\": \"~/.local/bin/tool-gates\", \"timeout\": 10 }]\n      }\n    ]\n  }\n}\n```\n\n\u003c/details\u003e\n\n### Claude Code Plugin (Optional)\n\ntool-gates ships as a [Claude Code plugin](https://code.claude.com/docs/en/plugins) with the `/tool-gates:review` skill for interactive approval management. The plugin provides the skill only. Hook installation is handled by the binary (see [Configure Claude Code](#configure-claude-code) above).\n\n**Prerequisites:** The `tool-gates` binary must be installed and hooks configured before using the plugin.\n\n**Install from marketplace:**\n\n```bash\n# In Claude Code, add the marketplace\n/plugin marketplace add camjac251/tool-gates\n\n# Install the plugin\n/plugin install tool-gates@camjac251-tool-gates\n```\n\n**Install from local clone:**\n\n```bash\n# Launch Claude Code with the plugin loaded\nclaude --plugin-dir /path/to/tool-gates/claude-plugin\n```\n\n**Using the review skill:**\n\n```bash\n# Review all pending approvals\n/tool-gates:review\n\n# Review only current project\n/tool-gates:review --project\n```\n\nThe skill lists commands you've been manually approving, shows counts and suggested patterns, and lets you multi-select which to make permanent at your chosen scope (local, project, or user).\n\n| Step                   | What happens                                | Permission                 |\n| ---------------------- | ------------------------------------------- | -------------------------- |\n| List pending approvals | `tool-gates pending list`                   | Auto-approved (read-only)  |\n| Show current rules     | `tool-gates rules list`                     | Auto-approved (read-only)  |\n| Approve a pattern      | `tool-gates approve '\u003cpattern\u003e' -s \u003cscope\u003e` | Requires your confirmation |\n\n#### Gemini CLI\n\nRequires Gemini CLI **v0.36.0+** (`ask` decision support for BeforeTool hooks).\n\n| Scope | File | Use case |\n|-------|------|----------|\n| `user` | `~/.gemini/settings.json` | Personal use (default) |\n| `project` | `.gemini/settings.json` | Share with team |\n\nTwo hooks are installed: `BeforeTool`, `AfterTool`\n\n\u003cdetails\u003e\n\u003csummary\u003eManual installation\u003c/summary\u003e\n\nAdd to `~/.gemini/settings.json`:\n\n```json\n{\n  \"hooks\": {\n    \"BeforeTool\": [\n      {\n        \"matcher\": \"run_shell_command|read_file|read_many_files|write_file|replace|glob|grep_search|activate_skill\",\n        \"hooks\": [{\"type\": \"command\", \"command\": \"~/.local/bin/tool-gates\", \"timeout\": 5000}]\n      },\n      {\n        \"matcher\": \"mcp_.*\",\n        \"hooks\": [{\"type\": \"command\", \"command\": \"~/.local/bin/tool-gates\", \"timeout\": 5000}]\n      }\n    ],\n    \"AfterTool\": [\n      {\n        \"matcher\": \"run_shell_command|write_file|replace\",\n        \"hooks\": [{\"type\": \"command\", \"command\": \"~/.local/bin/tool-gates\", \"timeout\": 5000}]\n      }\n    ]\n  }\n}\n```\n\n\u003c/details\u003e\n\nThe client is auto-detected from the `hook_event_name` field. No configuration needed. The same binary handles both.\n\n---\n\n## Permission Gates\n\n### Tool Gates (Self)\n\ntool-gates recognizes its own CLI commands:\n\n| Allow                                                                                                                   | Ask                                                                                  |\n| ----------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------ |\n| `pending list`, `pending count`, `rules list`, `rules ask-audit`, `hooks status`, `--help`, `--version`, `--tools-status` | `approve`, `rules remove`, `rules ask-audit --apply`, `pending clear`, `hooks add`, `review`, `--refresh-tools` |\n\n### Basics\n\n~180 safe read-only commands: `echo`, `cat`, `ls`, `grep`, `rg`, `awk`, `sed` (no -i), `ps`, `whoami`, `date`, `jq`, `yq`, `bat`, `fd`, `tokei`, `hexdump`, `glow`, `jc`, `mktemp`, and more. Custom handlers for `xargs` (safe only with known-safe targets) and `bash -c`/`sh -c` (parses inner script).\n\n### Beads Issue Tracker\n\n[Beads](https://github.com/steveyegge/beads) - Git-native issue tracking\n\n| Allow                                                                                | Ask                                                                              |\n| ------------------------------------------------------------------------------------ | -------------------------------------------------------------------------------- |\n| `list`, `show`, `ready`, `blocked`, `search`, `stats`, `doctor`, `dep tree`, `prime` | `create`, `update`, `close`, `delete`, `sync`, `init`, `dep add`, `comments add` |\n\n### GitHub CLI\n\n| Allow                                                       | Ask                                                  | Block                        |\n| ----------------------------------------------------------- | ---------------------------------------------------- | ---------------------------- |\n| `pr list`, `issue view`, `repo view`, `search`, `api` (GET) | `pr create`, `pr merge`, `issue create`, `repo fork` | `repo delete`, `auth logout` |\n\n### Git\n\n| Allow                                        | Ask                                      | Ask (warning)                               |\n| -------------------------------------------- | ---------------------------------------- | ------------------------------------------- |\n| `status`, `log`, `diff`, `show`, `branch -a` | `add`, `commit`, `push`, `pull`, `merge` | `push --force`, `reset --hard`, `clean -fd` |\n\n### Shortcut CLI\n\n[shortcut-cli](https://github.com/shortcut-cli/shortcut-cli) - Community CLI for Shortcut\n\n| Allow                                                                                 | Ask                                                                                        |\n| ------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------ |\n| `search`, `find`, `story` (view), `members`, `epics`, `workflows`, `projects`, `help` | `create`, `install`, `story` (with update flags), `search --save`, `api` (POST/PUT/DELETE) |\n\n### Cloud CLIs\n\nAWS, gcloud, terraform, kubectl, docker, podman, az, helm, pulumi\n\n| Allow                                         | Ask                                        | Block                                      |\n| --------------------------------------------- | ------------------------------------------ | ------------------------------------------ |\n| `describe-*`, `list-*`, `get`, `show`, `plan` | `create`, `delete`, `apply`, `run`, `exec` | `iam delete-user`, `delete ns kube-system` |\n\nDocker extended: `buildx ls/inspect` allow, `buildx build/prune` ask. `scout quickview/cves` allow. `context/manifest/image/container` read subcommands allow, mutations ask.\n\nkubectl: `diff`, `kustomize`, `wait` allow. `debug` asks. terraform: `workspace show` allow. `test`, `console`, `force-unlock` ask.\n\n### Network\n\n| Allow                         | Ask                                                  | Block                                    |\n| ----------------------------- | ---------------------------------------------------- | ---------------------------------------- |\n| `curl` (GET non-GitHub), `wget --spider` | `curl -X POST`, `wget`, `ssh`, `rsync`, `nmap`, `socat`, `telnet`, `curl/xh` against GitHub content URLs | `nc -e/-c/--exec` (reverse shell) |\n\n### Filesystem\n\n| Allow                 | Ask                                 | Block                  |\n| --------------------- | ----------------------------------- | ---------------------- |\n| `tar -tf`, `unzip -l` | `rm`, `mv`, `cp`, `chmod`, `sed -i` | `rm -rf /`, `rm -rf ~` |\n\n### Language Runtimes\n\npython3/python, node, ruby, deno, php, lua/luajit, java/javac, dotnet, swift, elixir/iex\n\n| Allow                                                     | Ask                                                |\n| --------------------------------------------------------- | -------------------------------------------------- |\n| `--version`, `--help`, syntax check (`node -c`, `ruby -c`, `php -l`) | `-c`/`-e`/`-m` (code execution), running scripts  |\n\ndeno: `check`, `lint`, `test`, `fmt --check` allow. `run`, `fmt`, `install`, `publish` ask. dotnet: `build`, `test`, `run` allow. `publish`, `new`, `add` ask.\n\n### Developer Tools\n\n~77 tools with write-flag detection.\n\n**Linters/type checkers (read-only, always allow):** `eslint`, `biome`, `ruff`, `pylint`, `flake8`, `mypy`, `pyright`, `bandit`, `shellcheck`, `hadolint`, `golangci-lint`, `oxlint`, `stylelint`\n\n**Test runners (allow):** `jest`, `vitest`, `mocha`, `pytest`, `playwright test`, `cypress run`\n\n**Formatters (allow with check flags, ask with write flags):** `prettier`, `black`, `isort`, `ruff format`, `biome format`, `gofmt`, `rustfmt`, `shfmt`, `autopep8`, `clang-format`\n\n**Build tools (allow):** `vite`, `esbuild`, `tsup`, `turbo`, `nx`, `webpack`, `rollup`, `swc`, `tsc`\n\n**Code execution (ask):** `tsx`, `ts-node`, `watchexec`, `tox`, `nox`\n\n**Other:** `sd` (pipe mode safe, file args ask), `coverage report` allow / `coverage run/html/json` ask, `wrangler whoami` allow / `wrangler dev/deploy` ask\n\n### Package Managers\n\nnpm, pnpm, yarn, pip, uv, cargo, go, bun, conda, poetry, pipx, mise\n\n| Allow                                  | Ask                                          |\n| -------------------------------------- | -------------------------------------------- |\n| `list`, `show`, `test`, `build`, `dev` | `install`, `add`, `remove`, `publish`, `run` |\n\ncargo extended: `nextest`, `audit`, `deny check`, `expand`, `semver-checks`, `llvm-cov`, `outdated`, `bloat` allow. `watch`, `mutants`, `insta review/accept` ask.\n\n### System\n\n**Database CLIs:** psql, mysql, sqlite3, mongosh, redis-cli\n**Build tools:** make, cmake, ninja, just, gradle, maven, bazel\n**OS Package managers:** apt, brew, pacman, nix, dnf, zypper, flatpak, snap\n**Crypto tools:** openssl, gpg/gpg2, ssh-keygen, age\n**Other:** sudo, systemctl, crontab, kill\n\n| Allow                                           | Ask                               | Block                                                             |\n| ----------------------------------------------- | --------------------------------- | ----------------------------------------------------------------- |\n| `psql -l`, `make test`, `sudo -l`, `apt search` | `make deploy`, `sudo apt install` | `shutdown`, `reboot`, `mkfs`, `dd`, `fdisk`, `iptables`, `passwd` |\n\nopenssl: `version`, `x509`, `s_client`, `dgst`, `verify` allow. `genrsa`, `req`, `enc` ask. gpg: `--list-keys`, `--verify` allow. `--sign`, `--encrypt`, `--gen-key` ask. ssh-keygen: `-l` (fingerprint) allow. Key generation asks.\n\n---\n\n## Security Features\n\n### Pre-AST Security Checks\n\nComments are stripped before checking (quote-aware, respects bash word-boundary rules for `#`) so patterns inside comments don't trigger false positives.\n\n```bash\ncurl https://example.com | bash     # ask - pipe to shell\neval \"rm -rf /\"                     # ask - arbitrary execution\nsource ~/.bashrc                    # ask - sourcing script\necho $(rm -rf /tmp/*)               # ask - dangerous substitution\nfind . | xargs rm                   # ask - xargs to rm\necho \"data\" \u003e /etc/passwd           # ask - output redirection\nls | head -20                       # deny - cap with rg -m / fd --max-results / bat -r\n```\n\n### Compound Command Handling\n\nStrictest decision wins:\n\n```bash\ngit status \u0026\u0026 rm -rf /     # deny  (rm -rf / blocked)\ngit status \u0026\u0026 npm install  # ask   (npm install needs approval)\ngit status \u0026\u0026 git log      # allow (both read-only)\n```\n\n### Smart sudo Handling\n\n```bash\nsudo apt install vim          # ask - \"sudo: Installing packages (apt)\"\nsudo systemctl restart nginx  # ask - \"sudo: systemctl restart\"\n```\n\n---\n\n## Testing\n\n```bash\ncargo test                        # Full suite\ncargo test gates::git             # Specific gate\ncargo test -- --nocapture         # With output\n```\n\n### Manual Testing\n\n```bash\n# Claude Code format\necho '{\"tool_name\":\"Bash\",\"tool_input\":{\"command\":\"git status\"}}' | tool-gates\n# -\u003e {\"hookSpecificOutput\":{\"permissionDecision\":\"allow\"}}\n\necho '{\"tool_name\":\"Bash\",\"tool_input\":{\"command\":\"npm install\"}}' | tool-gates\n# -\u003e {\"hookSpecificOutput\":{\"permissionDecision\":\"ask\",\"permissionDecisionReason\":\"npm: Installing packages\"}}\n\necho '{\"tool_name\":\"Bash\",\"tool_input\":{\"command\":\"rm -rf /\"}}' | tool-gates\n# -\u003e {\"hookSpecificOutput\":{\"permissionDecision\":\"deny\"}}\n\n# Gemini CLI format (auto-detected from hook_event_name)\necho '{\"hook_event_name\":\"BeforeTool\",\"tool_name\":\"run_shell_command\",\"tool_input\":{\"command\":\"git status\"}}' | tool-gates\n# -\u003e {\"decision\":\"allow\",\"reason\":\"Read-only operation\"}\n\necho '{\"hook_event_name\":\"BeforeTool\",\"tool_name\":\"run_shell_command\",\"tool_input\":{\"command\":\"rm -rf /\"}}' | tool-gates\n# -\u003e {\"decision\":\"block\",\"reason\":\"rm: rm -rf / blocked\"}  (exit code 2)\n```\n\n---\n\n## Configuration\n\nAll configuration is in `~/.config/tool-gates/config.toml`. The file is optional. If missing, all features are enabled with sensible defaults.\n\n### Feature Toggles\n\n```toml\n[features]\nbash_gates = true            # AST-based Bash command gating (default: true)\nfile_guards = true           # Symlink guard for AI config files (default: true)\nhints = true                 # Modern CLI hints, e.g. cat-\u003ebat, grep-\u003erg, etc. (default: true)\nsecurity_reminders = true    # Scan Write/Edit for security anti-patterns (default: true)\nhead_tail_pipe_block = true  # Deny `| head -N` / `| tail -N` pipes (default: true)\n```\n\n### Head/Tail Pipe Block\n\n`head_tail_pipe_block` denies `| head` and `| tail` pipes so the agent caps output at the source with native limits like `rg -m N`, `fd --max-results N`, and `bat -r START:END` instead of truncating stdout after the fact.\n\nCarve-outs that do not trigger:\n\n- **Streaming** `| tail -f` / `| tail -F` (the Monitor tool's log-watching idiom)\n- **Stderr-combining** `|\u0026 tail -f` / `|\u0026 tail -F`\n- **Quoted literals** like `rg '| head' file.txt` where `| head` is a search pattern, not a shell pipe\n- **No upstream pipe**, e.g. `head file.txt` or `tail -n 20 README.md`\n\nSet the toggle to `false` to disable.\n\n### Security Reminders\n\n```toml\n[security_reminders]\nsecrets = true         # Tier 1: hardcoded secrets, always deny (default: true)\nanti_patterns = true   # Tier 2: eval, exec, innerHTML, etc. PostToolUse nudge (default: true)\nwarnings = true        # Tier 3: SSL verify=False, chmod 777, etc. Informational (default: true)\ndisable_rules = [\"eval_injection\", \"pickle_deserialization\"]  # skip individual rules\n```\n\n\u003cdetails\u003e\n\u003csummary\u003eAll 26 rule names (click to expand)\u003c/summary\u003e\n\n| Tier | Rule Name | Detects |\n|:----:|-----------|---------|\n| 1 | `hardcoded_aws_key` | AWS access keys (`AKIA...`) |\n| 1 | `hardcoded_private_key` | RSA/EC/DSA/SSH private keys |\n| 1 | `hardcoded_github_token` | GitHub PATs (`ghp_`, `ghs_`, etc.) |\n| 1 | `hardcoded_generic_secret` | Stripe (`sk-`), Slack (`xoxb-`), Google (`AIza`) keys |\n| 1 | `github_actions_injection` | Untrusted input in GHA `run:` blocks |\n| 2 | `child_process_exec` | `child_process.exec` / `execSync` |\n| 2 | `new_function_injection` | `new Function()` code injection |\n| 2 | `eval_injection` | `eval()` arbitrary code execution |\n| 2 | `os_system_injection` | `os.system()` shell injection |\n| 2 | `pickle_deserialization` | `pickle.load` / `pickle.loads` |\n| 2 | `dangerous_inner_html` | React `dangerouslySetInnerHTML` |\n| 2 | `document_write_xss` | `document.write()` XSS |\n| 2 | `inner_html_assignment` | `.innerHTML =` XSS |\n| 2 | `unsafe_yaml_load` | `yaml.load()` without SafeLoader |\n| 2 | `sql_string_interpolation` | SQL via f-strings / `.execute(f\"...\")` |\n| 2 | `subprocess_shell_true` | `subprocess.run(..., shell=True)` |\n| 2 | `flask_ssti` | `render_template_string()` SSTI |\n| 2 | `marshal_deserialization` | `marshal.load` / `shelve.open` |\n| 2 | `python_dynamic_import` | `__import__()` injection |\n| 2 | `php_unserialize` | PHP `unserialize()` object injection |\n| 3 | `ssl_verification_disabled` | `verify=False` / `rejectUnauthorized: false` |\n| 3 | `chmod_777` | `chmod 777` / `0o777` overly permissive |\n| 3 | `weak_crypto_hash` | `hashlib.md5()` / `hashlib.sha1()` |\n| 3 | `cors_wildcard` | `Access-Control-Allow-Origin: *` |\n| 3 | `vue_v_html` | Vue `v-html=` XSS |\n| 3 | `template_autoescape_disabled` | Jinja2/Django `autoescape=False` |\n\n\u003c/details\u003e\n\n### Tool Blocking\n\n```toml\n# Override built-in block rules (Glob, Grep, and firecrawl/ref/exa blocked for GitHub).\n# Omit entirely to use defaults. Set to [] to disable all blocking.\n[[block_tools]]\ntool = \"Glob\"\nmessage = \"Use 'fd' instead of Glob.\"\nrequires_tool = \"fd\"   # only block if fd is installed\n\n[[block_tools]]\ntool = \"*firecrawl*\"\nmessage = \"Use 'gh api' for GitHub URLs.\"\nblock_domains = [\"github.com\", \"raw.githubusercontent.com\"]\nrequires_tool = \"gh\"\n```\n\n### File Guards\n\n```toml\n[file_guards]\nextra_names = [\".teamrules\"]       # additional filenames to protect from symlink attacks\nextra_dirs = [\".myide\"]            # additional directory names to protect\nextra_prefixes = [\".myrules-\"]     # additional filename prefixes\nextra_extensions = [\".toml\"]       # additional extensions in guarded dirs\n```\n\n### Hints\n\n```toml\n[hints]\ndisable = [\"man\", \"du\"]  # suppress hints for specific legacy commands\n```\n\n### Skill Auto-Approval\n\n```toml\n[[auto_approve_skills]]\nskill = \"my-plugin*\"                        # Glob pattern for skill name\nif_project_has = [\".my-plugin\"]             # Only approve if project dir contains this\n\n[[auto_approve_skills]]\nskill = \"deploy-tool\"                       # Exact match\nif_project_under = [\"~/projects/staging\"]   # Only approve if project is under this path\n```\n\nAuto-approve Skill tool calls based on configurable rules. Supports `~` expansion in paths. Replaces external Python/bash hooks. If no rules are configured, Skill calls pass through to Claude Code's normal flow.\n\n| Condition | Description |\n|-----------|-------------|\n| `if_project_has` | Project directory must contain one of these files/directories |\n| `if_project_under` | Project directory must be at or under one of these paths |\n| *(no conditions)* | Skill is auto-approved unconditionally |\n\n### MCP Accept-Edits Approval\n\n```toml\n[[accept_edits_mcp]]\ntool = \"mcp__serena__replace_symbol_body\"   # exact tool name\n\n[[accept_edits_mcp]]\ntool = \"mcp__serena__*\"                      # all tools on a server\nreason = \"Symbol edits batched through acceptEdits\"\n\n[[accept_edits_mcp]]\ntool = \"mcp__playwright__browser_click\"\nif_project_under = [\"~/projects/trusted\"]    # scope to a directory tree\n\n[[accept_edits_mcp]]\ntool = \"*firecrawl*\"                          # matches Claude (mcp__) and Gemini (mcp_) namespaces\nif_project_has = [\".firecrawl-ok\"]\n```\n\nAuto-approve MCP tool calls **only when the session is in `acceptEdits` mode**. In any other mode the rules are inert and the MCP tool falls through to whatever `permissions.allow` in `settings.json` decides. Directory conditions and glob matching are identical to `auto_approve_skills`.\n\n**Why this exists.** Claude Code's acceptEdits only natively auto-approves Edit/Write/NotebookEdit, Read (in allowed dirs), and a small fixed Bash set (`mkdir, touch, rm, rmdir, mv, cp, sed`). MCP tools ignore permission mode entirely -- their internal `checkPermissions` always returns passthrough, so acceptEdits gains them nothing. This config surface is the tool-gates extension: \"prompt me normally, batch me through in acceptEdits\" for named MCP tools.\n\n**Safety.** Block rules (e.g. the default firecrawl/ref/exa GitHub-URL blocks) run before these allow rules, so `[[accept_edits_mcp]]` cannot unlock a blocked tool.\n\n**Don't double-gate with `permissions.ask`.** Claude Code evaluates `settings.json` `permissions.ask` *after* the PreToolUse hook returns. If the MCP tool also matches an ask rule there, that rule overrides the hook's `allow` and the prompt shows anyway (logged as `Hook returned 'allow' for X, but ask rule/safety check requires full permission pipeline`). For `[[accept_edits_mcp]]` to actually auto-approve a tool, remove that tool from your `permissions.ask` list. If you want unconditional approval regardless of mode, put it in `permissions.allow` instead.\n\n**Substring-glob sharp edge.** `\"*serena*\"` is a pure substring match, so it will also catch unrelated servers whose name merely contains `serena` (e.g. `mcp__my-serenity__*`). For cross-namespace coverage of one specific server across Claude (`mcp__`) and Gemini (`mcp_`) prefixes, prefer pairing `mcp__serena*` with `mcp_serena*`.\n\n**Reason field asymmetry.** `reason` only surfaces on the main-thread PreToolUse path. On the subagent PermissionRequest path, the `allow` wire format has no reason slot (`PermissionRequestDecision::Allow` carries only `updatedInput` and `updatedPermissions`), so a custom reason is silently dropped there.\n\n| Condition | Description |\n|-----------|-------------|\n| `tool` | MCP tool name. Exact (`mcp__serena__find_symbol`), prefix (`mcp__serena*`), suffix (`*_scrape`), or contains (`*serena*` — pure substring match, see sharp-edge note above) |\n| `reason` | Optional approval message shown to the AI assistant (main-thread only; silently dropped for subagents) |\n| `if_project_has` | Project directory must contain one of these files/directories |\n| `if_project_under` | Project directory must be at or under one of these paths |\n\n### Cache\n\n```toml\n[cache]\nttl_days = 14  # tool detection cache TTL in days (default: 7)\n```\n\n### Health Check\n\n```bash\ntool-gates doctor\n```\n\nVerifies config file validity, hook installation status across all settings scopes, cache file health, and flags legacy remnants (old Python hooks, bash-gates directories). Non-zero exit code when issues are found.\n\n---\n\n## Architecture\n\n```\nsrc/\n├── main.rs              # Entry point, CLI commands\n├── models.rs            # Types (HookInput, HookOutput, Decision)\n├── parser.rs            # tree-sitter-bash AST parsing\n├── router.rs            # Security checks + gate routing\n├── security_reminders.rs # Content scanning for security anti-patterns (Write/Edit)\n├── settings.rs          # settings.json parsing and pattern matching\n├── hints.rs             # Modern CLI hints (cat-\u003ebat, grep-\u003erg, etc.)\n├── hint_tracker.rs      # Session-scoped dedup for hints + security warnings (disk-backed)\n├── tool_cache.rs        # Tool availability cache for hints\n├── mise.rs              # Mise task file parsing and command extraction\n├── package_json.rs      # package.json script parsing and command extraction\n├── tracking.rs          # PreToolUse-\u003ePostToolUse correlation (24h TTL)\n├── pending.rs           # Pending approval queue (JSONL format)\n├── patterns.rs          # Pattern suggestion algorithm\n├── post_tool_use.rs     # PostToolUse handler\n├── permission_request.rs # PermissionRequest hook handler\n├── settings_writer.rs   # Write rules to Claude settings files\n├── config.rs            # User configuration (~/.config/tool-gates/config.toml)\n├── file_guards.rs       # Symlink guard for AI config files\n├── tool_blocks.rs       # Configurable tool blocking\n├── generated/           # Auto-generated by build.rs (DO NOT EDIT)\n│   └── rules.rs         # Rust gate functions from rules/*.toml\n├── tui/                 # Interactive review TUI (three-panel dashboard)\n└── gates/               # 13 specialized permission gates\n    ├── mod.rs           # Gate registry (ordered by priority)\n    ├── helpers.rs       # Common gate helper functions\n    ├── tool_gates.rs    # tool-gates CLI itself\n    ├── basics.rs        # Safe commands (~180)\n    ├── beads.rs         # Beads issue tracker (bd) - github.com/steveyegge/beads\n    ├── gh.rs            # GitHub CLI\n    ├── git.rs           # Git\n    ├── shortcut.rs      # Shortcut CLI (short) - github.com/shortcut-cli/shortcut-cli\n    ├── cloud.rs         # AWS, gcloud, terraform, kubectl, docker, podman, az, helm, pulumi\n    ├── network.rs       # curl, wget, ssh, rsync, netcat, HTTPie, nmap, socat, telnet\n    ├── filesystem.rs    # rm, mv, cp, chmod, tar, zip\n    ├── devtools.rs      # sd, ast-grep, semgrep, biome, prettier, eslint, ruff, pytest, mypy, playwright, cypress, tsx, webpack\n    ├── package_managers.rs  # npm, pnpm, yarn, pip, uv, cargo, go, bun, conda, poetry, pipx, mise\n    ├── runtimes.rs      # python3, node, ruby, deno, php, lua, java, dotnet, swift, elixir\n    └── system.rs        # psql, mysql, make, sudo, systemctl, OS pkg managers, build tools, openssl, gpg\n```\n\n---\n\n## Credits\n\nSecurity reminder patterns were built on and informed by:\n\n- [Anthropic's security-guidance plugin](https://github.com/anthropics/claude-plugins-official/tree/main/plugins/security-guidance), the official Claude Code security hook (9 base patterns we expanded to 26)\n- [Arcanum-Sec/sec-context](https://github.com/Arcanum-Sec/sec-context), curated security anti-pattern database synthesized from 150+ sources\n- [SecureCodeWarrior/ai-security-rules](https://github.com/SecureCodeWarrior/ai-security-rules), security rule files for AI coding tools\n- [OWASP Top 10](https://owasp.org/www-project-top-ten/), standard web application security risks\n- [dwarvesf/claude-guardrails](https://github.com/dwarvesf/claude-guardrails), multi-layer defense hooks for Claude Code\n- [GitHub Actions workflow injection research](https://github.blog/security/vulnerability-research/how-to-catch-github-actions-workflow-injections-before-attackers-do/), GHA injection patterns and remediation\n\n---\n\n## Links\n\n- [Claude Code Hooks Documentation](https://code.claude.com/docs/en/hooks)\n- [Gemini CLI](https://github.com/google-gemini/gemini-cli)\n- [Claude Code MCP-CLI (experimental)](https://github.com/anthropics/claude-code/issues/12836#issuecomment-3629052941)\n- [tree-sitter-bash](https://github.com/tree-sitter/tree-sitter-bash)\n- [Beads Issue Tracker](https://github.com/steveyegge/beads)\n- [Shortcut CLI](https://github.com/shortcut-cli/shortcut-cli)\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcamjac251%2Ftool-gates","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fcamjac251%2Ftool-gates","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcamjac251%2Ftool-gates/lists"}