fix(serve): validate list maxEntries as a positive integer#5719
fix(serve): validate list maxEntries as a positive integer#5719wenshao merged 1 commit intoQwenLM:mainQwenLM/qwen-code:mainfrom he-yufeng:fix/list-validate-max-entrieshe-yufeng/qwen-code:fix/list-validate-max-entriesCopy head branch name to clipboard
Conversation
|
@qwen-code /triage |
qwen-code-ci-bot
left a comment
There was a problem hiding this comment.
Hey @he-yufeng — thanks for the fix! The code change itself looks clean and well-scoped.
However, the PR body is missing several required sections from the PR template:
### How to verify,### Evidence (Before & After),### Tested onunder Reviewer Test Plan## Risk & Scope(currently just## Risk, and missing the sub-items: tradeoff, out of scope, breaking changes)## Linked Issues<details>中文说明</details>— the bilingual summary
Could you fill those in? The test plan especially helps reviewers confirm the fix works as expected without having to run it themselves. Happy to take another look once updated.
✅ Local verification report (maintainer)Built and tested this PR locally to confirm the fix before merge. Environment
Results
Mutation test (the key evidence). To confirm the new test actually guards the gap rather than passing vacuously, I reverted With the unvalidated code, Correctness review
Verdict: LGTM. Minimal, correct, mutation-verified, consistent with the sibling 🇨🇳 中文版✅ 本地验证报告(维护者)合并前在本地构建并测试了本 PR 以确认修复。 环境
结果
变异测试(关键证据)。 为确认新增测试是真的能挡住这个缺口、而不是空过,我把 在没有校验的代码里, 正确性审查
结论:LGTM。 修复最小化、正确、经变异测试验证,与同文件 |
|
Filled in the missing template sections: How to verify / Evidence / Tested on, the full Risk & Scope sub-items, Linked Issues, and the 中文说明. Ready for another look — thanks! |
|
@qwen-code /triage |
|
Thanks for the PR, @he-yufeng! Template looks good ✓ — all required sections present, including the bilingual summary and reviewer test plan. On direction: this is a clean follow-up to #5639, which added the same On approach: the scope is tight — 12 lines of guard + 19 lines of test, touching exactly one method. The implementation is a direct copy of the Moving on to code review and tests. 🔍 中文说明感谢 @he-yufeng 的贡献! 模板完整 ✓ — 所有必填章节都在,包括双语说明和 Reviewer Test Plan。 方向:这是 #5639 的自然延续——那次给 方案:范围很紧——12 行校验 + 19 行测试,只动了一个方法。实现完全复用了 进入代码审查和测试。🔍 — Qwen Code · qwen3.7-max |
Code ReviewThe guard is a faithful mirror of the existing One minor observation (not a blocker): the test asserts Test ResultsWith the guard (PR applied): 77/77 passed ✓ Without the guard (source reverted, test kept): 1 failed, 76 passed — confirming the new test catches the gap. Before/After (tmux): N/A — non–user-visible change in the serve layer (input validation), no TUI surface. Verified entirely by unit test. 中文说明代码审查校验逻辑完全复用了 一个小观察(不是 blocker):测试用了 测试结果有校验(PR 已应用): 77/77 通过 ✓ 去掉校验(回退源码,保留测试): 1 失败、76 通过——确认新测试确实能捕获这个漏洞。 Before/After(tmux): 不适用——serve 层的非用户可见变更(输入校验),无 TUI 界面。完全由单元测试验证。 — Qwen Code · qwen3.7-max |
|
This is a textbook consistency fix. My independent proposal before reading the diff would have been: add Nothing to cut, nothing missing, nothing to bikeshed. Approving. ✅ 中文说明这是一个标准的 consistency fix。 独立提案和 PR 实现完全一致,没有可砍的范围、没有遗漏、没有争议点。通过。✅ — Qwen Code · qwen3.7-max |
qwen-code-ci-bot
left a comment
There was a problem hiding this comment.
LGTM, looks ready to ship. ✅
✅ Runtime-surface follow-up — drove the real
|
| Request | Response |
|---|---|
GET /list?path=small (3 files) |
200 · truncated:false · 3 entries |
GET /list?path=big (2001 files) |
200 · truncated:true · 2000 entries — the cap machinery is intact |
Reachability probe (the part worth adding)
I tried to feed a bad maxEntries from the client — the exact input the new guard exists to reject:
| Client request | Server response |
|---|---|
GET /list?path=big&maxEntries=1 |
200 · truncated:true · count 2000 |
…&maxEntries=Infinity |
200 · truncated:true · count 2000 |
…&maxEntries=NaN · -5 · 1.5 |
200 · truncated:true · count 2000 |
Every value is ignored — the query param never reaches list(). Both call sites (the GET /list route and the ACP qwen/file/list method) hardcode maxEntries server-side, and nowhere in the tree is ListOptions.maxEntries populated from client input. So the new parse_error guard cannot be reached from any external surface — it is pure defense-in-depth on the internal interface contract (faithfully mirroring the readText() guards), and the unit test is the right — and only — place to exercise it.
Verdict
PASS (runtime side) — confirms LGTM. Because the guard never fires on the real path, this PR is externally indistinguishable from base: same responses, breaks nothing. One scoping note for the merge log — the "entire directory is returned" scenario in the PR description is latent (no current caller passes a bad cap; both pass 2001), not a live bug. That makes this a consistency / future-proofing hardening, which is still a perfectly good reason to land it.
🇨🇳 中文版
✅ 运行时层面的补充验证 —— 实际拉起了 qwen serve 守护进程
作为上面那份「测试 + 变异」报告(基于 vitest)的补充。这一份跑的是真实守护进程,在唯一能触达 WorkspaceFileSystem.list() 的运行时入口 —— serve 的 HTTP API —— 上检查这次改动。
方法: 构建 PR head,启动 qwen serve --workspace <tmp> --port 4179 --no-web(回环地址,默认关闭 bearer 鉴权),然后对一个包含「3 文件目录」和「2001 文件目录」的工作区发 GET /list。
接口层无回归
守卫在每次 list 调用时都会跑,用的是硬编码的 MAX_LIST_ENTRIES + 1 = 2001,这是个合法的安全整数,所以会通过、列目录不受影响:
| 请求 | 响应 |
|---|---|
GET /list?path=small(3 文件) |
200 · truncated:false · 3 条 |
GET /list?path=big(2001 文件) |
200 · truncated:true · 2000 条 —— 截断机制完好 |
可达性探针(值得补充的点)
我尝试从客户端塞一个非法的 maxEntries —— 也就是这个新守卫本来要拒绝的输入:
| 客户端请求 | 服务端响应 |
|---|---|
GET /list?path=big&maxEntries=1 |
200 · truncated:true · 共 2000 条 |
…&maxEntries=Infinity |
200 · truncated:true · 共 2000 条 |
…&maxEntries=NaN · -5 · 1.5 |
200 · truncated:true · 共 2000 条 |
每个值都被忽略 —— 这个 query 参数根本传不到 list()。两个调用点(GET /list 路由和 ACP 的 qwen/file/list 方法)都在服务端硬编码 maxEntries,整个代码树里也没有任何地方用客户端输入去填 ListOptions.maxEntries。所以这个新的 parse_error 守卫从任何外部入口都触达不到 —— 它纯粹是对内部接口契约的纵深防御(忠实复刻了 readText() 的守卫),单元测试就是验证它的正确、且唯一的地方。
结论
通过(运行时层面)—— 确认 LGTM。 因为守卫在真实路径上永不触发,本 PR 在外部表现上与 base 无法区分:响应相同、不破坏任何东西。给合并记录补一条 scope 说明 —— PR 描述里「整目录被返回」的场景是潜在的(当前没有任何调用方传非法 cap,两个都传 2001),并非一个活跃 bug。这让本 PR 成为一次一致性 / 未来防护的加固 —— 这依然是个完全成立的合并理由。
Runtime-surface verification on macOS — real qwen serve daemon (loopback, no auth), GET /list + client-maxEntries injection probe. Complements the static/test/mutation report above.
What this PR does
Validates the
maxEntriesoption of the workspace file-systemlist()as a positive integer, rejectingInfinity/NaN/ floats /0/ negatives with aparse_error— the same guardreadText()already applies to itslimitandlineoptions in the same file.Why it's needed
list()caps results withif (entries.length >= opts.maxEntries) break. With an unvalidatedmaxEntriesofInfinityorNaN, that comparison is always false, so the cap is silently ignored and the entire directory is returned; a float,0, or a negative is equally meaningless.readText()in the same file already rejects badlimit/linevalues this way (#5639), so this closes the same validation gap forlist()'smaxEntriesand keeps the two entry points consistent.Reviewer Test Plan
How to verify
Run the workspace file-system suite:
npx vitest run packages/cli/src/serve/fs/workspace-file-system.test.ts. The new testrejects a non-positive-integer maxEntries with parse_errorexercisesInfinity,NaN,0,-1,1.5, andNumber.MAX_SAFE_INTEGER + 1, asserting each is rejected with anFsErrorof kindparse_error. Expected: 77 passed. Stashing only the source guard makes that one test fail (the bad value is no longer rejected), which confirms the test reproduces the gap; restoring the guard makes it pass again. Valid positive-integermaxEntriesis unchanged.Evidence (Before & After)
N/A — non–user-visible change in the serve layer (input validation), no TUI surface. Verified by unit test (see above): 1 failed without the guard, 77 passed with it.
Tested on
Environment (optional)
Unit tests only (
vitest); no runtime / sandbox needed.Risk & Scope
maxEntries(e.g.Infinity, a float,0, a negative) now throwsparse_errorinstead of silently returning the whole directory. Any caller relying on that silent no-cap behavior would now see an error, but that path was already broken (the cap was ignored).list()'smaxEntriesis touched; the otherlist()options and the rest of the file are unchanged.maxEntries), which matchesreadText()'s existing contract.Linked Issues
No issue; mirrors the validation added for
readText()in #5639.中文说明
list()用if (entries.length >= opts.maxEntries) break来限制返回条数,但没有校验maxEntries。传入Infinity/NaN时这个比较恒为 false,上限被静默忽略、整目录被返回;浮点数、0、负数同样没有意义。同文件的readText()早已对limit/line做了同样的校验(#5639),这个 PR 给list()的maxEntries补上一致的「正整数」校验,非法值抛parse_error。新增测试覆盖Infinity/NaN/0/-1/1.5/超安全整数;去掉修复后该测试失败、加回后 77 通过。合法的正整数行为不变。