fix(desktop): keep workspace image paths in root - #5513
Conversation
|
📌 Same maintainer note applies here — full version on #5509. This is the desktop fix batch (#5507, #5509, #5511, #5513) and the second path-boundary one alongside #5507. Please fold the path-boundary work into a single PR and reuse the existing boundary helper — Thanks! 🙏 中文说明📌 同一条维护者说明也适用于此 —— 完整版见 #5509。 这是 desktop 这批修复(#5507、#5509、#5511、#5513)里第二个路径边界的(与 #5507 同类)。请把路径边界的改动合成一个 PR,并复用已有的边界 helper —— 谢谢!🙏 |
|
@qwen-code /triage |
qwen-code-ci-bot
left a comment
There was a problem hiding this comment.
Hey @tt-a1i, thanks for the security fix — symlink escape hardening on workspace image paths is a real issue worth addressing.
Two things blocking this from moving forward:
1. PR template: the body is missing several required sections from the PR template: "What this PR does", "Why it's needed", "Reviewer Test Plan" (with "How to verify", "Evidence (Before & After)", "Tested on" table), "Risk & Scope", "Linked Issues", and the Chinese translation in <details>. Please reformat the body to follow the template.
2. Consolidation needed: as @wenshao noted, this is one of four desktop fix PRs (#5507, #5509, #5511, #5513) and the second path-boundary one alongside #5507. Please fold the path-boundary work into a single PR. Also, the PR introduces a new isPathWithinDirectory helper, but the project already has isSubpath in packages/core/src/utils/paths.ts that does the same relative-path-prefix check. Reuse the existing helper rather than adding a per-file duplicate — the new isExistingWorkspacePath / isWorkspacePathAllowingMissingTarget functions can build on top of isSubpath for the structural check, and the realpath-based symlink hardening layer is the genuinely new contribution here.
Once the template is filled in and the path-boundary PRs are consolidated, happy to re-run the review.
中文说明
@tt-a1i 你好,感谢这个安全修复——workspace 图片路径的 symlink 逃逸加固确实是值得处理的问题。
目前有两点阻塞:
1. PR 模板:正文缺少 PR 模板 中多个必填章节:"What this PR does"、"Why it's needed"、"Reviewer Test Plan"(含 "How to verify"、"Evidence (Before & After)"、"Tested on" 表格)、"Risk & Scope"、"Linked Issues" 以及 <details> 中的中文翻译。请按模板重新整理正文。
2. 需要合并:正如 @wenshao 指出,这是四篇 desktop 修复 PR(#5507、#5509、#5511、#5513)之一,也是与 #5507 同类的第二个路径边界修复。请把路径边界改动合并到一个 PR。另外,PR 新增了 isPathWithinDirectory helper,但项目已有 packages/core/src/utils/paths.ts 中的 isSubpath 做了同样的相对路径前缀检查。请复用已有 helper——新的 isExistingWorkspacePath / isWorkspacePathAllowingMissingTarget 可以在 isSubpath 之上构建结构检查,而基于 realpath 的 symlink 加固层才是这里真正的新贡献。
模板补全、路径边界 PR 合并后,随时可以重新审查。
— Qwen Code · qwen3.7-max
|
@qwen-code /triage |
|
Hey @tt-a1i, thanks for the security fix — symlink escape hardening on workspace image READ_IMAGE / WRITE_IMAGE is a real vulnerability worth addressing. Template looks good — all required sections present ✓ (note: the previous review incorrectly flagged missing template sections; the PR body is complete). On direction: this is squarely aligned with the project's security posture. Workspace-scoped image handlers must not follow symlinks outside the workspace. Fixes #5512 directly. On approach: the core strategy — resolve real paths via On consolidation: @wenshao asked to fold the path-boundary PRs (#5507, #5509, #5511, #5513) into a single PR. That's a maintainer process call — flagging it here but deferring to the maintainer on whether individual PRs are acceptable. Moving on to code review and testing. 🔍 中文说明@tt-a1i 你好,感谢这个安全修复——workspace 图片 READ_IMAGE / WRITE_IMAGE 的 symlink 逃逸加固确实是值得处理的安全漏洞。 模板完整 ✓(注意:此前的 review 错误地标记了模板缺失章节;PR 正文实际上是完整的)。 方向:与项目的安全立场完全一致。workspace 范围的图片处理器不应跟踪 symlink 逃逸出 workspace。直接修复 #5512。 方案:核心策略——通过 关于合并:@wenshao 要求将路径边界 PR(#5507、#5509、#5511、#5513)合并为一个 PR。这是维护者的流程决定——在此标记但交由维护者决定单独 PR 是否可接受。 进入代码审查和测试阶段 🔍 — Qwen Code · qwen3.7-max |
Code ReviewIndependent proposal (before reading the diff): the fix should resolve symlinks via The PR's approach matches this conceptually — the realpath resolution layer, the ancestor-walking for missing files, and the "workspace root is a symlink" handling are all correct. Two code-level concerns: 1. 2. Neither is a merge blocker — the security hardening works correctly for all realistic image path scenarios. The Test ResultsAll 5 new tests pass: Typecheck clean, prettier clean, no regressions in existing 中文说明代码审查独立方案(在读 diff 之前):修复应该通过 PR 的方案在概念上与此一致——realpath 解析层、缺失文件的祖先查找、以及"workspace 根目录本身是 symlink"的处理都是正确的。两个代码层面的问题: 1. 2. 两者都不是合并阻塞项——安全加固对所有现实的图片路径场景都正确工作。 测试结果5 个新测试全部通过: Typecheck 通过,prettier 通过,已有 — Qwen Code · qwen3.7-max |
|
Stepping back: this PR does one thing well — it closes a symlink-escape hole in the workspace image read/write path. The approach (realpath resolution + ancestor walking for missing files + symlink-root workspace handling) is the right approach for this class of vulnerability. Five focused tests cover the key scenarios, typecheck and formatting are clean, no regressions in existing path validation tests. The The consolidation question (@wenshao's request to fold #5507/#5509/#5511/#5513 into one PR and reuse My honest take: the security fix is correct and ready to ship. The Not approving or requesting changes — deferring to @wenshao on the consolidation decision. 中文说明退一步看:这个 PR 做好了一件事——修补 workspace 图片读/写路径中的 symlink 逃逸漏洞。方案(realpath 解析 + 缺失文件的祖先查找 + symlink 根 workspace 处理)对于此类漏洞是正确的。5 个专注的测试覆盖了关键场景,typecheck 和格式化都干净,已有路径验证测试无回归。
合并问题(@wenshao 要求将 #5507/#5509/#5511/#5513 合并为一个 PR 并复用 我的真实看法: 安全修复是正确的,可以合并。 不做 approve 或 request changes——将合并决定交给 @wenshao。 — Qwen Code · qwen3.7-max |
|
consolidated this into #5545 so the desktop path-boundary fixes share one helper. closing this one to avoid duplicate review. |
What this PR does
This PR hardens the workspace image RPC handlers (
READ_IMAGE/WRITE_IMAGE) so that resolved image paths cannot escape the workspace root via symlinks. The previous boundary check relied only on a stringstartsWith(workspace.rootPath)comparison on the joined path, which can be bypassed when an entry inside the workspace is a symlink (or has a symlinked parent directory) that points outside the root.The new logic resolves the real path of both the workspace root and the target via
realpathSync.native, and verifies containment with a properpath.relativebased check (isPathWithinDirectory) instead of a raw string prefix match. For writes to a not-yet-existing file, it walks up to the nearest existing ancestor and validates that ancestor's real path is still inside the workspace, so a symlinked parent directory or a broken final symlink is rejected before any file is written. Reads of a missing optional image continue to returnnullrather than throwing, preserving existing behavior. Legitimate cases — including a workspace whose root itself is a symlink — are still allowed.A new focused test file adds coverage for these read/write image path boundaries.
Why it's needed
Without resolving symlinks, the
startsWithcontainment check could be tricked into reading or writing files outside the workspace directory (a path-traversal / symlink-escape issue). This fixes that boundary while keeping the existing "missing optional image returns null" behavior intact. See the linked issue for the reported problem.Reviewer Test Plan
How to verify
bun test packages/desktop/packages/server-core/src/handlers/rpc/workspace.image-path.test.tsbun run typecheckinpackages/desktop/packages/server-corenpx prettier --check packages/desktop/packages/server-core/src/handlers/rpc/workspace.ts packages/desktop/packages/server-core/src/handlers/rpc/workspace.image-path.test.tsgit diff --checkEvidence (Before & After)
N/A — internal logic change covered by unit tests. The new
workspace.image-path.test.tsasserts: missing optional images returnnull; reads/writes that escape via a symlink or symlinked parent throwoutside workspace directoryand create no outside file; and overwriting an image works when the workspace root is itself a symlink.Tested on
Environment (optional)
Local macOS workspace; unit tests via npm/vitest (desktop server-core uses bun:test, so the test command above is run via bun).
Risk & Scope
READ_IMAGE/WRITE_IMAGEpath-containment check. The symlink-aware checks addrealpathSync/lstatSynccalls, but only on the image read/write paths.Linked Issues
Fixes #5512
AI Assistance Disclosure
I used Codex to review the changes, sanity-check the implementation against existing patterns, and help spot potential edge cases.
中文说明
这个 PR 做了什么
本 PR 加固了工作区图片的 RPC 处理器(
READ_IMAGE/WRITE_IMAGE),使得解析后的图片路径无法通过符号链接(symlink)逃逸出工作区根目录。此前的边界检查只是对拼接后的路径做字符串startsWith(workspace.rootPath)比较,当工作区内的某个条目是指向根目录之外的符号链接(或其父目录是符号链接)时,这种检查可以被绕过。新逻辑通过
realpathSync.native解析工作区根目录和目标路径的真实路径,并使用基于path.relative的正确包含性检查(isPathWithinDirectory)来替代原始的字符串前缀匹配。对于写入尚不存在的文件,它会向上查找最近的已存在祖先目录,并校验该祖先目录的真实路径仍位于工作区内,因此在写入任何文件之前,符号链接的父目录或损坏的末端符号链接都会被拒绝。读取缺失的可选图片仍然返回null而非抛错,保持了既有行为。合法场景——包括工作区根目录本身就是符号链接的情况——依然被允许。新增了一个专门的测试文件来覆盖这些读/写图片路径的边界。
为什么需要
如果不解析符号链接,
startsWith包含性检查可能被欺骗,从而读取或写入工作区目录之外的文件(一种路径穿越 / 符号链接逃逸问题)。本 PR 修复了该边界,同时保持了"缺失的可选图片返回 null"这一既有行为。具体问题见关联 Issue。Reviewer Test Plan(评审测试计划)
如何验证
bun test packages/desktop/packages/server-core/src/handlers/rpc/workspace.image-path.test.tspackages/desktop/packages/server-core中执行bun run typechecknpx prettier --check packages/desktop/packages/server-core/src/handlers/rpc/workspace.ts packages/desktop/packages/server-core/src/handlers/rpc/workspace.image-path.test.tsgit diff --check证据(前后对比)
不适用 —— 这是内部逻辑改动,已由单元测试覆盖。新增的
workspace.image-path.test.ts断言:缺失的可选图片返回null;通过符号链接或符号链接父目录逃逸的读/写会抛出outside workspace directory且不会在外部创建任何文件;当工作区根目录本身是符号链接时,覆盖写入图片可以正常工作。测试平台
环境(可选)
本地 macOS 工作区;单元测试通过 npm/vitest 运行(desktop server-core 使用 bun:test,因此上述测试命令通过 bun 运行)。
风险与范围
READ_IMAGE/WRITE_IMAGE的路径包含性检查。符号链接感知的检查增加了realpathSync/lstatSync调用,但仅作用于图片读/写路径。关联 Issue
Fixes #5512
AI 协助声明
我使用了 Codex 来评审改动、对照既有模式核查实现,并帮助发现潜在的边界情况。