fix(cli): validate ACP file read windows - #5482
Conversation
|
@qwen-code /triage |
qwen-code-ci-bot
left a comment
There was a problem hiding this comment.
Hi @tt-a1i — thanks for picking up #5481! The change itself looks focused and well-scoped from the diff, but the PR body doesn't follow the pull request template, which helps reviewers (and CI triage) assess PRs consistently.
Missing or renamed sections:
## What this PR does— currently## Summary. The template asks for prose, not a bullet list of file-level changes.## Why it's needed— missing entirely. The linked issue #5481 has the motivation, but the PR body should carry it too.## Reviewer Test Plan— currently## Test Plan. The template expects three subsections:### How to verify— steps a reviewer can follow to confirm the fix### Evidence (Before & After)— expected vs observed behavior (N/A is fine for non-TUI changes like this one)### Tested on— the OS table (🍏/🪟/🐧)
## Risk & Scope— missing. Even a one-liner ("low risk — validation only, no behavior change for valid inputs") helps.## Linked Issues—Fixes #5481is in the summary body but should be in its own section for auto-close.<details>中文说明</details>— missing.
Could you restructure the PR body to match the template? The content you have is good — it just needs to be in the right sections. Happy to help if anything is unclear.
中文说明
@tt-a1i 你好——感谢认领 #5481!从 diff 来看改动聚焦且范围合理,但 PR 正文没有按照 PR 模板 填写,这会影响审查效率和 CI 分流。
缺少或重命名的章节:
## What this PR does— 当前写的是## Summary,模板要求用散文描述而非文件级变更列表。## Why it's needed— 完全缺失。关联 issue #5481 有动机说明,但 PR 正文也应包含。## Reviewer Test Plan— 当前写的是## Test Plan,模板要求三个子章节:### How to verify— 审查者可以复现的步骤### Evidence (Before & After)— 预期 vs 实际行为(非 TUI 变更可写 N/A)### Tested on— 操作系统测试表格(🍏/🪟/🐧)
## Risk & Scope— 缺失。即使一句话("低风险——仅增加校验,合法输入行为不变")也有帮助。## Linked Issues—Fixes #5481写在 summary 里,应单独成节以触发自动关闭。<details>中文说明</details>— 缺失。
请按模板重新组织 PR 正文,内容本身没问题,只是需要放到正确的章节里。
— Qwen Code · qwen3.7-max
|
updated the PR description to match the template. thanks. |
|
@qwen-code /triage |
| // shared module to avoid churning the 2987-line server.ts near merge; a | ||
| // follow-up may lift all three to a `serve/limits.ts`.) | ||
| const MAX_NAME_LENGTH = 256; | ||
| const MAX_FILE_LINE_LIMIT = 2000; |
There was a problem hiding this comment.
[Suggestion] MAX_FILE_LINE_LIMIT = 2000 is a duplicate of the exported constant at packages/cli/src/serve/routes/workspaceFileRead.ts:37. If the REST route's value changes (e.g., raised to 3000), the ACP boundary silently retains 2000 — divergent enforcement on the same logical constraint.
| const MAX_FILE_LINE_LIMIT = 2000; | |
| import { MAX_FILE_LINE_LIMIT } from '../routes/workspaceFileRead.js'; |
(Remove the local const MAX_FILE_LINE_LIMIT = 2000; declaration.)
— DeepSeek/deepseek-v4-pro via Qwen Code /review
| if (id !== undefined) | ||
| conn.sendConn( | ||
| error( | ||
| id, |
There was a problem hiding this comment.
[Suggestion] The new parameter validation rejects invalid params with INVALID_PARAMS errors, but produces zero server-side log output. If a client sends malformed params (e.g., maxBytes: 0), the file read fails silently from an operations perspective — there is no way to distinguish "request never arrived" from "request arrived but had invalid params" in server logs.
Consider adding this.logger?.warn(...) before each conn.sendConn(error(...)) call in the new validation blocks, logging the rejected parameter name, the invalid value, and the client ID.
— DeepSeek/deepseek-v4-pro via Qwen Code /review
|
Re-run after rebase onto Template ✓ — all sections present and properly structured. Direction: defensive parameter validation at the ACP transport boundary, fixing #5481. Malformed Approach: 2 files, +326/-25. One helper function, two validation insertion points, thorough tests. Scope is tight — no drive-by changes, no scope creep. Moving on to code review. 🔍 中文说明Rebase 到 模板 ✓ —— 所有章节齐全且结构正确。 方向:在 ACP transport 边界做防御性参数校验,修复 #5481。畸形的 方案:2 个文件,+326/-25。一个辅助函数,两处校验插入点,测试全面。范围紧凑 —— 无夹带改动,无范围蔓延。 进入代码审查 🔍 — Qwen Code · qwen3.7-max |
Code ReviewRe-run after rebase onto
Validation logic is correct across all edge cases: The error handling pattern (null-check + early return with Test quality is solid:
No issues found. Code is straightforward, tests are comprehensive. TestingThis PR adds transport-layer JSON-RPC parameter validation — no TUI or user-visible behavior change. Validation is only reachable through ACP HTTP requests, not the CLI prompt, so tmux testing is not applicable here. The unit test suite is the correct verification path. Focused test run (
|
|
Re-run after rebase onto Stepping back: this PR does exactly what #5481 asks for, nothing more. The implementation is the most straightforward path — a single helper function, inline validation at the dispatch boundary, and thorough tests. My independent proposal before reading the diff would have been nearly identical: add range/type checks before the The code is boring in the best way — no abstractions, no cleverness, just careful parameter checking that matches the existing code style. The 102-test suite passing with no regressions confirms nothing was broken. Lint and typecheck are clean. This is a clean fix from a first-time contributor. Approving. ✅ 中文说明Rebase 到 回顾:本 PR 完全按照 #5481 的要求实现,没有多余内容。实现路径最直接 —— 一个辅助函数、在 dispatch 边界内联校验、测试全面。我在阅读 diff 前的独立方案几乎一致:在 代码朴素到最好 —— 无抽象、无花哨,只是仔细的参数校验,与现有代码风格一致。102 个测试全部通过,无回归。Lint 和 Typecheck 均无问题。 这是一位首次贡献者的干净修复。批准 ✅ — Qwen Code · qwen3.7-max |
qwen-code-ci-bot
left a comment
There was a problem hiding this comment.
LGTM, looks ready to ship. ✅
✅ Local real-build verification (maintainer) — tmux A/B against the real
|
_qwen/file/read |
PRE-FIX 56e76e7e |
FIXED e2604e88 |
|---|---|---|
maxBytes:10,line:1,limit:1 (valid) |
✅ "hello worl" |
✅ "hello worl" |
maxBytes:0 |
"" (silent) |
✅ -32602 `maxBytes` must be a positive integer in [1, 262144] |
maxBytes:1.5 |
"h" (silent truncation) |
✅ -32602 |
limit:-5 |
"" (silent) |
✅ -32602 `limit` must be a positive integer in [1, 2000] |
limit:5000 on a 3000-line file |
✅ -32602 |
|
line:0 / line:1.5 |
-32602 (already validated by readText) |
-32602 |
_qwen/file/read_bytes |
PRE-FIX | FIXED |
|---|---|---|
offset:0,maxBytes:5 (valid) |
✅ bytes hello |
✅ bytes hello |
offset:-1 / 1.5, maxBytes:0 / 262145 |
-32602 (already validated by readBytesWindow) |
-32602 |
Findings
⚠️ The fix's real protective value is concentrated in_qwen/file/read'smaxBytes+limit. Pre-fix silently returned empty / truncated content for0/ negative / fractional values (no error at all — confirmed silent in the server log), and bypassed the 2000-linelimitcap (limit:5000→ all 3001 lines returned = response-size amplification). The fix turns all of these into a clean-32602with a precise message.- 🔍
_qwen/file/read_bytes(offset,maxBytes) and_qwen/file/read'slinewere already validated downstream (readText/readBytesWindowreject with-32602). For these params the PR is defense-in-depth — it rejects earlier (beforefs.resolve) and unifies the message wording. Harmless and tidier, but not a behavior change. - 🔍
maxBytesis not a size-DoS vector forread:readTextindependently caps by file size ("file of N bytes exceeds read cap of 262144 bytes") — verified on a 300 KB file, where both builds reject. So themaxBytesvalidation is a correctness fix (reject0/ negative / fractional), not a size cap. The genuine amplification fix is thelimitcap above. - 🔍 String params (
maxBytes:"1"): pre-fix silently coerced to the default (full read); fixed rejects. Minor tightening, consistent with the rest.
Supplementary (deterministic)
- Unit (fixed):
transport.test.ts"file methods" → 34 passed. - Mutation: ran the PR's
transport.test.tsagainst the pre-fixdispatch.ts→ 25 of 27 invalid-param assertions FAIL → the new tests genuinely guard the validation (non-vacuous).
Tested on macOS (Node v22.22.2); CI covers Linux/Windows.
🇨🇳 中文版(点击展开)
✅ 维护者本地真实构建验证 —— 用 tmux 对真实 qwen serve 的 ACP HTTP 端点做 A/B
结论:PASS —— 建议合并。 我从源码构建了两侧,在 tmux 里跑起真实的 qwen serve /acp 套接字,并端到端地(initialize → SSE → 方法调用)对真实文件调用 _qwen/file/read / _qwen/file/read_bytes。修复后:所有越界 / 非整数的 window 参数都在碰文件系统之前就被干净地以 -32602 拒绝;而修复前的构建要么静默读错、要么绕过行数上限。
方法
- FIXED
dist/cli.js@e2604e88(PR head)对比 PRE-FIX @56e76e7e(origin/main)—— 两者都npm run build(exit 0)。 - 在隔离的 tmux 会话里跑
qwen serve --hostname 127.0.0.1(loopback,免鉴权),绑定到一个含真实文件的临时工作区。 - 一个独立的 Node 客户端走真实套接字:
POST /acpinitialize →GET /acpSSE →POST /acp方法调用,按 id 从 SSE 流上读回响应帧。不 import 仓库代码 —— 这正是一个 ACP 客户端实际走的链路。
真实 /acp 套接字上的 A/B(节选)
_qwen/file/read |
PRE-FIX 56e76e7e |
FIXED e2604e88 |
|---|---|---|
maxBytes:10,line:1,limit:1(合法) |
✅ "hello worl" |
✅ "hello worl" |
maxBytes:0 |
""(静默) |
✅ -32602 |
maxBytes:1.5 |
"h"(静默截断) |
✅ -32602 |
limit:-5 |
""(静默) |
✅ -32602 |
在 3000 行文件上 limit:5000 |
✅ -32602 |
|
line:0 / line:1.5 |
-32602(readText 已校验) |
-32602 |
_qwen/file/read_bytes |
PRE-FIX | FIXED |
|---|---|---|
offset:0,maxBytes:5(合法) |
✅ 字节 hello |
✅ 字节 hello |
offset:-1 / 1.5、maxBytes:0 / 262145 |
-32602(readBytesWindow 已校验) |
-32602 |
发现
⚠️ 修复的真正保护价值集中在_qwen/file/read的maxBytes+limit。 修复前对0/负数/小数会静默返回空 / 截断内容(完全没有报错 —— 我在服务端日志里确认是静默的),并且绕过了 2000 行的limit上限(limit:5000→ 返回全部 3001 行 = 响应体放大)。修复后这些都变成带精确提示的干净-32602。- 🔍
_qwen/file/read_bytes(offset、maxBytes)以及_qwen/file/read的line本来就在下游被校验(readText/readBytesWindow已用-32602拒绝)。对这些参数,本 PR 属于纵深防御 —— 把拒绝提前到fs.resolve之前,并统一了提示措辞。无害、更整洁,但不是行为改变。 - 🔍 对
read而言maxBytes不是 size-DoS 向量:readText会按文件大小独立设限("file of N bytes exceeds read cap of 262144 bytes")—— 在 300 KB 文件上验证过,两侧都拒绝。所以maxBytes校验是正确性修复(拒绝0/负数/小数),不是大小上限。真正的放大修复是上面的limit。 - 🔍 字符串参数(
maxBytes:"1"):修复前静默退化为默认值(整文件读取);修复后拒绝。轻微收紧,与其余一致。
补充(确定性)
- 单测(fixed):
transport.test.ts"file methods" → 34 通过。 - 变异测试: 用 PR 的
transport.test.ts跑修复前的dispatch.ts→ 27 个非法参数断言里有 25 个 FAIL → 新测试确实守护了该校验(非空泛)。
测试环境:macOS(Node v22.22.2);Linux/Windows 由 CI 覆盖。
Local real-build verification by the maintainer · FIXED e2604e88 vs base 56e76e7e · macOS / Node 22.22.2.
|
📌 Same maintainer note — full version on #5509. Part of the strict-validation series. Heads-up: much of this path is already enforced downstream — invalid ACP read params already surface as 中文说明📌 同一条维护者说明 —— 完整版见 #5509。 属于严格校验系列。提醒:这条路径大部分在下游已经有校验了 —— 非法的 ACP read 参数已经会经 FS 层的 |
|
Heads-up: this PR now conflicts with 中文说明提示:近期一些 PR 合并后,本 PR 与 |
e2604e8 to
69502a3
Compare
|
rebased onto latest main and pushed. conflict is gone on my side; local checks passed: @qwen-code /triage |
|
@qwen-code /triage |
|
@tt-a1i — also a note on triage: 中文说明@tt-a1i —— 顺带说明 triage: |
qwen-code-ci-bot
left a comment
There was a problem hiding this comment.
LGTM, looks ready to ship. ✅
✅ Maintainer verification — real end-to-end test (Linux)Verified this PR locally with the real Environment: isolated Summary
A/B proof — the new tests actually guard the fix (and reproduce #5481)I neutralized The 25 failures are exactly the "rejects invalid" cases; the "valid" and "omitted-default" cases keep passing. This confirms the tests are meaningful (not vacuously green) and demonstrates the precise bug from #5481 — e.g. Real daemon end-to-end (the "real test")Booted the actual binary inside a tmux session against a real file on disk (no mocks — real TCP, real HTTP, real SSE, real filesystem): A real HTTP/JSON-RPC client Server-side, the daemon pane logged the matching
Minor (non-blocking) observationIn both handlers ConclusionThe change brings ACP 🇨🇳 中文版(点击展开)✅ 维护者验证 —— 真实端到端测试(Linux)我在本地用真实的 环境: 独立 汇总
A/B 证明 —— 新测试确实在守护这个修复(并复现 #5481)我把 这 25 个失败正好是"拒绝非法参数"用例,而"合法"与"省略默认值"用例始终通过。这说明测试是有意义的(不是空过),并复现了 #5481 的确切 bug —— 例如 真实守护进程端到端("真实测试"本体)在 tmux 会话里启动真实二进制,针对磁盘上的真实文件运行(无 mock —— 真实 TCP / HTTP / SSE / 文件系统): 真实 HTTP/JSON-RPC 客户端
守护进程侧日志记录了连接 次要(不阻塞)观察两个 handler 都在窗口参数校验之前就创建了 结论此改动使 ACP Verification was scripted against a real loopback daemon; no model/auth involved (file-read methods don't touch the LLM). Test artifacts were run in a throwaway worktree + temp workspace. |
What this PR does
This PR validates ACP
_qwen/file/readwindow parameters before calling the file system, and validates_qwen/file/read_bytesoffset and maxBytes parameters before byte reads. It adds transport coverage for valid params, omitted defaults, and invalid window values.Why it's needed
ACP file reads should reject malformed window values at the transport boundary instead of letting partial parses or invalid numeric values reach file-system logic. That keeps the method contract predictable for IDE and HTTP callers.
Reviewer Test Plan
How to verify
Run the focused ACP HTTP transport file-method tests. Confirm valid and omitted window params still work, while invalid
startLine,limit,offset, ormaxBytesvalues return validation errors before a file read is attempted.Evidence (Before & After)
Before: malformed ACP file-read window params could pass through to the file-system call path.
After: invalid window params are rejected at dispatch/transport validation; valid and omitted defaults continue to work.
Tested on
Environment (optional)
Local validation used the CLI package test runner for
src/serve/acpHttp/transport.test.ts.Risk & Scope
Linked Issues
Fixes #5481
Testing
npm --workspace packages/cli run test -- src/serve/acpHttp/transport.test.ts -t "file methods"npm --workspace packages/cli run test -- src/serve/acpHttp/transport.test.tsnpx prettier --check packages/cli/src/serve/acpHttp/dispatch.ts packages/cli/src/serve/acpHttp/transport.test.tsnpx eslint packages/cli/src/serve/acpHttp/dispatch.ts packages/cli/src/serve/acpHttp/transport.test.tsnpm --workspace packages/cli run typechecknpm run build -- --cli-onlygit diff --checkAI Assistance Disclosure
I used Codex to review the changes, sanity-check the implementation against existing patterns, and help spot potential edge cases.
中文说明
这个 PR 做了什么
这个 PR 在调用文件系统前校验 ACP
_qwen/file/read的窗口参数,并在 byte read 前校验_qwen/file/read_bytes的 offset 和 maxBytes 参数。同时补充了 valid params、默认省略值和非法窗口参数的 transport 测试。为什么需要
ACP 文件读取应该在 transport 边界拒绝畸形窗口值,而不是让部分解析或非法数字进入文件系统逻辑。这样 IDE 和 HTTP 调用方看到的方法合约更稳定。
Reviewer 测试计划
如何验证
运行聚焦的 ACP HTTP transport file-method 测试。确认合法和省略的窗口参数仍然可用,非法
startLine、limit、offset或maxBytes会在读取文件前返回校验错误。前后证据
之前:畸形 ACP file-read 窗口参数可能进入文件系统调用路径。
之后:非法窗口参数会在 dispatch/transport 校验处被拒绝;合法值和省略默认值行为不变。
测试平台
环境
本地验证使用 CLI package test runner,目标测试为
src/serve/acpHttp/transport.test.ts。风险与范围
关联 Issue
Fixes #5481
测试
npm --workspace packages/cli run test -- src/serve/acpHttp/transport.test.ts -t "file methods"npm --workspace packages/cli run test -- src/serve/acpHttp/transport.test.tsnpx prettier --check packages/cli/src/serve/acpHttp/dispatch.ts packages/cli/src/serve/acpHttp/transport.test.tsnpx eslint packages/cli/src/serve/acpHttp/dispatch.ts packages/cli/src/serve/acpHttp/transport.test.tsnpm --workspace packages/cli run typechecknpm run build -- --cli-onlygit diff --checkAI Assistance Disclosure
I used Codex to review the changes, sanity-check the implementation against existing patterns, and help spot potential edge cases.