Configuration
tool-gates works without a config file, and most users never write one. When you do, it lives at ~/.config/tool-gates/config.toml and covers feature toggles, tool blocking, MCP and Skill auto-approval, file guards, hints, cache, and git aliases.
Feature toggles
defaults shown[features] bash_gates = true file_guards = true hints = true security_reminders = true head_tail_pipe_block = true git_aliases = true design_lint = false
Each subsystem can be turned off independently. Toggles merge with defaults; missing keys keep their default value.
head_tail_pipe_block denies consumer-side | head -N and | tail -N caps for every producer so the agent uses a source-native bound such as rg -m N, fd --max-results N, or bat -r START:END. The producer changes the recovery message, not the decision. First-N slices through sed or awk, plus catch-all | rg . filters, use the same floor.
Streaming tail -f/-F, top-N selection after sort, and picks inside command substitutions or backticks are exempt. Quoted literals and standalone head/tail calls with no upstream pipe are not caps. Complete-stream aggregation with rg -c ., --count, or --count-matches is also exempt, but combining count mode with -m/--max-count remains blocked.
git_aliases resolves user-defined aliases against ~/.gitconfig so git st runs through the same allow/ask rules as git status.
design_lint is the one opt-in subsystem (default false): a frontend design-quality linter for UI writes. Security reminders cover the safety floor; this one covers style. Set it true to enable.
Tool blocking
documented[[block_tools]] tool = "Glob" message = "Use rg instead of Glob" [[block_tools]] tool = "mcp__firecrawl__*" message = "Use gh CLI for GitHub content." block_domains = ["github.com"] requires_tool = "rg"
Blocks tools by name. Works for any tool type: Bash, Read/Write/Edit, Glob, Grep, Skill, MCP.
tool is exact, prefix glob (mcp__exa*), or contains pattern. block_domains narrows the block to URLs containing the listed domains. requires_tool only blocks when the named modern CLI is detected on PATH.
Omitting [[block_tools]] uses the built-in defaults (Glob, Grep, firecrawl / ref / exa GitHub URLs). Set block_tools = [] to disable all blocking.
MCP accept-edits approval
documented[[accept_edits_mcp]] tool = "mcp__serena__replace_symbol_body" [[accept_edits_mcp]] tool = "mcp__serena__*" reason = "Symbol edits batched through acceptEdits" [[accept_edits_mcp]] tool = "mcp__playwright__browser_click" if_project_under = ["~/projects/trusted"]
Auto-approve MCP tool calls only when the session is in acceptEdits mode. In any other mode the rules are inert. Block rules run first; this cannot unlock a blocked tool.
tool supports exact, prefix (mcp__serena*), suffix (*_scrape), and substring (*serena*) patterns. Substring matches catch sibling servers whose names also contain the literal, so prefer prefix patterns for cross-namespace coverage.
if_project_has and if_project_under scope rules to specific directories. ~ expansion is supported.
reason surfaces only on the main-thread PreToolUse path. Subagent PermissionRequest allow has no reason slot; the field is silently dropped there. The rule is active only for clients that emit acceptEdits; Codex currently does not, so Codex MCP calls do not use this feature. Codex apply_patch file edits have a separate toggle, [codex] accept_project_edits.
Skill auto-approval
documented[[auto_approve_skills]] skill = "my-plugin*" if_project_has = [".my-plugin"] [[auto_approve_skills]] skill = "deploy-tool" if_project_under = ["~/projects/staging"]
Auto-approve Skill calls based on directory conditions. Explicit trust declaration; no external hook scripts needed.
if_project_has requires the project directory to contain one of the listed files or directories. if_project_under requires the project to be at or under one of the listed paths.
The project directory comes from CLAUDE_PROJECT_DIR or, for Gemini compatibility, GEMINI_PROJECT_DIR. If neither is set, directory-conditioned rules fail closed.
Honoured under auto mode too. [[auto_approve_skills]] rules aren't revoked by opting into the classifier.
File guards
documented[file_guards] extra_names = [".custom-config"] extra_dirs = [".mytools"] extra_prefixes = ["CUSTOM_"] extra_extensions = [".secret"]
Symlink guards protect AI configuration files from being read or edited through a symlink that points somewhere else. Default list covers CLAUDE.md, .cursorrules, GEMINI.md, the .codex/ directory, .agentignore, .claude/settings.json, and others.
extra_* fields extend the built-in list by exact filename, directory name, filename prefix, or file extension.
Hints
documented[hints] disable = ["man", "du"]
When features.hints = true (default), allowed commands using legacy tools get one-line suggestions for modern alternatives via additionalContext.
disable suppresses hints for specific commands you prefer to keep. Hints never change the decision; they only inform.
Cache
documented[cache] ttl_days = 14 # default 7
Controls how often tool-gates re-checks which modern CLI tools are installed. Lower values detect newly installed tools faster; higher values reduce disk I/O.
Force a refresh anytime with tool-gates --refresh-tools.
Git aliases
documented[git_aliases] include_local_repo = true # default false
Repo-local aliases (in $REPO/.git/config) are off by default. A malicious alias in a third-party repo should not silently inherit alias trust on first checkout.
Built-in commands always win over aliases (alias.status = log does not shadow real status). Shell-prefixed aliases (!cmd) never resolve; they ask. Chained aliases resolve up to five levels with cycle detection; a cycle or depth overflow also asks.
The global alias map is read once per process. When local aliases are enabled, the repository map is read for the current project and shadows the global map.
Security reminders
documented[security_reminders] secrets = true anti_patterns = true warnings = true disable_rules = ["eval_injection"]
Disable individual rules by id, including Tier 1 rules, when you have a deliberate local exception. For a broad secret-scan opt-out, set secrets = false.
Use Tier 1 disables sparingly: they remove the hard-deny floor for that specific secret rule.
Design lint
documented[features] design_lint = true [design_lint] disable_rules = ["color/default-indigo"]
Opt-in (default off). Set design_lint = true under [features] to scan UI file writes and edits for generic, templated design patterns and missing UI-quality basics.
Disable individual rules by id (for example color/default-indigo or content/dash) when a project deliberately uses that pattern. CSS custom-property definitions in a :root block are exempt from the raw-color rules, so defining a brand token is never flagged.
Codex settings
defaults shown[codex] accept_project_edits = false allow_edits_anywhere = false
Configures Codex-specific auto-approval rules.
When accept_project_edits = true, in-project apply_patch edits auto-approve on the Codex PermissionRequest hook. It also evaluates Codex shell commands in in-project directories as acceptEdits, allowing safe formatting and linting while keeping dangerous operations gated.
When allow_edits_anywhere = true, the auto-approval is widened to edits anywhere on disk, subject to standard deny rules and file guards.
Codex command gating lives outside this file. Codex's built-in safe-read auto-approval is governed by execpolicy rules in ~/.codex/rules/, not tool-gates config. To route those reads through tool-gates, see the Codex approval model. The one Codex setting that does live here, [codex] accept_project_edits, auto-approves in-project edits on Codex (apply_patch plus file-editing shell commands); that same page covers it.
Config path
~/.config/tool-gates/config.toml
Optional. Sane defaults if absent.