qwen-code/packages/core
chinesepowered 548863d9f4
fix(core): treat a bare & as a command boundary in splitCompoundCommand (#7864)
* 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.
2026-07-31 13:17:24 +00:00
..
scripts
src fix(core): treat a bare & as a command boundary in splitCompoundCommand (#7864) 2026-07-31 13:17:24 +00:00
vendor feat test tool permissions 2026-03-10 16:30:22 +08:00
index.ts
package.json chore(release): v0.21.2 (#8200) 2026-07-31 05:22:46 +00:00
test-setup.ts feat(memory): managed auto-memory and auto-dream system (#3087) 2026-04-16 20:05:45 +08:00
tsconfig.json fix: upgrade @lydell/node-pty to 1.2.0-beta.10 to fix PTY FD leak 2026-04-01 07:55:56 +08:00
vitest.config.ts test: raise timeout ceiling for I/O-bound tests flaky under CI contention (#7230) 2026-07-19 12:03:48 +00:00