fix(core): enforce shell directory workspace boundary#5454
Conversation
wenshao
left a comment
There was a problem hiding this comment.
No issues found. LGTM! ✅
— qwen3.7-max via Qwen Code /review
|
@qwen-code /triage |
|
Thanks for the PR! Template: content is all there under reorganized headings (Summary / Validation / Fixes #5453). Missing the formal "Risk & Scope" section and 中文说明 — not blocking for a focused security fix, but worth including next time. On direction: this is a clear-cut security fix. The shell tool's On approach: the fix is exactly right — 中文说明感谢贡献! 模板:内容齐全,只是标题用了 Summary/Validation 而非模板规定的格式,缺少 "Risk & Scope" 和中文说明部分。对于聚焦的安全修复不阻塞,但下次建议补全。 方向:这是一个明确的安全修复。shell 工具的 方案:修复方式完全正确—— — Qwen Code · qwen3.7-max |
Code ReviewThe fix is clean and minimal. Two observations from reading the code: Independent proposal: I would have done exactly the same thing — replace the bespoke Diff comparison: the PR's implementation matches my proposal. The production change is 4 lines deleted, 2 lines added — swaps the manual loop+startsWith for a single method call. The regression test properly mocks One note on the test: the mock's default TestingUnit tests (240 tests, all pass)Note: the new regression test from this PR ( Real-scenario verificationDirect verification of Before/after comparison using the same test script: All cases behave correctly with the fix. The sibling-prefix bug is resolved, and legitimate child paths still work. 中文说明代码审查修复简洁且最小化。 独立方案: 我会做完全一样的事——用已有的 Diff 对比: PR 的实现与我的方案一致。生产代码改动 4 行删除、2 行新增——将手动循环+startsWith 替换为单个方法调用。回归测试正确 mock 了 测试
— Qwen Code · qwen3.7-max |
|
This is a textbook minimal security fix. The PR does exactly what I would have done: swap the bespoke The before/after verification confirms the bug is real and the fix works — sibling-prefix paths get rejected, child paths and the workspace itself still work. All 240 existing unit tests pass. The one thing I'd flag for the author's awareness (not a blocker): the mock helper Approving. ✅ 中文说明这是一个教科书式的最小化安全修复。PR 的做法与我的独立方案完全一致:将临时的 前后对比验证确认了 bug 真实存在且修复有效——兄弟前缀路径被拒绝,子路径和工作区本身仍然正常工作。全部 240 个现有单元测试通过。 一个非阻塞的观察:mock 辅助函数 批准 ✅ — 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 verification — safe to mergeI verified this PR locally before merging. Tested PR head
Results — all green
The fix is load-bearing (before / after)Running the new regression test against the pre-fix That is exactly the bug: the old One nuance worth flagging (non-blocking)The committed regression test mocks the very gate it depends on:
So the committed test proves that To close that gap, I ran a throwaway end-to-end test using a real
The real behavior is correct because VerdictThe change is correct, minimal, and well-scoped; the bug is real and the fix closes it. All checks pass. LGTM 👍 🇨🇳 中文版(点击展开)✅ 本地验证结论 —— 可以合并合并前我在本地对该 PR 做了验证。在一个独立的 git worktree 中、基于 PR 头
验证结果 —— 全部通过
该修复确实是“关键且必要的”(前后对比)把新增的回归测试跑在修复前的 这正是该 bug:旧的 一个值得提醒的点(不阻塞合并)提交的回归测试把它所依赖的校验函数 mock 掉了:
因此该测试只能证明 为补上这个空白,我用真实的
真实行为之所以正确,是因为 结论改动正确、精简、范围清晰;bug 真实存在,且此修复确实将其闭合。所有检查均通过。LGTM 👍 |
wenshao
left a comment
There was a problem hiding this comment.
No issues found — a correct, minimal security fix. It closes a real sibling-prefix bypass (/tmp/project-other passed the old startsWith('/tmp/project') check) by delegating to the canonical isPathWithinWorkspace (realpath canonicalization + path.relative boundary), matching the 6 other workspace-bounded tools. Verified: it fails closed, resolves ../symlinks (both the child and the registered workspace dirs are realpath'd), and the new test genuinely guards the fix (it fails under the old startsWith code). 241/241 shell tests pass; CI green.
Non-blocking follow-up (separate, out of this diff): the shared test mock packages/core/src/test-utils/mockWorkspaceContext.ts still implements isPathWithinWorkspace with the same buggy startsWith semantics this PR removes — which is why the new test must override it with mockReturnValue(false). Pointing that mock at the real (exported, pure) isPathWithinRoot would let the test use the default mock honestly and stop other tools' tests from silently enshrining the sibling-prefix bug.
LGTM ✅
中文
无问题 —— 一个正确、最小的安全修复。它通过委托给规范的 isPathWithinWorkspace(realpath 归一化 + path.relative 边界判断,和另外 6 个受工作区约束的工具一致),堵住了一个真实的同前缀绕过(/tmp/project-other 能通过旧的 startsWith('/tmp/project') 检查)。已验证:它 fail-closed,会解析 ../符号链接(child 和已注册的工作区目录两侧都做了 realpath),且新测试确实守护了修复(在旧 startsWith 代码下会失败)。shell 测试 241/241 通过,CI 绿。
非阻塞的后续(独立、不在本 diff 内):共享测试 mock packages/core/src/test-utils/mockWorkspaceContext.ts 里的 isPathWithinWorkspace 仍是本 PR 要删掉的那套有 bug 的 startsWith 语义 —— 这正是新测试不得不用 mockReturnValue(false) 去覆盖它的原因。把那个 mock 改成委托给真正的(已导出、纯函数)isPathWithinRoot,既能让这个测试老实用默认 mock,也能避免其他工具的测试悄悄把同前缀 bug 固化下来。
LGTM ✅
— claude-opus-4-8 via Claude Code /qreview
Summary
WorkspaceContext.isPathWithinWorkspacefor shelldirectoryvalidation/tmp/project-otherFixes #5453
Validation
npx vitest run packages/core/src/tools/shell.test.ts --testNamePattern "sibling-prefix"npx vitest run packages/core/src/tools/shell.test.tsnpx eslint packages/core/src/tools/shell.ts packages/core/src/tools/shell.test.tsnpx prettier --check packages/core/src/tools/shell.ts packages/core/src/tools/shell.test.ts && git diff --checknpm run typecheck --workspace=packages/coreAI Assistance Disclosure
I used Codex to review the changes, sanity-check the implementation against existing patterns, and help spot potential edge cases.