Repository navigation
fix(core): recognize sed quiet aliases as read-only - #12221
Conversation
|
@qwen-code /review |
|
@qwen-code /triage |
|
@qwen-code /review |
|
@qwen-code /triage |
Verification report — PR 12221
|
| cell | build | oracle | dialog-free (allow) |
concurrency-safe |
|---|---|---|---|---|
| control | source = base blob 3402bcb1 |
getDefaultPermission() / isToolCallConcurrencySafe() over 43 real command strings |
4/43 | 2/43 |
| head | source = head blob 1ff179c8 |
same | 16/43 | 14/43 |
| delta | +12 / −0 | +12 / −0 |
All 12 rows that moved carry a literal --quiet or --silent token; 0 non-alias rows moved (H4-B4). The head arm reproduces bit-identically across two independent builds (43/43 rows, H6-G9). Witness: 04-ab-dialog-and-concurrency-flip.png, and at the TUI level 01-e2e-head-auto-approved-no-dialog.png vs 02-e2e-control-confirmation-dialog.png.
Blast-radius census (pure classifier). 6,156 distinct sed argument lists (every long/short option GNU sed 4.9 documents, hostile near-miss spellings, three option positions, two-option combinations, -- terminator forms) run through both commit blobs: 304 verdict deltas, 304 carry an alias, 0 do not; shapes unknown→read-only ×206 and unknown→write ×98 (the latter is a precision gain, never a laxing). Alias↔-n substitution parity: 752 substitutions, 0 divergences at head; the same parity held on only 454/752 at base, proving the parity claim is load-bearing rather than vacuous. Witness: 03-census-6156-arglists-all-deltas-carry-alias.png.
Real-environment E2E (the user-visible surface). Identical scratch project, identical prompt, identical --approval-mode default (the host's global settings are yolo, which would mask everything, so the mode was pinned explicitly):
| arm | what the real TUI did | witness |
|---|---|---|
| head | ✓ Shell sed --quiet '1,3p' file.txt — executed immediately, output shown, no dialog |
01-e2e-head-auto-approved-no-dialog.png |
| control | ? Shell sed --quiet '1,3p' file.txt → "Allow execution of: 'sed'?" with the 4-option menu, Waiting for user confirmation… |
02-e2e-control-confirmation-dialog.png |
2. Ground truth from the real GNU sed binary
The PR's premise is that --quiet/--silent are aliases of -n; that was verified from the tool itself, not from documentation: 16 scripts × 3 spellings produced byte-identical stdout and exit codes (H2-G1), and 48 real executions left the scratch directory byte-identical (H2-G2). The filesystem oracle is not blind: all 5 intended writes (-i, w, s///w, W, -i.bak) were detected (H2-G3). Critically, --quiet does not defang a writing script — all 5 writers still wrote when prefixed with --quiet (H2-G4) — which is exactly why the classifier must keep them unsafe, and H1-S1/H3-X1/X2 show it does: no command any arm called read-only ever touched the disk, on either arm.
3. Corrections
None required. The linked issue #12215's root-cause account (the guard at the SAFE_SED_OPTION consumer short-circuits --quiet/--silent to unknown; the allowlist entries were unreachable) matched my measurements exactly, including its claim that --line-length=80 was already read-only at base. The bot review rounds on this PR disclose that no finding was ever independently verified (all carried [unverified]), so there was no earlier substantive round to carry forward; this is the first measurement-backed round.
4. Findings
Ordered by severity. None are blocking; all are either scope/completeness observations or pre-existing conditions proven identical on both arms.
F1 — Suggestion (scope): the PR bundles an undeclared, unrelated change.
packages/cli/src/commands/review/lib/narrow-diff.integration.test.ts gains maxRetries: 5, retryDelay: 100 on the afterAll rmSync (commit test(cli): retry transient review repo cleanup). It is not mentioned anywhere in the PR description, touches a different package and subsystem, and addresses Windows teardown flakiness. It is benign — 32/32 green here (H6-G8) and inert on Linux — but the retry path it targets (EPERM/EBUSY) is Windows-only and no runner in this round or in CI exercised it, the same gap the bot's round-3 comment disclosed. Recommend splitting it into its own PR or declaring it in the description; as it stands a reviewer of "recognize sed quiet aliases" is silently also reviewing review-command teardown policy.
Repro: git diff 34246c66..aaba62f0 -- packages/cli/src/commands/review/lib/narrow-diff.integration.test.ts
F2 — Observation (pre-existing, both arms identical): the two SAFE_SED_OPTION copies have drifted.
shell-safety-rules.ts uses /^(?:-[nElrsuz]|--(?:quiet|silent|line-length(?:=.*)?))$/; shellReadOnlyChecker.ts:92 uses /^(?:-[nErsuz]|--(?:quiet|silent))$/ (no l, no --line-length). Measured consequence: sed --line-length=80 'l' f and sed -l 80 'l' f skip the confirmation dialog (AST says read-only) while the scheduler refuses to run them concurrently (legacy says not) — two inconsistent decisions about one command. Identical on control and head (H4-B7), so not introduced here, but it is the same "two copies of one predicate" hazard this PR's fix had to thread through both. Worth a follow-up that unifies the copies.
Repro: node h3-product-paths.mjs <arm> rows sed --line-length=80 'l' file.txt / sed -l 80 'l' file.txt (assertion X9).
F3 — Observation (pre-existing): isCommandNeedsPermission() has no production caller.
Repo-wide grep finds only its own unit test (shell-utils.test.ts:1310,1315). The real dialog decision is ShellTool.getDefaultPermission() → evaluatePermissionFlow() L3 (packages/core/src/core/permissionFlow.ts:72). The function's name invites exactly the misreading I initially made while wiring this harness. Dead-code candidate; harmless today.
Repro: grep -rn isCommandNeedsPermission --include='*.ts' packages | grep -v test
F4 — Observation (pre-existing, over-conservative, safe direction): sibling shapes still prompt.
sed's y/// (transliterate) is absent from SAFE_SED_COMMAND, so sed -n 'y/ab/AB/' f prompts despite being read-only; likewise the long forms --separate, --unbuffered, --null-data, --regexp-extended prompt while their short forms (-s -u -z -E) are allowlisted. Same class of over-conservatism as the bug this PR fixes, one command down; unchanged by this PR and out of its stated scope. Recorded so the next alias PR has the list.
Repro: node h3-product-paths.mjs head rows tagged preExisting / the --separate etc. rows.
F5 — Observation: GNU sed accepts long-option abbreviations the allowlist rejects.
Real sed 4.9 accepts --qui and --sil (getopt_long unambiguous abbreviation) and rejects --s as ambiguous; the anchored allowlist rejects all of them, so they prompt. Safe direction (over-conservative). Noted because any future "accept abbreviations" change must re-run the write-shape matrix of §2 — an abbreviation that expands to a writer would otherwise be admitted by the alias parity argument.
Repro: sed --qui 'p' file.txt; echo $? → 0; classifier verdict unknown.
F6 — Observation (test completeness, non-blocking): 2 of the PR's 8 new assertions do not discriminate this change.
Reverting the fix (source = base blob) kills 6 of the 8 new assertions — the 4 it.each cases in shellAstParser.test.ts and the 2 read-only expects in shellReadOnlyChecker.test.ts — each with a behavioural expected X to be Y message (H5-M3/M4). The 2 surviving ones are the toBe(false) expects for sed --quiet/--silent 'w output.txt' inside the legacy rejects sed with write command block: at base the verdict is unknown, which also maps to false, so they pass with or without the fix (H5-M6/M7, corroborated by the control arm's own measurement). Classification: redundant defence, not a coverage gap and not dead code — they still catch a future regression that made the alias writable. No action required; noted so the kill-count is not over-read as "all 8 prove the diff".
Repro: node h5-mutation-matrix.mjs (matrix in 05-mutation-matrix-5-killed-1-survivor.png).
5. Not covered
- macOS / Windows. This host is Linux/aarch64. The F1 retry path (
EPERM/EBUSYonrmSync) is Windows-specific and was exercised by no runner here or in CI. - Root
integration-testssuite (no-sandbox CLI E2E) not run; the PR's own CI covers it and the targeted suites plus the real-TUI E2E above cover the changed surface. - Non-GNU sed (busybox, BSD/macOS sed) not tested — the PR explicitly scopes to GNU sed, and H2 pins the premise against GNU sed 4.9 only.
autoapproval mode (the LLM classifier that can auto-approve) not exercised; onlydefaultwas, which is the mode where this classification is the deciding input.- Per-commit attribution. The PR has 5 commits including two merges from
main; I verified the aggregatebase..headdiff, not each commit individually. - Full-repo test/lint sweep not run; gates were targeted to the affected workspaces and proven live (planted type error → TS2322 exit 2; planted unused var →
no-unused-varsexit 1). - F4/F5 gaps are measured as observations only; no fix for them was implemented or tested.
6. Methodology
One isolated git worktree at the PR head inside the repo's tmp/, installed with pnpm install --frozen-lockfile (≈6 min, prepare built every package); the control arm was produced by restoring the deleted 2-line guard so the source file's git hash-object equals the base commit's blob, then rebuilding only packages/core (≈64 s), and reverted the same way afterwards (worktree left clean at head). H1 drives both commit blobs directly through Node's native TS type-stripping for the pure-classifier census; H2 executes real sed 4.9 in per-case scratch directories with a sha256+size+name filesystem snapshot oracle and a positive control; H3 imports the built dist and composes the real product functions in the order shell.ts:2191-2219 and coreToolScheduler.ts:1432 use them, executing every command string for real in the same scratch directory; H4 adjudicates the arm pair; H5 diffs per-test vitest JSON between head and the reverted-fix mutant; H6 re-asserts every gate, the reproducibility check and the two TUI captures from their recorded artifacts; H7 sums the assertions. The TUI A/B ran the real built CLI in tmux at 190×46 with --approval-mode default against a scratch project outside the repo, frames captured with tmux capture-pane -e and rasterised by scripts/verify-capture.mjs. Raw logs, harness sources and JSON results are in this run's artifact directory (tmp/pr12221-verify-20260926-220346/); every number quoted above is reproduced by node h7-aggregate.mjs and the per-harness logs.
Evidence
Hosted on the orphan branch assets-pr12221 of wenshao/qwen-code under pr12221/r1/ (commit 13f9f2ce). Every blob sha was verified against the local git hash-object before its URL was referenced here.
01-e2e-head-auto-approved-no-dialog.png
HEAD (aaba62f0), real TUI, --approval-mode default: sed --quiet '1,3p' file.txt executes immediately (✓ Shell …) with no confirmation dialog. The status bar reads "Ask permissions", proving the mode is default, not yolo.
02-e2e-control-confirmation-dialog.png
CONTROL (source file = base blob 3402bcb1), same command, same scratch project, same mode: the 4-option confirmation dialog blocks execution. This is the user-visible bug the PR removes.
03-census-6156-arglists-all-deltas-carry-alias.png
H1 census over 6,156 distinct sed argument lists: all 304 base→head verdict deltas carry a literal --quiet/--silent token (0 non-alias); alias↔-n substitution parity holds 752/752 at head vs 454/752 at base.
04-ab-dialog-and-concurrency-flip.png
H4 A/B adjudication: dialog-free 4 → 16 and concurrency-safe 2 → 14 (+12/−0 each); the 12 writing commands still ask on both arms and every one of them really wrote to disk.
05-mutation-matrix-5-killed-1-survivor.png
H5 vacuity check: reverting the fix (source = base blob) kills 5/5 of the load-bearing new tests with behavioural expected X to be Y messages; one legacy block survives as redundant defence, explained by measurement.
Local maintainer verification round 1 · Linux/aarch64 · GNU sed 4.9 · Node v24.14.0 · harnesses and raw logs in tmp/pr12221-verify-20260926-220346/
|
@qwen-code /triage |
Verification report — PR 12221, round 2 (macOS, real TUI, non-GNU sed)Verdict: Arms. The PR head is
What this round adds
ResultsE1 — the user-visible fix, on macOS (default mode, GNU sed first on E2 — stock macOS sed (BSD). BSD sed has no long options. On the PR ARM the command is auto-approved and then fails harmlessly: E3 — "lost concurrency-safe classification" demonstrated end to end. One model turn emits two parallel calls,
→ E4 —
The PR saves one classifier round trip per alias call, and a writing alias still reaches the classifier. The scripted classifier always answered E5 — census on macOS against real binaries. 5,184 distinct sed command lines were classified by each arm's built core, using the same two predicates the product uses:
All 2,057 lines that either arm auto-approves were then executed via
730 of the 732 newly dialog-free lines changed nothing under either sed. The other 2 are the F1 spellings. GNU sed 4.10 on macOS also reproduces the PR's premise: Tests and build on macOS. FindingsF1 — Pre-existing, macOS, not introduced by this PR; non-blocking. The classifier parses sed's For
This reproduced in the real TUI in default mode on both arms: The PR's relationship to it: head adds exactly 2 spellings to the exposed set ( Candidate fix (measured, not part of this PR): require a numeric value after - if (/^(?:-l|--line-length)$/.test(arg) && !args[++i]) return 'unknown';
+ if (/^(?:-l|--line-length)$/.test(arg) && !/^\d+$/.test(args[++i] ?? '')) return 'unknown';
This was measured at the classifier and unit level only, not end to end. I recommend a separate follow-up issue/PR rather than widening this one. → F2 — Observation (round-1 F1, still open): the undeclared Merge mechanics. The template-gate Not covered
MethodologyTwo fresh
EvidenceHosted on 01 — E1: default mode, GNU sed: dialog on CONTROL vs. auto-run on PR ARM02 — E2: stock macOS BSD sed: PR ARM auto-runs; BSD rejects the long option; nothing written03 — E3: FIFO rendezvous:
|
| 第 1 轮「未覆盖」项 | 本轮做法 | 结果 |
|---|---|---|
| macOS | macOS 26 arm64、Node 24.18.1,真实打包 CLI 跑在 tmux 里,脚本化模型基于仓库自带的 integration-tests/fake-openai-server.ts |
见 E1–E6 |
| 非 GNU sed | 系统自带 /usr/bin/sed(BSD)与 Homebrew GNU sed 4.10,均真实执行 |
E2、E5、F1 |
auto 审批模式 |
统计 CLI 发出的 LLM 分类器请求数 | E4 |
| 端到端并发 | 真实 TUI 中做 FIFO 会合,并带 -n 阳性对照 |
E3 |
| macOS 单测(CI 跳过了 macOS 作业) | PR 的 core 测试文件 + narrow-diff.integration.test.ts,两臂都跑 |
782/782;两臂各 32/32 ×3 |
结果
E1 —— macOS 上用户可见的修复(默认模式,PATH 上优先是 GNU sed)。同一 prompt、同一脚本化模型、--approval-mode default(底栏显示 Ask permissions)。CONTROL 停在 Allow execution of: 'sed'?;PR ARM 直接执行 sed --quiet '1,3p' notes.txt,并输出 alpha / beta / gamma。→ 01-…png
E2 —— macOS 自带 sed(BSD)。 BSD sed 没有长选项。在 PR ARM 上这条命令被自动放行,然后无害地失败:sed: illegal option -- -,exit 1,notes.txt 未变(sha1 等于初始内容,也没有新文件)。CONTROL 会弹确认框,批准后也是同样的失败。所以对没装 GNU sed 的 macOS 用户,这个改动只是去掉了一个反正会失败的命令前面的确认框,不构成安全问题。→ 02-…png
E3 —— 端到端证明「丢失并发安全分类」。 同一轮模型输出两个并行调用:sed --quiet p fifo-a 和 sed --quiet p fifo-b。写端只有在两个读端同时挂上时才同时放行;若 4 s 内只有一个读端,就按 SERIAL 单独放行它。这样工具输出本身就说明了调度器做了什么:
- CONTROL:
SERIAL/SERIAL。fifo-b 的读端在 6691 ms 才挂上,晚于 fifo-a 在 6628 ms 被放行。 - PR ARM:
CONCURRENT/CONCURRENT。两个读端挂上的时间相差 22 ms。 - 阳性对照(
sed -n):两臂都是CONCURRENT,说明这套装置在main上能看到并行。
→ 03-…png
E4 —— auto 模式下分类器的快速通道。 只读判定可以让调用跳过 LLM 安全分类器(coreToolScheduler.ts 的 L5,除非开启 classifyAllShell)。场景按顺序执行三步:
| 步骤 | CONTROL:是否送分类器 | PR ARM:是否送分类器 |
|---|---|---|
sed -n '1,2p' notes.txt |
否 | 否 |
sed --quiet '3,4p' notes.txt |
是 | 否 |
sed --silent 'w copy.txt' notes.txt |
是 | 是(仍受审查) |
| 分类器请求数 | 2 | 1 |
每次别名调用省掉一次分类器往返,而带写操作的别名仍会送分类器。脚本化分类器始终回答 shouldBlock:false,所以这里测的是分类器是否被调用,而不是真实模型会如何判断。→ 04-…png
E5 —— macOS 上对真实二进制的普查。 5,184 条不同的 sed 命令行,由两臂各自构建产物里的 core 分类,所用的两个判定与产品一致:
- 免确认:
hasShellSubstitution→stripShellWrapper→isShellCommandReadOnlyASTInDirectory,同shell.ts的getDefaultPermission(); - 并发安全:
isShellCommandReadOnly,同isToolCallConcurrencySafe()。
凡是任一臂自动放行的 2,057 条,都经 bash -c 在 BSD sed 和 GNU sed 4.10 下各真实执行一次。每次执行都在全新目录里进行,目录中预置了 out 文件,执行前后各做一次 sha256 快照。
| CONTROL | PR ARM | 差值 | |
|---|---|---|---|
| 免确认 | 1,325 | 2,057 | +732 / −0 |
| 并发安全 | 610 | 1,130 | +520 / −0 |
不含 --quiet/--silent 字面量的差异行 |
0 | ||
| 自动放行且实际写盘,GNU sed 4.10(两个判定) | 0 | 0 | 0 |
| 自动放行且实际写盘,BSD sed(免确认) | 11 | 13 | +2,见 F1 |
| 自动放行且实际写盘,BSD sed(并发安全) | 0 | 0 | 0 |
新增免确认的 732 条中,730 条在两种 sed 下都没有改动任何文件;另外 2 条就是 F1 的写法。macOS 上的 GNU sed 4.10 也复现了 PR 的前提:--quiet、--silent 的输出与 -n 逐字节相同。→ 05-…png
macOS 上的测试与构建。 shellAstParser.test.ts + shellReadOnlyChecker.test.ts:782/782,与 PR 描述一致。narrow-diff.integration.test.ts:两臂各 32/32,各跑 3 次。它新增的重试路径(rmSync 的 EPERM/EBUSY)在 macOS 上不会触发,因此这只证明该测试在这里无副作用,不证明重试本身有效。合并后的树完整构建(25 个包的 tsc --build),TypeScript 错误为 0。
发现
F1 —— 既有问题,macOS,不是本 PR 引入的,不阻塞本 PR:分类器按 GNU 的方式解析 sed 的 -l,但 macOS 自带的 sed 是 BSD 版,所以 w 脚本可以冒充 -l 的值混过去。
classifySedCommandSafety 认为 -l 带一个值(SED_VALUE_OPTIONS;shell-safety-rules.ts 中的 if (/^(?:-l|--line-length)$/.test(arg) && !args[++i]))。而 BSD sed 的 -l 是不带值的开关(行缓冲输出),紧随其后的词是脚本。
以 sed -l wimportant.txt 1p 为例:
- 分类器看到的是
-l <值>加脚本1p→ 只读 →allow,不弹框。 - BSD sed 看到的脚本是
wimportant.txt→ 在编译脚本时就截断了important.txt,之后才发现输入文件1p不存在(exit 1)。
在真实 TUI 默认模式下,两臂都复现:important.txt 从 35 字节变成 0,全程没有提示。GNU sed 在这些写法下不会写盘(E5 中为 0),所以 Linux 用户不受影响。旧版并发检查器从未接受过 -l,因此两臂的并发安全计数都是 0。
与本 PR 的关系:head 恰好给暴露集合新增了 2 种写法(sed -l wout --quiet p 和 --silent)。它们的 -n 孪生写法 sed -l wout -n p 在 main 上已经会被自动放行并写盘,所以本 PR 只是增加了写法,没有引入新的问题类别。
候选修复(已实测,不属于本 PR):要求 -l/--line-length 后面跟数字。
- if (/^(?:-l|--line-length)$/.test(arg) && !args[++i]) return 'unknown';
+ if (/^(?:-l|--line-length)$/.test(arg) && !/^\d+$/.test(args[++i] ?? '')) return 'unknown';- 同一普查,head 对 head + 修复:BSD 经自动放行写盘的条数 13 → 0,GNU 保持 0 → 0。
- 代价:247 条回到需要确认,且全部是
-l <非数字>。792 条带数字-l的行、3,978 条不含-l的行,判定变化都是 0。并发判定 1,130 → 1,130。 - 打上修复后跑
shellAstParser+shellReadOnlyChecker+shell-utils+tools/shell测试:1317/1317。
该修复只在分类器层和单测层实测过,没有做端到端验证。建议另开 issue/PR 跟进,而不是扩大本 PR 的范围。→ 06-…png
在自带 sed 的 macOS 上复现:printf 'x\n' > f; sed -l wf 1p; wc -c f → 0 f。
F2 —— 观察(第 1 轮 F1,仍未关闭):narrow-diff.integration.test.ts 的重试改动没有在描述中声明。 它在 macOS 上是绿的(见上),但它针对的 Windows 专属路径,仍只有作者本人的 Windows 运行覆盖过。triage bot 的批准意见也提到同一点。这是规范问题,不是缺陷。
合并流程。 09-18 因模板门禁给出的 CHANGES_REQUESTED 已被 bot 的 APPROVED(09-26 17:06 UTC)取代。reviewDecision 现在是 REVIEW_REQUIRED,还需要一位维护者批准。head 上 CI 实际执行的检查全部通过;Test (macos-latest)、Test (windows-latest) 和 Integration Tests (CLI, No Sandbox) 被 CI 跳过,其中第一项已由本轮在本地补上。
未覆盖
- Windows:本轮没有跑,第 1 轮也没有;只有作者的报告覆盖。
- 其它 sed 实现(busybox、toybox):未测。只真实执行了 BSD
/usr/bin/sed和 GNU 4.10。 auto模式下的真实分类器模型:脚本化分类器始终放行。E4 测的是分类器是否被调用,不是它的判断。- F1 候选修复的端到端验证:只在分类器层和单测层实测,没有用重建的 bundle 跑 TUI。
- 根目录
integration-tests套件:未跑(head 上 CI 的no-AK已通过)。 - 逐 commit 归因:验证的是合并到
main之后的整体 diff,不是 5 个提交逐个验证。
方法
在临时目录建两个全新的 git worktree,执行 pnpm install --frozen-lockfile(其 prepare 会构建所有包),再执行 npm run bundle。TUI 运行使用 env -i、独立的 tmux socket、隔离的 QWEN_HOME(自带 settings.json,selectedType: openai),并把 OPENAI_BASE_URL 指向一个基于仓库 integration-tests/fake-openai-server.ts 的脚本化模型。这个模型:
- 按声明的工具面区分主会话轮次与 side query;
- 通过强制使用的
respond_in_schema工具加分类器系统提示词识别分类器请求; - 记录每次分类器调用审查的是哪条命令。
每次运行的 sed 版本由 PATH 最前面的符号链接决定(gsed 或 /usr/bin/sed)。画面用 tmux capture-pane -e 抓取,再用 scripts/verify-capture.mjs 渲染成图。普查直接 import 两臂各自的 packages/core/dist。候选修复是在 PR ARM 编译产物的副本上测的;它的单测运行临时修改了 PR ARM 的源码,结束后已恢复该文件并核对 blob(1ff179c8)。
证据图
托管在 wenshao/qwen-code 的 assets-pr12221 分支 pr12221/r2/ 目录下。URL 锚定到提交 8e2eef35(在第 1 轮 13f9f2ce 之上快进推送)。每个 URL 都已回读验证:返回 200,且字节与本地文件一致。图见上方英文部分:01 默认模式弹框 vs 自动执行;02 BSD sed 无害失败;03 FIFO 会合并发证明;04 auto 模式分类器请求 2 → 1;05 5,184 条普查;06 既有的 BSD -l 截断与候选修复。
Local maintainer verification round 2 · macOS 26 arm64 · BSD sed + GNU sed 4.10 · Node v24.18.1 · CONTROL main@a480bc78 vs PR ARM main+aaba62f0 = ffa0794a











What this PR does
Recognizes GNU sed's
--quietand--silentaliases as read-only, matching the existing-nclassification. Sed scripts that write output files remain classified as writes. Regression coverage exercises both the AST and legacy shell-safety paths.Why it's needed
The safety allowlist already included
--quietand--silent, but an earlier long-option guard returnedunknownbefore the allowlist could run. Equivalent read-only commands therefore received unnecessary confirmation prompts and lost concurrency-safe classification.Reviewer Test Plan
How to verify
sed --quiet 's/hello/world/' file.txtandsed --silent -n '1,5p' file.txt: both classify as read-only.sed --quiet 'w output.txt' file.txtand the equivalent--silentcommand: both remain writes and still require confirmation.Evidence (Before & After)
Before: the generic long-option guard returned
unknownfor--quietand--silentbefore the existing safe-option allowlist was reached.After: those aliases reach the same safety classification as
-n; write scripts remain unsafe in both classification paths. The focused tests pass 782/782.Tested on
Environment (optional)
Windows, Node.js 24.15.0, npm 11.12.1. The focused core tests (782/782), core typecheck, targeted ESLint, and CLI workspace build passed. On the current PR head, GitHub Linux tests, no-AK integration, lint/static, Linux and Windows desktop shell, no-flicker, and TUI parity checks pass. The separate
review-prautomation failed because its runner could not load the PR review cache and reported unhealthy directories; retrying that job requires repository-admin permissions.Risk & Scope
wscript case.Linked Issues
Fixes #12215
中文说明
这个 PR 做了什么
将 GNU sed 的
--quiet和--silent别名识别为只读,与现有-n分类一致。会写出文件的 sed 脚本仍分类为写操作。回归覆盖 AST 和旧版 shell safety 两条路径。为什么需要
现有安全 allowlist 已包含
--quiet和--silent,但更早执行的长选项检查会先返回unknown,使 allowlist 无法生效。因此等价的只读命令会触发多余确认,并失去并发安全分类。审查者测试计划
如何验证
sed --quiet 's/hello/world/' file.txt和sed --silent -n '1,5p' file.txt:两者都应分类为只读。sed --quiet 'w output.txt' file.txt及对应的--silent命令:两者仍是写操作并需要确认。前后证据
修改前:通用长选项检查会在现有安全选项 allowlist 之前将
--quiet和--silent判为unknown。修改后:这两个别名与
-n使用相同的安全分类;AST 和旧版路径中的写脚本测试仍保持不安全。定向测试 782/782 通过。测试平台
环境(可选)
Windows、Node.js 24.15.0、npm 11.12.1。core 定向测试(782/782)、core 类型检查、相关 ESLint 检查和 CLI workspace 构建通过。当前 PR head 的 GitHub Linux tests、no-AK integration、lint/static、Linux/Windows desktop shell、no-flicker 与 TUI parity checks 均通过。独立的
review-pr自动审查因 runner 找不到 PR review cache 且 runner 目录不健康而失败;重跑该 job 需要仓库管理员权限。风险与范围
w写脚本覆盖。关联 Issue
Fixes #12215