Files
everything-claude-code/scripts/lib/shell-substitution.js
Jamkris 70b86d81c4 fix(lib): track quote state inside command-substitution depth counters
Greptile flagged a bypass in PR #1889: `$(echo ")"; (npm run dev))`
threaded the depth-counting loops in `extractCommandSubstitutions`
and `extractSubshellGroups` to terminate early, because a literal `)`
inside double quotes was treated as a real closing paren. The
truncated body then ended in a dangling `"` that toggled `inDouble`
in the outer scan, masking the subsequent `(npm run dev)` group from
extraction.

Reproduced (before this commit) by piping the synthetic PreToolUse
payload `{"tool_input":{"command":"$(echo \")\"; (npm run dev))"}}`
into `scripts/hooks/pre-bash-dev-server-block.js` and observing
exit 0 (allow) where the dev pattern is clearly present.

Fix: each `$(...)` and `(...)` body loop now tracks its own
single/double quote state and only treats `(` / `)` as depth
delimiters when outside quotes. The quoted `)` no longer closes
the group early, the body now extends to the real closing paren,
and the outer scan's quote state remains untouched.

After this commit:
  $ echo '{"tool_input":{"command":"$(echo \")\"; (npm run dev))"}}' \
      | node scripts/hooks/pre-bash-dev-server-block.js; echo $?
  2

The symmetric form `$(echo "(npm run dev)")` correctly remains
allowed (bash does not honor `(...)` inside double quotes).
2026-05-14 12:22:22 +09:00

6.5 KiB