fix(core): reject Windows-style workspace artifact paths#6483
Conversation
Reject Windows-style absolute and traversal paths in record_artifact workspacePath validation by checking portable slash-normalized path semantics. This keeps artifact metadata aligned with the workspace-relative locator contract while preserving valid relative paths. Co-authored-by: chatgpt-codex-connector[bot] <199175422+chatgpt-codex-connector[bot]@users.noreply.github.com>
|
Thanks for the PR! Template looks good ✓ Problem: The gap is real — I verified on this Linux box that the old Direction: Aligned. Size: 14 production lines (+9/-5 in Approach: The scope is right — one function, one concern, corresponding tests. The fix uses Moving on to code review. 🔍 中文说明感谢贡献! 模板完整 ✓ 问题: 差距确实存在——我在 Linux 上验证了旧的 方向: 对齐。 规模: 14 行生产代码( 方案: 范围合理——一个函数、一个关注点、对应测试。修复使用 进入代码审查 🔍 — Qwen Code · qwen3.7-max |
Code ReviewThe diff is focused — one function (
Safe paths ( One minor observation: No critical blockers. No AGENTS.md violations. The code follows project conventions (ESM imports, Test ResultsThis is a non-UI validation change — tmux scenario testing is N/A. Ran unit tests directly: Before (main branch, without PR)13 tests pass. The old test only covers After (this PR)All 15 tests pass. The PR splits the old combined test into focused 中文说明代码审查Diff 聚焦于一个函数(
安全路径( 一个小观察: 无关键 blocker。无 AGENTS.md 违规。代码遵循项目规范。 测试结果非 UI 校验变更——tmux 场景测试不适用。直接运行单元测试: Before(main 分支,无 PR): 13 个测试全部通过。旧测试仅覆盖 After(本 PR): 15 个测试全部通过。PR 将旧的组合测试拆分为聚焦的 — Qwen Code · qwen3.7-max |
|
Stepping back: this is a clean, narrow validation fix that does exactly what it says. The cross-platform gap in The one observation from code review — The problem isn't an observed-in-the-wild bug (no linked issue, no reproduction from actual usage), but it's not purely theoretical either — the validation semantics are demonstrably wrong on POSIX for Windows-style inputs. For a 14-line fix with no risk of regression, closing the gap is worth doing. Ships it. ✅ 中文说明总结:这是一个干净、窄范围的校验修复,完全如其描述。 代码审查中的一个观察—— 问题不是已在线上观测到的 bug(无关联 issue,无实际使用中的复现),但也不是纯理论性的——POSIX 上对 Windows 风格输入的校验语义可证明是错误的。对于一个 14 行的修复且无回归风险,补上这个缺口值得做。 通过 ✅ — Qwen Code · qwen3.7-max |
qwen-code-ci-bot
left a comment
There was a problem hiding this comment.
LGTM, looks ready to ship. ✅
| /^[A-Za-z]:/.test(trimmed) | ||
| ) { | ||
| return '"workspacePath" must be relative to the workspace'; | ||
| } |
There was a problem hiding this comment.
[Suggestion] The three-layer absolute-path check (path.isAbsolute + path.win32.isAbsolute + /^[A-Za-z]:/ regex) is non-obvious. Each layer catches what the others miss:
path.isAbsolute— native platform absolute (POSIX:/foo; Win32:C:\foo,\foo)path.win32.isAbsolute— Windows-style absolute even on Linux (\tmp,\\UNC,C:\foo)/^[A-Za-z]:/— drive-relative paths likeC:tmp\file(win32.isAbsolutereturnsfalsefor these)
Without a comment, a future maintainer may remove the seemingly redundant win32.isAbsolute call or regex, silently reopening the path-traversal gap on some platform.
| } | |
| // Three layers, each catching what the others miss: | |
| // 1. path.isAbsolute — native platform absolute | |
| // 2. path.win32.isAbsolute — Windows-style absolute even on POSIX (\tmp, \\UNC, C:\foo) | |
| // 3. /^[A-Za-z]:/ — drive-relative paths like C:tmp\foo (win32.isAbsolute returns false) | |
| path.isAbsolute(trimmed) || | |
| path.win32.isAbsolute(trimmed) || | |
| /^[A-Za-z]:/.test(trimmed) |
— qwen3.7-max via Qwen Code /review
|
|
||
| await expect( | ||
| tool | ||
| .build({ |
There was a problem hiding this comment.
[Suggestion] Backslash paths like reports\\summary.html pass validation and are stored verbatim in the artifact metadata. On POSIX, path.resolve() treats \ as a literal filename character — the artifact resolves to a single file named reports\summary.html in the workspace root, not summary.html inside a reports/ directory. Downstream code (e.g., sessionArtifacts.ts calling path.resolve(workspaceCwd, workspacePath)) will silently resolve to a nonexistent path.
Consider normalizing backslashes to forward slashes in the stored value so the artifact path resolves correctly cross-platform:
// In execute(), after validation:
workspacePath: trimmed.replace(/\\/g, '/'),— qwen3.7-max via Qwen Code /review
wenshao
left a comment
There was a problem hiding this comment.
Reviewed — no blockers. Suggestion-level recommendations are in the Suggestion summary comment below.
Suggestions — commit
|
| File | Issue | Suggested fix |
|---|---|---|
packages/core/src/tools/record-artifact.test.ts:98 |
Loop-based test (for (const workspacePath of [...]) collapses 10 inputs into one test entry. If one input fails, the error doesn't identify which workspacePath caused it. The codebase convention is it.each (35 uses across packages/core/src/tools/*.test.ts). |
Convert to it.each([...])('rejects workspace path %j', (workspacePath) => { ... }) for per-case isolation and named output. |
packages/core/src/tools/record-artifact.test.ts:95 |
Bare .. input (no trailing filename) is not tested. path.posix.normalize('..') returns '..', hitting the === '..' branch — distinct from startsWith('../'). That branch has zero direct test coverage. |
Add '..' to the traversal rejection list. |
packages/core/src/tools/record-artifact.ts:370 |
FULLWIDTH Unicode homoglyph bypass: ../secret.txt (U+FF0E U+FF0E U+FF0F) passes validation because replace(/\\/g, '/') only matches U+005C and path.posix.normalize doesn't do Unicode normalization. After NFKC, it becomes ../secret.txt. Pre-existing, but the PR's new backslash handling has the same gap for U+FF3C (FULLWIDTH REVERSE SOLIDUS). |
Apply NFKC normalization before all other checks: const trimmed = value.normalize('NFKC').trim(); |
— glm-5.2 via Qwen Code /review
What this PR does
Tightens
record_artifactworkspacePath validation so Windows-style absolute paths and backslash traversal are rejected consistently, including when Qwen Code is running on POSIX/Linux.Why it's needed
What Problem This Solves
record_artifactacceptsworkspacePathas a workspace-relative metadata locator. The tool already rejected native absolute paths and POSIX-style parent-directory traversal, but its validation relied partly on the host platform's path semantics.On POSIX/Linux, backslash is not a path separator. That meant Windows-style path strings such as
..\secret.txt,reports\..\..\secret.txt,C:\tmp\report.html, or UNC-like paths could be treated as ordinary relative strings at therecord_artifactboundary, even though they do not match the intended workspace-relative contract.This change makes the validation portable: path strings carrying Windows absolute, drive-relative, UNC-like, or backslash traversal semantics are rejected consistently before
record_artifactreports success.Changes
path.win32.isAbsolute(...).C:\tmp\report.html,C:/tmp/report.html, andC:tmp\report.html.reports/summary.htmlandreports\summary.html.record_artifactscoped to metadata only; it still does not read, write, upload, publish, or verify the referenced resource.Evidence
The PR changes only
packages/core/src/tools/record-artifact.tsandpackages/core/src/tools/record-artifact.test.ts.The implementation now checks native absolute paths, Windows absolute paths, drive-prefixed paths, and portable parent-directory traversal before accepting
workspacePath.Regression coverage was expanded to reject:
../secret.txt..\secret.txt..\..\secret.txtreports\..\..\secret.txtreports/..\..\secret.txtC:\tmp\report.htmlC:/tmp/report.htmlC:tmp\report.html\\server\share\report.html\tmp\report.htmlThe tests also confirm that normal workspace-relative paths still resolve successfully for both slash and backslash examples.
Possible call chain / impact
A caller provides
workspacePathtorecord_artifact,RecordArtifactTool.build(...)validates the locator, andexecute(...)returns artifact metadata only after validation succeeds.Before this change, some Windows-style escape or absolute-path strings could pass validation when the tool was running on POSIX/Linux because backslashes were treated as plain characters. After this change, those strings are rejected at build-time, so clients receive a validation error instead of a successful artifact metadata result.
The expected impact is limited to invalid
workspacePathinputs. Valid workspace-relative artifact paths should continue to work unchanged.Reviewer Test Plan
How to verify
Confirm
record_artifactstill accepts normal workspace-relative paths such asreports/summary.htmlandreports\summary.html, and rejects../secret.txt,..\secret.txt,..\..\secret.txt,reports\..\..\secret.txt,reports/..\..\secret.txt,C:\tmp\report.html,C:/tmp/report.html,C:tmp\report.html,\\server\share\report.html, and\tmp\report.htmlbefore reporting success.Evidence (Before & After)
N/A - non-UI validation change. Regression coverage is in
packages/core/src/tools/record-artifact.test.ts.Focused validation run on Windows:
Additional local validation:
Linux/WSL validation:
Note: this checkout shares
node_modulesbetween Windows and WSL. Running the focused test on WSL required installing Rollup's Linux optional package; restoring the Windows optional package afterward kept the Windows focused test/typecheck/lint path green. No package files were changed.Tested on
Environment (optional)
Windows: Node.js
v24.15.0, npm11.12.1. Linux: WSL Ubuntu-22.04 with Node.jsv24.15.0.Risk & Scope
Linked Issues
N/A
中文说明
本 PR 做了什么
收紧
record_artifact的workspacePath校验,让 Windows 风格的绝对路径和反斜杠父目录跳转在 POSIX/Linux 环境下也会被一致拒绝。为什么需要
workspacePath是一个指向当前 workspace 内产物文件的 metadata locator。之前的校验能拒绝本机绝对路径和 POSIX 风格的../...,但依赖当前运行平台的node:path分隔符语义。在 POSIX/Linux 上,反斜杠不会被当作路径分隔符,因此
..\secret.txt、reports\..\..\secret.txt、C:\tmp\report.html这类 Windows 风格路径可能在工具入口处看起来像普通相对字符串。这会让record_artifact的工具层校验弱于它声明的 workspace-relative 语义。本 PR 保持范围很窄:
record_artifact仍然只记录 artifact metadata,不读取、不写入、不上传、也不验证引用资源;这里只是在返回成功 artifact 结果前拒绝带有 Windows 绝对路径、盘符相对路径、UNC 风格路径或反斜杠 traversal 语义的字符串。Reviewer Test Plan
验证
record_artifact仍接受reports/summary.html和reports\summary.html这类正常 workspace 相对路径,同时拒绝../secret.txt、..\secret.txt、..\..\secret.txt、reports\..\..\secret.txt、reports/..\..\secret.txt、C:\tmp\report.html、C:/tmp/report.html、C:tmp\report.html、\\server\share\report.html和\tmp\report.html。本地已在 Windows 和 WSL Ubuntu-22.04 上跑过 focused test。Windows 上还跑过 core typecheck、core lint 和
git diff --check。风险与范围
风险较低;该修改只拒绝违反现有 workspace-relative contract 的跨平台路径字符串,不改 artifact 读取、写入、上传或 daemon store 行为。它不应被表述为高危 path traversal 漏洞。