claude-code icon indicating copy to clipboard operation
claude-code copied to clipboard

[BUG] `$ARGUMENTS` in slash commands not escaped for bash execution

Open mattkeenan opened this issue 1 week ago • 0 comments

Preflight Checklist

  • [x] I have searched existing issues and this hasn't been reported yet
  • [x] This is a single bug report (please file separate reports for different bugs)
  • [x] I am using the latest version of Claude Code

What's Wrong?

When using $ARGUMENTS in custom slash commands (.claude/commands/*.md) with bash execution syntax (!`command`), user input is passed to bash without any escaping or sanitization.

This causes:

  1. Syntax errors with quotes, apostrophes, and other shell meta-characters
  2. Command injection vulnerabilities via $, backticks, and other special characters
  3. Inconsistent behaviour - the bundled shell-quote library is used elsewhere in the codebase but not for $ARGUMENTS substitution

What Should Happen?

The command should output: don't try this

User input in $ARGUMENTS should be properly escaped for bash execution using the already-bundled shell-quote library, preventing both syntax errors and command injection.

Error Messages/Logs

For example, running `/test-bash-args don't try this` produces:

Error: Bash command failed for pattern "!`echo don't try this`": [stderr]
/bin/bash: eval: line 1: unexpected EOF while looking for matching `''

Steps to Reproduce

  1. Create .claude/commands/test-bash-args.md:
---
description: Test $ARGUMENTS passed to bash
allowed-tools: Bash(echo:*)
---

# Test Bash Arguments

Command output: !`echo $ARGUMENTS`
  1. Restart Claude Code to load the new command

  2. Run the command with an apostrophe:

$ claude
> /test-bash-args don't try this
  1. Observe the bash syntax error

Additional test cases that fail:

Input Issue
can't go Syntax error: unmatched quote
$(whoami) Command injection via $()
`whoami` Command injection via backticks
$HOME Variable expansion
test;ls Command chaining

Claude Model

Sonnet (default)

Is this a regression?

No, this never worked

Last Working Version

No response

Claude Code Version

2.0.76 (Claude Code)

Platform

Anthropic API

Operating System

Ubuntu/Debian Linux

Terminal/Shell

Xterm

Additional Information

Root Cause Analysis

Investigation process (reproducible):

  • Version: v2.0.76
  • Beautifier: js-beautify v1.14.11
  • Command: js-beautify ~/.npm-global/lib/node_modules/@anthropic-ai/claude-code/cli.js > output.js

Key findings in beautified code:

Line 277247 - $ARGUMENTS substitution uses simple replaceAll() with NO escaping:

if (R)
    if (P.includes("$ARGUMENTS")) P = P.replaceAll("$ARGUMENTS", R);

Line 49020-49029 - shell-quote library IS bundled but NOT used for $ARGUMENTS:

var kBQ = U((Vd7, vBQ) => {
    vBQ.exports = function(Q) {
        return Q.map(function(B) {
            if (B === "") return "''";
            // ... proper escaping of ", \, $, `, !, #, &, ', etc.
        }).join(" ")
    }
});

Line 49193-49208 - shell-quote is ALREADY used elsewhere in the codebase (function t6()).

Production Commands Avoid This Pattern

Analysis of all production commands in the repository (/.claude/commands/*.md) shows NONE pass $ARGUMENTS to bash execution:

  • clean_gone.md - Uses static bash commands only
  • commit-push-pr.md - Uses bash for info gathering (!`git status`), not with user args
  • dedupe.md - Static bash commands
  • oncall-triage.md - Static bash commands

This appears to be a known design limitation - production commands carefully avoid passing $ARGUMENTS to bash.

Proposed Solution

Use the existing bundled shell-quote library to escape $ARGUMENTS before substitution:

if (R)
    if (P.includes("$ARGUMENTS")) {
        let escapedArgs = G5A.quote([R]);  // Use shell-quote
        P = P.replaceAll("$ARGUMENTS", escapedArgs);
    }

Benefits:

  • ✅ Makes $ARGUMENTS safe to use in bash commands
  • ✅ Would enable implementation of $1, $2, $3 (currently documented but not implemented - likely for this same reason)
  • ✅ No new dependencies (library already bundled)
  • ✅ Consistent with existing t6() function behavior

Why $1, $2, $3 Don't Exist

The documentation mentions positional arguments $1, $2, $3, but:

  • ❌ No implementation exists in the code
  • ❌ Empirical testing shows they remain as literal text
  • ❌ Issue #8406 (Sept 2025) shows users trying unsuccessfully to use them

This feature was likely documented but never implemented because it would encourage the same unsafe pattern.

Environment Details

  • OS: Linux 6.18.1-local
  • Node: v18+
  • Installation: npm global (~/.npm-global/lib/node_modules/@anthropic-ai/claude-code)

mattkeenan avatar Jan 03 '26 14:01 mattkeenan