mirror of
https://github.com/QwenLM/qwen-code.git
synced 2026-08-10 17:27:10 +00:00
* fix(core): treat a bare & as a command boundary in splitCompoundCommand
`SHELL_OPERATORS` lists `&&`, `||`, `;;`, `|&`, `|`, `;` and newline, but not
the bare `&`. Everything after an async operator is a separate command that
the shell will run, so a compound command joined with `&` was handed to the
permission checks as one long segment.
PermissionManager splits on these boundaries at three sites and evaluates
each sub-command independently, taking the most restrictive verdict. Without
the split there is only one segment, so `Bash(git status)` matches the whole
of `git status & rm -rf /tmp/x` and authorises the `rm` along with it. The
sibling splitter in shell-utils already treats `&` as a boundary, so the two
disagreed about the same command.
The `&` character has three other uses that are not boundaries: `&>` and
`&>>` redirect both streams, and `>&` / `<&` duplicate a descriptor. Guard
against all of them. Note this is stricter than the shell-utils guard, which
only inspects the preceding character and so splits `a &> /dev/null`.
Also corrects an existing test that asserted `echo a \&& b` is a single
command. The backslash escapes only the first ampersand; `bash -x` shows the
second is a live async operator and runs it as `echo a '&'` plus `b`.
* test(core): cover the input-descriptor form of the redirection guard
`isAsyncOperator` scans back past whitespace and returns `ch !== '>' && ch
!== '<'`, but every existing case landed on the `>` branch, so removing the
`<` exclusion went undetected. `exec 3<&4` and `cat <&3` are the input
duplication form; bash consumes the `&` as part of `<&` there, so neither
may split.
Verified by deleting `&& ch !== '<'`: exactly these two fail and the five
existing redirection cases still pass.
* fix(core): make the async-operator split escape-, arithmetic- and subshell-aware
Follow-up on review of the bare-& boundary, addressing three cases where the
new split reads the shell wrong.
1. `isAsyncOperator`'s backward scan was not escape-aware. `\>` is a literal
`>` argument, not a redirection, so bash backgrounds `echo a \>` and then
runs whatever follows. Reading it as a redirection kept both halves in one
segment, so the leading command's allow rule covered the trailing one.
2. Inside `$(( … ))` / `(( … ))` a bare `&` is bitwise AND. Splitting there
turned `VAR=$(( FLAGS & MASK ))` into two fragments that match no rule, so
an allowed command stopped matching its own allow rule.
3. A backgrounded `cd` runs in a subshell and does not move the parent's cwd,
but `walkCompoundCommand` applied it as a foreground `cd`. For
`cd /tmp & echo {} > settings.json` the write really lands in the original
cwd — where a protected settings file lives — and was being attributed to
/tmp. `splitCompoundCommandSegments` now reports each segment's terminating
operator so the walker can tell the two apart; `splitCompoundCommand` is a
projection of it and is unchanged for every existing caller.
Also covers the backward scan's loop-exhaustion branch (`&` at index 0 or
after only whitespace), which had no test.
|
||
|---|---|---|
| .. | ||
| scripts | ||
| src | ||
| vendor | ||
| index.ts | ||
| package.json | ||
| test-setup.ts | ||
| tsconfig.json | ||
| vitest.config.ts | ||