PreToolUse hooks: shell operator chaining bypasses command detection

Resolved 💬 3 comments Opened Feb 1, 2026 by fizz Closed Feb 1, 2026

Summary

PreToolUse hooks that inspect tool_input.command for dangerous patterns are trivially bypassed when the LLM agent chains commands with shell operators (&&, ||, ;).

Reproduction

Given a hook that blocks a dangerous git pattern:

def check_command(command):
    normalized_cmd = ' '.join(command.strip().split())
    if normalized_cmd.startswith('git add'):
        # check for dangerous patterns...

The agent can bypass it by chaining with cd:

cd /some/dir && <dangerous-command>

The hook sees the full string, checks if it starts with the target command — it doesn't — and approves. The dangerous subcommand passes through unchecked.

This affects any hook using startswith(), re.match(), or ^-anchored re.search() on the raw command string.

Impact

This is not a theoretical issue. In practice, LLM agents routinely chain cd with the actual command to ensure correct working directory. This bypassed all of my safety hooks until I discovered and patched them.

Current workaround

Hook authors can split on shell operators before matching:

import re

def check_command(command):
    subcommands = re.split(r'\s*(?:&&|\|\||;)\s*', command)
    for subcmd in subcommands:
        should_block, reason = _check_single(subcmd.strip())
        if should_block:
            return True, reason
    return False, None

Suggested platform fix

Consider having the PreToolUse hook system pre-split chained commands and invoke the hook once per subcommand (or at minimum, document this pitfall prominently). This would eliminate the entire class of bypass for all hook authors rather than requiring each one to independently discover and fix it.

Alternatively, adding a prominent warning in the hooks documentation about command chaining would help hook authors avoid this pattern.

View original on GitHub ↗

This issue has 3 comments on GitHub. Read the full discussion on GitHub ↗