mirror of
https://github.com/affaan-m/everything-claude-code.git
synced 2026-06-11 02:33:10 +08:00
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).
This commit is contained in:
@@ -74,10 +74,13 @@ function extractCommandSubstitutions(input) {
|
||||
if (ch === '$' && source[i + 1] === '(') {
|
||||
let depth = 1;
|
||||
let body = '';
|
||||
let bodyInSingle = false;
|
||||
let bodyInDouble = false;
|
||||
i += 2;
|
||||
while (i < source.length && depth > 0) {
|
||||
const inner = source[i];
|
||||
if (inner === '\\') {
|
||||
const innerPrev = source[i - 1];
|
||||
if (inner === '\\' && !bodyInSingle) {
|
||||
body += inner;
|
||||
if (i + 1 < source.length) {
|
||||
body += source[i + 1];
|
||||
@@ -85,12 +88,18 @@ function extractCommandSubstitutions(input) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
if (inner === '(') {
|
||||
depth += 1;
|
||||
} else if (inner === ')') {
|
||||
depth -= 1;
|
||||
if (depth === 0) {
|
||||
break;
|
||||
if (inner === "'" && !bodyInDouble && innerPrev !== '\\') {
|
||||
bodyInSingle = !bodyInSingle;
|
||||
} else if (inner === '"' && !bodyInSingle && innerPrev !== '\\') {
|
||||
bodyInDouble = !bodyInDouble;
|
||||
} else if (!bodyInSingle && !bodyInDouble) {
|
||||
if (inner === '(') {
|
||||
depth += 1;
|
||||
} else if (inner === ')') {
|
||||
depth -= 1;
|
||||
if (depth === 0) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
body += inner;
|
||||
@@ -154,15 +163,24 @@ function extractSubshellGroups(input) {
|
||||
|
||||
if (ch === '$' && source[i + 1] === '(') {
|
||||
let depth = 1;
|
||||
let skipInSingle = false;
|
||||
let skipInDouble = false;
|
||||
i += 2;
|
||||
while (i < source.length && depth > 0) {
|
||||
const inner = source[i];
|
||||
if (inner === '\\') {
|
||||
const innerPrev = source[i - 1];
|
||||
if (inner === '\\' && !skipInSingle) {
|
||||
i += 2;
|
||||
continue;
|
||||
}
|
||||
if (inner === '(') depth += 1;
|
||||
else if (inner === ')') depth -= 1;
|
||||
if (inner === "'" && !skipInDouble && innerPrev !== '\\') {
|
||||
skipInSingle = !skipInSingle;
|
||||
} else if (inner === '"' && !skipInSingle && innerPrev !== '\\') {
|
||||
skipInDouble = !skipInDouble;
|
||||
} else if (!skipInSingle && !skipInDouble) {
|
||||
if (inner === '(') depth += 1;
|
||||
else if (inner === ')') depth -= 1;
|
||||
}
|
||||
i += 1;
|
||||
}
|
||||
i -= 1;
|
||||
@@ -184,10 +202,13 @@ function extractSubshellGroups(input) {
|
||||
if (ch === '(') {
|
||||
let depth = 1;
|
||||
let body = '';
|
||||
let bodyInSingle = false;
|
||||
let bodyInDouble = false;
|
||||
i += 1;
|
||||
while (i < source.length && depth > 0) {
|
||||
const inner = source[i];
|
||||
if (inner === '\\') {
|
||||
const innerPrev = source[i - 1];
|
||||
if (inner === '\\' && !bodyInSingle) {
|
||||
body += inner;
|
||||
if (i + 1 < source.length) {
|
||||
body += source[i + 1];
|
||||
@@ -195,12 +216,18 @@ function extractSubshellGroups(input) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
if (inner === '(') {
|
||||
depth += 1;
|
||||
} else if (inner === ')') {
|
||||
depth -= 1;
|
||||
if (depth === 0) {
|
||||
break;
|
||||
if (inner === "'" && !bodyInDouble && innerPrev !== '\\') {
|
||||
bodyInSingle = !bodyInSingle;
|
||||
} else if (inner === '"' && !bodyInSingle && innerPrev !== '\\') {
|
||||
bodyInDouble = !bodyInDouble;
|
||||
} else if (!bodyInSingle && !bodyInDouble) {
|
||||
if (inner === '(') {
|
||||
depth += 1;
|
||||
} else if (inner === ')') {
|
||||
depth -= 1;
|
||||
if (depth === 0) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
body += inner;
|
||||
|
||||
Reference in New Issue
Block a user