Skip to main content

Claude Code PreToolUse hook that screens bash commands with path validation

Project description

bashgate

A Claude Code hook that automatically approves bash commands you've allowlisted, while still prompting for everything else. It also validates that file paths stay inside your project directory — something Claude Code's built-in permissionDecision: "allow" rules don't do.

Why use this?

Claude Code asks permission before running bash commands. You can add Bash(...) rules in settings.json to auto-approve certain commands, but those rules bypass Claude Code's path validation — meaning an allowed command could read or write files anywhere on your system.

bashgate gives you the same auto-approval convenience with two improvements:

  • Path validation — commands that reference paths outside your project directory are flagged for approval, even if the command itself is allowlisted
  • Fine-grained control — allow specific subcommands (e.g. git push but not git reset --hard), block dangerous flags, and match argument patterns

Installation

Requires Python 3.10+.

pipx install bashgate
bashgate install

bashgate install registers the hook in ~/.claude/settings.json and copies a sensible default config to ~/.claude/bashgate.json if one doesn't already exist.

How it works

When Claude Code wants to run a bash command, bashgate intercepts it and makes one of three decisions:

  • Allow — the command matches your allowlist and all paths are within the project directory
  • Ask — the command is recognised but has a risky flag, targets a path outside the project, or hits a deny rule. Claude Code prompts you as normal
  • Fall through — the command isn't in the config at all, so Claude Code's default permission system handles it

Compound commands (&&, ||, ;, |) are only auto-approved if every part is individually allowed. Shell features like variable expansion, backticks, and process substitution always trigger a prompt.

Configuration

bashgate looks for config in two places:

  1. Global~/.claude/bashgate.json
  2. Local.bashgate.json files found by walking from the project directory up to the filesystem root

All found configs are merged, with the nearest-to-project file taking highest precedence. This lets you add project-specific rules (e.g. allowing mix test only in Elixir projects) on top of your global defaults.

If you don't want local configs to be able to weaken or disable your global rules, set "ignore_local_configs": true in your global config.

Quick start

The default config ships with sensible rules for common commands. After bashgate install, you'll have a working setup that auto-approves things like git status, ls, cat, and rg while still prompting for destructive operations.

It's recommended you review the config and change it for your needs.

Config format

The config is a JSON file with a commands array. Entries can be simple strings or detailed objects.

Simple string — prefix-matches the full command:

{
  "commands": [
    "cat",
    "ls",
    "mise exec -- bundle exec rspec"
  ]
}

"cat" matches cat foo.txt, cat -n bar.py, etc.

Object entry — for commands that need subcommand control or deny rules:

{
  "commands": [
    {
      "command": "git",
      "flags_with_args": ["-C", "-c"],
      "allow": {
        "subcommands": [
          "status",
          "diff",
          "log",
          {
            "subcommand": "push",
            "ask": {
              "flags": ["--force", "-f"]
            }
          }
        ]
      }
    }
  ]
}

This allows git status, git diff, git log, and git push — but prompts if git push is used with --force. Any other git subcommand (like git reset) falls through to Claude Code's default prompting.

Entry reference

Field Description
command The command name to match (required for object entries)
flags_with_args Flags that consume the next token (e.g. -C dir) — needed so bashgate can correctly identify the subcommand
allow.subcommands List of allowed subcommands (strings or objects with their own rules)
allow.any_path true to skip path validation entirely, or {"position": N} to skip it for the Nth positional argument (useful for sed and grep where the first argument is a pattern, not a path)
allow.flags_with_any_path Flags whose values should be exempt from path validation
deny.flags Flags that should always be blocked
deny.arg_regex Regex pattern matched against arguments — triggers a block if matched
deny.message Custom message shown when a deny rule triggers
ask.flags Flags that should trigger a prompt (same as deny but results in "ask" instead of "deny")
ask.arg_regex Regex pattern that triggers a prompt if matched

Additional config options

Field Default Description
enabled true Set to false to disable bashgate entirely (falls through to Claude Code defaults)
disable_inside_sandbox false Set to true to disable bashgate when Claude Code's sandbox is active
ignore_local_configs false Set to true in the global config to skip local .bashgate.json discovery entirely. Prevents project-level configs from weakening or disabling protection.
allowed_directories [] Additional directories that should be treated as valid path targets (supports relative paths resolved from the config file's location)

Security note: local config files

bashgate merges .bashgate.json files found in or above your project directory. This means a cloned repository could include a .bashgate.json that loosens your rules — for example, allowing commands or paths you wouldn't normally approve.

If you work with untrusted repositories, set "ignore_local_configs": true in your global ~/.claude/bashgate.json to prevent any local config from being loaded.

Validating your config

bashgate validate
bashgate validate --config path/to/config.json

This checks for structural errors, unknown keys, and invalid regex patterns.

Uninstalling

bashgate uninstall

This removes the hook entry from ~/.claude/settings.json. If ~/.claude/bashgate.json exists, it will alert you but won't delete it — remove it manually if you no longer need it.

find considered annoying

You'll find bashgate trips up on find quite a bit because of its awkward syntax. I'd suggest

    {
      "command": "find",
      "deny": {
        "message": "use `fd` instead of `find`"
      }
    }

and install the excellent fd to do your finding for you instead.

Debugging

If commands aren't being handled as expected:

# In your hook config, add --debug:
bashgate hook --debug

This logs decisions to ~/.claude/bashgate-debug.log.

License

MIT

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

bashgate-0.2.0.tar.gz (19.5 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

bashgate-0.2.0-py3-none-any.whl (17.1 kB view details)

Uploaded Python 3

File details

Details for the file bashgate-0.2.0.tar.gz.

File metadata

  • Download URL: bashgate-0.2.0.tar.gz
  • Upload date:
  • Size: 19.5 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.14.3

File hashes

Hashes for bashgate-0.2.0.tar.gz
Algorithm Hash digest
SHA256 e9d2faf6cf8fd5ee45f6c1d12d7845446f10e7274d6f9101b8f23ce6bb52e118
MD5 475bec8cf040f284d17b97c56cff68e3
BLAKE2b-256 90ca5ef6fb5427a51c2b74f1115654fd56eb72be831ee9a12dc89641a4e9ed03

See more details on using hashes here.

File details

Details for the file bashgate-0.2.0-py3-none-any.whl.

File metadata

  • Download URL: bashgate-0.2.0-py3-none-any.whl
  • Upload date:
  • Size: 17.1 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.14.3

File hashes

Hashes for bashgate-0.2.0-py3-none-any.whl
Algorithm Hash digest
SHA256 0e93093e7efdbbb1dbcef0b9c6e4e19ed71de11d3e22d17bf1898a1f35f9d33a
MD5 a035e83411d20e7564115839eb6d4d5d
BLAKE2b-256 93679869bc556e5338fb7476e540688584895ac77a059e5ca8df4e7874c4f879

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page