fix(hooks): literal braces in \${} and plus invocation flags

Only nest parameter expansions on \${, not ordinary braces, and accept
valid +i/+c/+l/+r/+s after bash -c while still rejecting +z.
This commit is contained in:
gaebal-gajae
2026-08-31 02:54:09 +00:00
parent 403a40ca4c
commit 6b621aa7c9
3 changed files with 9 additions and 8 deletions

View File

@@ -5,15 +5,15 @@
"provenance": {
"base": "05c800f40d1ad53b42a78609d2667ef4f726808b",
"planningHead": "0a91273e61dbbd47eb0af4c02844409251e08398",
"head": "06b4a48bd9eb0d2857c863d3c0c002166f9cf129",
"sourceSha256": "2fbb6b44c6c9ee6bf73e9df413ffb754a9dfd26a473443daec0e4acfbcb6ff5a",
"head": "403a40ca4cd080ae6fe1bcd7cf04f1e1a17e4d73",
"sourceSha256": "0d6eea19f36011c47bd0dadcebba72d306cfd21789c871f79aeb8125de950751",
"generatedAt": null,
"generator": "scripts/generate-inventory-graph.mjs"
},
"base": "05c800f40d1ad53b42a78609d2667ef4f726808b",
"planningHead": "0a91273e61dbbd47eb0af4c02844409251e08398",
"head": "06b4a48bd9eb0d2857c863d3c0c002166f9cf129",
"sourceSha256": "2fbb6b44c6c9ee6bf73e9df413ffb754a9dfd26a473443daec0e4acfbcb6ff5a",
"head": "403a40ca4cd080ae6fe1bcd7cf04f1e1a17e4d73",
"sourceSha256": "0d6eea19f36011c47bd0dadcebba72d306cfd21789c871f79aeb8125de950751",
"counts": {
"public": {
"skills": 35,
@@ -76776,5 +76776,5 @@
}
},
"inventorySha256": "f5ea9ebf29ba491f17b39431660e07f71eb3d78399d481c1c2b00fc34cf2ab50",
"manifestSha256": "f522f3644d5fde9ba2e04c1ba664bc826583bf648b6fc73c00f893f5e1314715"
"manifestSha256": "8e308f82d0de9d9051f498a6c049e856e000da8df3919cf3b050bcb4b1db7409"
}

View File

@@ -478,6 +478,8 @@ describe('pre-tool-use template source extension detection', () => {
['bash -c plus-option still executes the command string', "bash -c +x 'rm src/app.ts'", true],
['unexpanded dollar-paren heredoc delimiter still terminates', "cat <<$(printf EOF) > build.log\ndata\n$(printf EOF)\nrm src/app.ts", true],
['nested parameter expansion in dollar-paren delimiter still terminates', "cat <<$(echo ${x-)}) > build.log\ndata\n$(echo ${x-)})\nrm src/app.ts", true],
['literal brace in parameter default does not swallow delimiter', "cat <<$(echo ${x:-{}) > build.log\ndata\n$(echo ${x:-{})\nrm src/app.ts", true],
['bash -c plus-i still executes the command string', "bash -c +i 'rm src/app.ts'", true],
['explicit stdin shell heredoc source write', "bash -s <<'EOF'\necho hacked > src/app.ts\nEOF", true],
['explicit fd zero shell heredoc source write', "bash 0<<'EOF'\necho hacked > src/app.ts\nEOF", true],
['shell rcfile option still reads heredoc program', "bash --rcfile /tmp/rc <<'EOF'\necho hacked > src/app.ts\nEOF", true],

View File

@@ -555,8 +555,7 @@ function shellGroup(text, openIndex) {
if (ch === '\\') { i += 1; continue; }
if (ch === '$' && text[i + 1] === '{') { brace += 1; i += 1; continue; }
if (brace > 0) {
if (ch === '{') brace += 1;
else if (ch === '}') brace -= 1;
if (ch === '}') brace -= 1;
else if (ch === '(') depth += 1;
else if (ch === ')' && depth > 1) depth -= 1;
continue;
@@ -1439,7 +1438,7 @@ function checkSegment(segment, directory) {
if (!shellArgs[codeIndex + 1]) return true;
codeIndex += 2; continue;
}
if (/^[+-][abefhkmnptuvxBCEHPT]+$/.test(value) || /^-[cilsD]$/.test(value) || /^(?:--norc|--noprofile|--posix|--restricted|--verbose|--debugger)$/.test(value)) {
if (/^[+-][abefhkmnptuvxBCEHPTcilsD]+$/.test(value) || /^(?:--norc|--noprofile|--posix|--restricted|--verbose|--debugger)$/.test(value)) {
codeIndex += 1; continue;
}
if (value.startsWith('-') || value.startsWith('+')) return false;