Bash tool shell runs with `expand_aliases` enabled — shell aliases can silently rewrite commands after PreToolUse hook approval

Status Open
Reported on v2.1.206
Maintainer reply None cached
Activity 0 comments · opened Jul 20, 2026

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 -c reports expand_aliases off 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 see BASH_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:

  1. Default the non-interactive tool shell to shopt -u expand_aliases (and/or unalias -a at startup), or run it --norc; or
  2. A config option to select a pristine/no-rc shell for the Bash tool; or
  3. 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)

View original on GitHub ↗