diff --git a/docs/bash-tool-runtime.md b/docs/bash-tool-runtime.md index ad1c73b80..642fd96f2 100644 --- a/docs/bash-tool-runtime.md +++ b/docs/bash-tool-runtime.md @@ -43,7 +43,7 @@ Interception behavior: - on block, `BashTool` throws `ToolError` with message: - `Blocked: ...` - original command included. -- heredocs, command substitutions, backticks, grouping, and malformed quoting do not produce extra fragments; they retain only the complete-input check. Interception is best-effort routing to dedicated tools, not a shell-security policy. +- heredocs, parameter expansion, command substitutions, backticks, grouping, and malformed quoting do not produce extra fragments; they retain only the complete-input check. Interception is best-effort routing to dedicated tools, not a shell-security policy. Default rule patterns (defined in code) target common misuses: diff --git a/docs/tools/bash.md b/docs/tools/bash.md index 89d1aac6f..ddea53769 100644 --- a/docs/tools/bash.md +++ b/docs/tools/bash.md @@ -110,7 +110,7 @@ git add file && git commit -m "message" GIT_AUTHOR_NAME=Dev git commit -m "message" ``` -An anchored rule such as `^\s*git\s+commit\b` can therefore match the `git commit` command in both examples. Quoted, escaped, and commented text is not treated as a command. Heredocs, command substitution, backticks, grouping, and malformed quoting retain only the complete-command check; the interceptor deliberately does not attempt to become a full shell parser. +An anchored rule such as `^\s*git\s+commit\b` can therefore match the `git commit` command in both examples. Quoted, escaped, and commented text is not treated as a command. Heredocs, parameter expansion, command substitution, backticks, grouping, and malformed quoting retain only the complete-command check; the interceptor deliberately does not attempt to become a full shell parser. ### Interaction and selection guide @@ -221,7 +221,7 @@ Choose the setting by the desired outcome: - `strict = true` is set on `BashTool`; `concurrency` is resolved per call: `pty: true` is `"exclusive"` (it takes over the terminal UI), everything else is `"shared"`, so multiple non-pty bash calls in one assistant message run in parallel. When parallel calls overlap on the same shell session key, the first owns the persistent `Shell`; the rest run in isolated one-shot shells (see `shellSessionsInUse` in `bash-executor.ts`). - `command` URL expansions shell-escape replacements; `env` and `cwd` expansion use `noEscape: true` because they become environment values / filesystem paths, not shell text. - `checkBashInterception()` blocks only when the matching rule's `tool` name is present in `ctx.toolNames`; missing tools disable their corresponding rule. -- Interceptor configuration syntax is unchanged. It handles common flat command lists, not full shell parsing: heredocs, command substitution, backticks, grouping, and malformed quoting only receive the existing whole-input check. This is best-effort routing toward dedicated tools, not a security boundary. +- Interceptor configuration syntax is unchanged. It handles common flat command lists, not full shell parsing: heredocs, parameter expansion, command substitution, backticks, grouping, and malformed quoting only receive the existing whole-input check. This is best-effort routing toward dedicated tools, not a security boundary. - Default interceptor rules come from `DEFAULT_BASH_INTERCEPTOR_RULES` in `packages/coding-agent/src/config/settings-schema.ts`: - `cat|head|tail|less|more` -> `read` - `grep|rg|ripgrep|ag|ack` -> `grep` diff --git a/packages/coding-agent/src/tools/shell-tokenize.ts b/packages/coding-agent/src/tools/shell-tokenize.ts index e705b9ed0..faa50bb72 100644 --- a/packages/coding-agent/src/tools/shell-tokenize.ts +++ b/packages/coding-agent/src/tools/shell-tokenize.ts @@ -145,6 +145,7 @@ export function extractFlatShellCommandSegments(command: string): string[] { ch === "(" || ch === ")" || (ch === "$" && command[i + 1] === "(") || + (ch === "$" && command[i + 1] === "{") || (ch === "<" && command[i + 1] === "<") || ((ch === "{" || ch === "}") && atWordStart && diff --git a/packages/coding-agent/test/tools/bash-interceptor.test.ts b/packages/coding-agent/test/tools/bash-interceptor.test.ts index 2c3177acf..7a6d3e23d 100644 --- a/packages/coding-agent/test/tools/bash-interceptor.test.ts +++ b/packages/coding-agent/test/tools/bash-interceptor.test.ts @@ -127,6 +127,7 @@ describe("compound command interception", () => { for (const command of [ 'echo "$(git commit -m message)"', "echo `git commit -m message`", + "echo ${x:-foo;git commit -m message}", "( git commit -m message )", "echo start; { true; git commit -m message; }", "cat <<'EOF'\ngit commit -m message\nEOF",