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