Repository navigation
fix(core): track attached stdout fd redirects - #5317
Conversation
|
Independently verified this one — it's a real permission-gate bypass and the fix is correct. 👍 Repro (by construction): the tokenizer splits only on unquoted whitespace, so Optional follow-up (non-blocking, low priority): the regex still ignores arbitrary fds, so LGTM to merge once you flip it out of draft. Thanks for the catch! 中文说明我独立复现验证过了 —— 这是个真实的权限闸门绕过,修复正确。👍 复现(从构造上证明):tokenizer 只按非引号空白切词,所以 可选后续(非阻塞、低优先级):正则仍然忽略任意 fd,所以 翻出 draft 后即可合并,LGTM。感谢发现这个问题! |
|
@qwen-code /triage |
Maintainer verification — local build + before/after semantics harnessVerified PR head 1. Build / tests / typecheck / lint (PR code)
2. Before/after behavioral harness✅ Intended fix — attached stdout-fd redirects are now tracked as writes (were silently missed before):
✅ No regressions — unchanged before↔after: spaced 3. One finding (non-blocking): fd-dup over-detection is extended to
|
| Command | before | after |
|---|---|---|
echo hi 1>&2 |
[] |
write_file /repo/&2 |
echo hi 1>&- |
[] |
write_file /repo/&- |
This is pre-existing behavior, not introduced by this PR. Root cause is looksLikePath('&2') === true (it only rejects $-vars, flags, pure ints, braces, URLs — not &N fd-dups). The same quirk already exists on main for the other operators and is unchanged here:
echo hi 2>&1→write_file /repo/&1(same before & after)echo hi >&2→write_file /repo/&2(same before & after)
So the PR extends an existing looksLikePath limitation from >& / 2>& to 1>&. The direction is safe for a permission gate — it's over-detection on a nonsensical path, never under-detection, so it can't let a real write slip past; worst case is a spurious permission consideration on a 1>&2 command.
Suggested follow-up (separate, optional): skip &-prefixed fd-dup targets (&1, &2, &-) in extractRedirects / looksLikePath so the new 1>& and the pre-existing 2>& / >& cases are all cleaned up together.
Verdict
The core fix is correct and closes a real under-detection gap — attached 1>file / 1>>file writes were invisible to the permission analyzer while the spaced forms were tracked, so a command could write to a sensitive path (e.g. 1>.qwen/settings.json) without the write being seen. Zero regressions; the absolute-vs-relative cwd-dependency handling is correct. The only side effect (fd-dup → spurious write) is pre-existing behavior extended in the safe direction. ✅ Safe to merge. The fd-dup over-detection is worth a small follow-up but is not a blocker.
Verified by maintainer @wenshao: local build + unit suite + a before/after harness running the real semantics functions from both origin/main and the PR head (22bf5c4) over a 20-command corpus.
中文版(点击展开)
维护者验证 —— 本地构建 + 前后对比语义 harness
在 macOS(Darwin arm64,Node v22.22.2)上,针对 origin/main(f5761ac)验证了 PR head 22bf5c4。这块改动落在 shell 权限分析边界上(extractShellOperations 给写/读权限门控供数),所以除单元测试外,我还跑了一个前后对比 harness——用真实的 extractShellOperations / extractShellOperationsAcrossCommand 过一份对抗性命令语料。
1. 构建 / 测试 / 类型检查 / lint(PR 代码)
| 检查 | 结果 |
|---|---|
shell-semantics.test.ts |
✅ 103 通过(4 新 + 99 既有) |
tsc --noEmit(core) |
✅ exit 0,完全干净 |
npm run build(core) |
✅ 通过 |
eslint + prettier --check(改动文件) |
✅ 干净 |
| PR CI | ✅ macOS / Windows / Linux 全绿 |
2. 前后对比行为 harness
✅ 目标修复 —— 紧贴的 stdout-fd 重定向现在被识别为写操作(此前会被静默漏掉):
| 命令 | before | after |
|---|---|---|
echo hi 1>.qwen/settings.json |
[](无 op) |
write_file /repo/.qwen/settings.json |
echo hi 1>>.qwen/settings.json |
[] |
write_file …/.qwen/settings.json |
echo hi 1>/etc/passwd |
[] |
write_file /etc/passwd |
cd "$X" && echo hi 1>/tmp/out.txt |
[] |
write_file /tmp/out.txt · pathMayDependOnCwd:false(正确判为绝对路径) |
cd "$X" && echo hi 1>rel.txt |
[] |
write_file /repo/rel.txt · pathMayDependOnCwd:true(正确判为相对 cwd) |
✅ 无回归 —— before↔after 一致:带空格的 1> file / 1>> file、紧贴的 >file / >>file、2>file、2>/dev/null(忽略)、&>file、< file、纯 echo hi。
3. 一个发现(非阻塞):fd-dup 的过度识别被扩展到了 1>&
修复让 fd 1 上的 fd 复制也匹配了重定向正则,进而落到一个虚假的写 op:
| 命令 | before | after |
|---|---|---|
echo hi 1>&2 |
[] |
write_file /repo/&2 |
echo hi 1>&- |
[] |
write_file /repo/&- |
这是既有行为,并非本 PR 引入。 根因是 looksLikePath('&2') === true(它只排除 $ 变量、flag、纯整数、花括号、URL,不排除 &N 这种 fd 复制)。同样的怪癖在 main 上对其他操作符早已存在,本 PR 未改变:
echo hi 2>&1→write_file /repo/&1(前后一致)echo hi >&2→write_file /repo/&2(前后一致)
所以本 PR 只是把既有的 looksLikePath 局限从 >& / 2>& 扩展到了 1>&。方向对权限门控而言是安全的——属于过度识别(在一个无意义路径上多报一个写),绝不会漏报,因此不会让真实的写绕过;最坏情况只是对 1>&2 这类命令多做一次权限判断。
建议的后续(独立、可选): 在 extractRedirects / looksLikePath 里跳过 & 开头的 fd-dup 目标(&1、&2、&-),把新的 1>& 和既有的 2>& / >& 一起清理掉。
结论
核心修复正确,且堵上了一个真实的漏报缺口——紧贴的 1>file / 1>>file 写此前对权限分析器不可见,而带空格的形式却被识别,于是一条命令可以写入敏感路径(如 1>.qwen/settings.json)而该写操作不被看见。无回归;绝对 vs 相对的 cwd 依赖处理正确。唯一的副作用(fd-dup → 虚假写)是既有行为在安全方向上的扩展。✅ 可以合并。 fd-dup 过度识别值得一个小的后续修复,但不构成本 PR 的阻塞项。
维护者 @wenshao 验证:本地构建 + 单元测试 + 一个前后对比 harness(分别从 origin/main 与 PR head 22bf5c4 加载真实语义函数)跑 20 条命令的对抗性语料。
Fixes #5316
Summary
Testing
AI Assistance Disclosure
I used Codex to review the changes, sanity-check the implementation against existing patterns, and help spot potential edge cases.