Skip to content

fix(plugin-dev): hook JSON to stdout, tighten su* glob, fix CI detection and JSON injection in examples#68785

Open
AZERDSQ131 wants to merge 1 commit into
anthropics:mainfrom
AZERDSQ131:fix/bug-25-26-27-plugin-dev-example-hooks
Open

fix(plugin-dev): hook JSON to stdout, tighten su* glob, fix CI detection and JSON injection in examples#68785
AZERDSQ131 wants to merge 1 commit into
anthropics:mainfrom
AZERDSQ131:fix/bug-25-26-27-plugin-dev-example-hooks

Conversation

@AZERDSQ131

Copy link
Copy Markdown

Problems

Three example hook scripts in plugins/plugin-dev/skills/hook-development/examples/ have bugs that make them incorrect as reference implementations.

BUG-25 — validate-bash.sh: hook responses written to stderr

Lines 26, 32, 38 use >&2 on the decision JSON:

echo '{"hookSpecificOutput": {"permissionDecision": "deny"}, ...}' >&2
exit 2

Claude Code reads structured hook responses (permissionDecision, systemMessage) from stdout. Writing to stderr means the JSON is never processed — the hook exits 2 and the operation is blocked, but Claude receives no explanation and the UI shows nothing.

Fix: remove >&2 from all three decision echoes.

BUG-25b — validate-bash.sh: su* glob matches any command starting with "su"

Line 37:

if [[ "$command" == sudo* ]] || [[ "$command" == su* ]]; then

su* is a glob that matches submodule, subl, sum, substring, sudo (already caught), etc. git submodule status would trigger the privilege-escalation block.

Fix: replace with [[ "$command" =~ ^su([[:space:]]|-|$) ]] — matches bare su, su -l user, su root, but not submodule.

BUG-26 — validate-write.sh: same stderr bug + JSON injection via $file_path

All three decision echoes write to stderr (same root cause as BUG-25).

Additionally, $file_path is embedded in the JSON string via shell concatenation:

echo '{"systemMessage": "Path traversal detected in: '"$file_path"'"}' >&2

If $file_path contains " or \ (e.g. C:\Users\foo\..\"secret"), the resulting JSON is syntactically invalid and Claude Code cannot parse the hook response.

Fix: use jq -n --arg p "$file_path" '... + $p'jq properly escapes any character.

BUG-27 — load-context.sh: -f used to test a directory

Line 50:

if [ -f ".github/workflows" ] || ...

.github/workflows is a directory. -f tests for regular files and always returns false for directories — GitHub Actions CI is never detected and HAS_CI is never set.

Fix: change -f to -d.

Changes

File Change
examples/validate-bash.sh Remove >&2; change su* glob to regex
examples/validate-write.sh Remove >&2; use jq -n --arg for safe JSON
examples/load-context.sh Change -f to -d for .github/workflows

Testing

  • validate-bash.sh: run echo '{"tool_name":"Bash","tool_input":{"command":"rm -rf /tmp/x"}}' | bash validate-bash.sh — output JSON now appears on stdout
  • validate-bash.sh: echo '{"tool_name":"Bash","tool_input":{"command":"git submodule status"}}' | bash validate-bash.sh — exits 0 (not blocked)
  • validate-write.sh: echo '{"tool_input":{"file_path":"foo\"bar"}}' ... | bash validate-write.sh — produces valid JSON
  • load-context.sh: run in a repo with .github/workflows/HAS_CI now set

…n su* glob; fix -f/-d check and JSON injection in example hooks

- validate-bash.sh: remove >&2 from all three decision echoes — Claude Code
  reads hook JSON responses from stdout, not stderr; writing to stderr means
  the permissionDecision and systemMessage are silently discarded
- validate-bash.sh: replace `su*` glob (which matches submodule, subl, sum…)
  with `^su([[:space:]]|-|$)` regex so only the `su` switch-user command and
  its flags are caught, not every command starting with "su"
- validate-write.sh: same stdout fix for all three decision echoes
- validate-write.sh: replace shell string-concatenation into JSON literals with
  `jq -n --arg` so paths containing `"` or `\` produce valid JSON instead of
  breaking the message structure
- load-context.sh: `.github/workflows` is a directory; `-f` always returns
  false for it — change to `-d` so GitHub Actions CI is correctly detected

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant