Bash tool shell runs with `expand_aliases` enabled — shell aliases can silently rewrite commands after PreToolUse hook approval
Summary
The Bash tool's shell runs with shopt expand_aliases enabled, even though it is non-interactive. As a result, a shell alias or function whose name matches a command can silently rewrite what actually executes — after a PreToolUse hook has already inspected and approved the command text. This breaks the "the command I inspected is the command that runs" invariant that any command-gating PreToolUse hook depends on.
This is a hardening request, not a zero-click RCE (the alias must already be present in the shell — see Threat below). But it quietly undermines the security value of PreToolUse Bash hooks in general, and a PreToolUse hook cannot fix it from its side.
Repro (claude 2.1.206, Linux)
From inside the Bash tool:
$ shopt -q expand_aliases && echo ON || echo off
ON # the tool shell expands aliases
$ bash -c 'shopt -q expand_aliases && echo ON || echo off'
off # a plain subprocess does NOT — so the setting is applied by the tool-shell wrapper
$ alias cd='echo ALIAS-FIRED'; cd /tmp
ALIAS-FIRED /tmp # the alias replaced the builtin; the real cd never ran
So if cd (or git, grep, cat, any command a hook whitelists) is aliased, the hook approves the innocent literal cd subdir and the shell executes something else — e.g. alias cd='rm -rf' → rm -rf subdir.
Why a PreToolUse hook can't defend against it
- The hook is a separate process that decides before the tool shell is spawned; there is no exec shell to introspect at decision time.
- It could spawn its own probe shell, but that shell does not match the tool shell — a hook-spawned
bash -creportsexpand_aliasesoff while the tool shell is ON (above). The tool-shell setup is undocumented and non-default, so a probe can't faithfully reproduce it, and would give false "no aliases" readings. - Each command gets a fresh shell (no persistent alias table to read), and even a perfect probe races the rc file (TOCTOU).
- Reading rc files instead would require re-implementing bash's startup-file resolution and executing its interactive guards (
case $- in *i*) …), and still couldn't seeBASH_ENV/env-driven sources.
Threat / severity
Realistically defense-in-depth: the alias must be live in the tool shell, which means it was planted in a file the tool shell sources (e.g. above the common non-interactive early-return guard in ~/.bashrc, or via BASH_ENV). Reaching that generally implies pre-existing host access or a user-approved edit to a dotfile. So this is not directly exploitable out of the box — but it means the deterministic guarantee a PreToolUse command hook is supposed to provide ("this exact command was vetted") is not actually guaranteed, in every permission mode (default/auto/acceptEdits/bypass — the hook approves the literal regardless).
Requested change
Give the Bash tool's shell an aliases-off posture so parsed == executed:
- Default the non-interactive tool shell to
shopt -u expand_aliases(and/orunalias -aat startup), or run it--norc; or - A config option to select a pristine/no-rc shell for the Bash tool; or
- At minimum, document the exact tool-shell invocation (which files it sources, which
shopts it sets) so hook authors can reason about the gap.
Any of these restores the invariant for all alias/function sources at once, at zero per-command cost — which no PreToolUse hook can achieve on its own.
Related: #7490 (configure which shell the Bash tool uses).
Environment
- Claude Code 2.1.206, Linux (x86_64)