Skip to content

fix(cli): allow double dots in update archives - #5521

Merged
wenshao merged 1 commit into
QwenLM:mainfrom
tt-a1i:fix/standalone-update-dotdot-filenames
Jun 21, 2026
Merged

fix(cli): allow double dots in update archives#5521
wenshao merged 1 commit into
QwenLM:mainfrom
tt-a1i:fix/standalone-update-dotdot-filenames

Conversation

@tt-a1i

@tt-a1i tt-a1i commented Jun 20, 2026

Copy link
Copy Markdown
Contributor

What this PR does

Refines the standalone updater's tar-extraction path filter so it no longer rejects safe filenames that merely contain consecutive dots. A new isSafeTarEntryPath helper replaces the old p.startsWith('/') || p.includes('..') check inside extractArchive. The helper:

  • Rejects empty entry paths.
  • Rejects absolute paths (both POSIX and Windows, via path.posix.isAbsolute / path.win32.isAbsolute).
  • Splits the entry path on / or \ separators and rejects it only when a whole segment is exactly .. (a real parent-directory traversal).
  • Allows .. that appears inside a single filename segment (e.g. qwen-code/release..notes.md, qwen-code/node/lib/foo..bar, qwen-code/.../file.txt).

The function is exported so it can be unit-tested directly.

Why it's needed

The previous filter rejected any archive entry whose path contained .. anywhere. That blocked directory traversal, but it also dropped legitimate files whose names happen to contain consecutive dots (such as qwen-code/release..notes.md or qwen-code/node/lib/foo..bar), because the dots are part of a single path segment rather than a parent-directory segment. The desired behavior is to keep rejecting absolute paths and real .. path segments while allowing safe filenames that merely contain consecutive dots.

Reviewer Test Plan

How to verify

  • npm run test --workspace=packages/cli -- standalone-update.test.ts
  • npm run typecheck --workspace=packages/cli
  • npm run build
  • npx eslint packages/cli/src/utils/standalone-update.ts packages/cli/src/utils/standalone-update.test.ts
  • npx prettier --check packages/cli/src/utils/standalone-update.ts packages/cli/src/utils/standalone-update.test.ts
  • git diff --check

Evidence (Before & After)

N/A — internal logic change covered by unit tests. New isSafeTarEntryPath tests assert that double dots inside a filename segment are allowed while parent-directory segments and absolute paths (including backslash separators and C:\... drive paths) are rejected.

Tested on

OS Status
🍏 macOS ✅ tested
🪟 Windows ⚠️ not tested locally; covered by CI
🐧 Linux ⚠️ not tested locally; covered by CI

Environment (optional)

Local macOS workspace; unit tests via npm/vitest.

Risk & Scope

  • Main risk or tradeoff: low; scope-limited to the tar-entry path filter in packages/cli/src/utils/standalone-update.ts. Traversal protection is preserved (absolute paths and real .. segments are still rejected).
  • Not validated / out of scope: manual end-to-end standalone update against a real release archive.
  • Breaking changes / migration notes: none

Linked Issues

Fixes #5520

AI Assistance Disclosure

I used Codex to review the changes, sanity-check the implementation against existing patterns, and help spot potential edge cases.

中文说明

这个 PR 做了什么

优化独立更新器(standalone updater)的 tar 解压路径过滤逻辑,使其不再误拒仅仅包含连续点号的安全文件名。在 extractArchive 内部,用新的 isSafeTarEntryPath 辅助函数替换了原先的 p.startsWith('/') || p.includes('..') 判断。该辅助函数:

  • 拒绝空的条目路径。
  • 拒绝绝对路径(POSIX 与 Windows 两种,通过 path.posix.isAbsolute / path.win32.isAbsolute 判断)。
  • /\ 分隔符切分条目路径,仅当某个完整片段恰好为 ..(真正的上级目录穿越)时才拒绝。
  • 允许出现在单个文件名片段内部的 ..(例如 qwen-code/release..notes.mdqwen-code/node/lib/foo..barqwen-code/.../file.txt)。

该函数被导出,以便可以直接进行单元测试。

为什么需要

之前的过滤器会拒绝任何路径中任意位置包含 .. 的归档条目。这虽然阻止了目录穿越,但也丢弃了名字恰好包含连续点号的合法文件(例如 qwen-code/release..notes.mdqwen-code/node/lib/foo..bar),因为这些点号属于单个路径片段,而不是上级目录片段。期望的行为是:继续拒绝绝对路径和真正的 .. 路径片段,同时允许仅仅包含连续点号的安全文件名。

审阅者测试计划

如何验证

  • npm run test --workspace=packages/cli -- standalone-update.test.ts
  • npm run typecheck --workspace=packages/cli
  • npm run build
  • npx eslint packages/cli/src/utils/standalone-update.ts packages/cli/src/utils/standalone-update.test.ts
  • npx prettier --check packages/cli/src/utils/standalone-update.ts packages/cli/src/utils/standalone-update.test.ts
  • git diff --check

证据(前后对比)

N/A — 属于内部逻辑改动,已由单元测试覆盖。新增的 isSafeTarEntryPath 测试断言:文件名片段内部的连续点号被允许,而上级目录片段和绝对路径(包括反斜杠分隔符以及 C:\... 盘符路径)被拒绝。

测试平台

操作系统 状态
🍏 macOS ✅ 已测试
🪟 Windows ⚠️ 本地未测试;由 CI 覆盖
🐧 Linux ⚠️ 本地未测试;由 CI 覆盖

环境(可选)

本地 macOS 工作区;通过 npm/vitest 运行单元测试。

风险与范围

  • 主要风险或权衡:低;范围仅限于 packages/cli/src/utils/standalone-update.ts 中的 tar 条目路径过滤逻辑。穿越防护得以保留(绝对路径和真正的 .. 片段仍被拒绝)。
  • 未验证 / 范围之外:针对真实发布归档的手动端到端独立更新。
  • 破坏性变更 / 迁移说明:无

关联 Issue

Fixes #5520

AI 协助声明

我使用 Codex 审阅了这些改动,对照现有模式核对实现,并帮助发现潜在的边界情况。

@wenshao

wenshao commented Jun 20, 2026

Copy link
Copy Markdown
Collaborator

📌 Same maintainer note — full version on #5509.

This is the ..-prefixed-name / path-boundary theme again — same class as #5515 (bundle filenames) and the merged #5458 / #5460, now for CLI update archives. Please fold the path-boundary + dot-prefix work into a single PR and reuse the existing shared helper (isSubpath / isPathWithinRoot in packages/core/src/utils/paths.ts) rather than yet another per-file check. The change itself looks fine — this is purely grouping + DRY, not correctness.

中文说明

📌 同一条维护者说明 —— 完整版见 #5509

这又是 .. 前缀文件名 / 路径边界主题 —— 和 #5515(bundle 文件名)、已合的 #5458 / #5460 同类,现在用在 CLI update archives 上。请把路径边界 + dot 前缀相关改动合并成一个 PR,并复用已有共享 helper(packages/core/src/utils/paths.ts 里的 isSubpath / isPathWithinRoot),而不是再加一份逐文件检查。改动本身没问题 —— 这条纯粹是归并 + DRY,不涉及正确性。

@tt-a1i
tt-a1i marked this pull request as ready for review June 20, 2026 19:38
@wenshao

wenshao commented Jun 20, 2026

Copy link
Copy Markdown
Collaborator

@qwen-code /triage

@qwen-code-ci-bot qwen-code-ci-bot left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi @tt-a1i — thanks for the fix! The change itself looks correct and focused, but the PR body doesn't follow our PR template. Could you update the description to include the required sections?

Mainly missing:

  • What this PR does / Why it's needed — a prose description of the change and motivation
  • Reviewer Test Plan — How to verify, Evidence (Before & After), and the Tested-on table so reviewers can confirm the fix
  • Risk & Scope — what's in/out of scope, any tradeoffs
  • Linked IssuesFixes #5520 should go in this section (it's in the body but not under a dedicated heading)
  • 中文说明 — a Chinese translation in a <details> block

This helps reviewers move faster and is required for merge. The actual code change looks fine — just the description needs updating. 🙏

中文说明

@tt-a1i 你好,感谢修复!代码改动本身没问题,但 PR 描述没有按照我们的 PR 模板 填写。麻烦补充以下必填章节:

  • What this PR does / Why it's needed — 用文字描述改动内容和动机
  • Reviewer Test Plan — 验证步骤、Before/After 证据、测试过的操作系统表格
  • Risk & Scope — 范围、取舍、风险
  • Linked IssuesFixes #5520 放在这个章节下
  • 中文说明<details> 块里的中文翻译

这些帮助 reviewer 更快审查,也是合并的必要条件。代码本身没问题,只需要更新描述。🙏

Qwen Code · qwen3.7-max

@wenshao

wenshao commented Jun 20, 2026

Copy link
Copy Markdown
Collaborator

@qwen-code /triage

@qwen-code-ci-bot qwen-code-ci-bot left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey @tt-a1i, thanks for the PR! The body doesn't follow our PR template — a few required sections are missing or renamed:

  • ## What this PR does and ## Why it's needed — currently combined as ## Summary. Please split into the two template headings.
  • ## Reviewer Test Plan — missing entirely. This should include ### How to verify, ### Evidence (Before & After), and ### Tested on (OS table).
  • ## Risk & Scope — missing.
  • ## Linked Issues — missing (the Fixes #5520 reference should live under this heading).
  • <details>中文说明</details> — missing bilingual section.

Please update the body to match the template and re-request review. The code change itself looks reasonable — this is purely a process gate. 🙏

中文说明

@tt-a1i,感谢提交!PR 正文没有按照 PR 模板 填写——部分必填章节缺失或被改名了:

  • ## What this PR does## Why it's needed — 目前合并成了 ## Summary,请拆成模板中的两个标题。
  • ## Reviewer Test Plan — 完全缺失,应包含 ### How to verify### Evidence (Before & After)### Tested on(操作系统表格)。
  • ## Risk & Scope — 缺失。
  • ## Linked Issues — 缺失(Fixes #5520 应放在此章节下)。
  • <details>中文说明</details> — 缺失双语说明。

请按模板更新正文后重新请求 review。代码改动本身没问题——这纯粹是流程关卡。🙏

Qwen Code · qwen3.7-max

@wenshao

wenshao commented Jun 21, 2026

Copy link
Copy Markdown
Collaborator

@qwen-code /triage

@qwen-code-ci-bot

Copy link
Copy Markdown
Collaborator

Thanks for the PR, @tt-a1i!

Template looks good ✓

On direction: This is a straightforward bug fix — the old p.includes('..') filter was over-aggressive, rejecting legitimate filenames with consecutive dots (e.g. release..notes.md). The fix tightens the check to only reject real .. path segments. Clearly aligned with the project's update mechanism. No CHANGELOG reference for this specific pattern, but archive path safety is core to the standalone updater.

On approach: The scope is tight — +9 lines of logic, +20 lines of tests, one file changed. The isSafeTarEntryPath helper is well-structured: rejects empty paths, rejects absolute paths (both POSIX and Windows), then splits on / or \ and checks for .. as a whole segment. That's the right algorithm.

One thing to flag: @wenshao has already noted that this is part of a family of path-boundary fixes (#5515, #5458/#5460) and would prefer consolidating them + reusing the shared isSubpath helper in packages/core/src/utils/paths.ts rather than adding per-file checks. The existing isSubpath operates on resolved paths though, while isSafeTarEntryPath works on raw tar entry strings that may not exist on the filesystem — so a direct reuse isn't trivial. This is worth discussing, but not a blocker for the code review.

Moving on to code review and testing. 🔍

中文说明

感谢贡献,@tt-a1i

模板完整 ✓

方向: 这是一个直接的 bug 修复——旧的 p.includes('..') 过滤器过于激进,会拒绝包含连续点号的合法文件名(如 release..notes.md)。修复将检查收紧为仅拒绝真正的 .. 路径段。与项目的更新机制完全对齐。CHANGELOG 中没有此特定模式的参考,但归档路径安全是独立更新器的核心功能。

方案: 范围紧凑——+9 行逻辑、+20 行测试,仅修改一个文件。isSafeTarEntryPath 辅助函数结构良好:拒绝空路径、拒绝绝对路径(POSIX 和 Windows),然后按 /\ 分割并检查 .. 是否为完整段。这是正确的算法。

需要指出的是:@wenshao 已经指出这是一系列路径边界修复的一部分(#5515#5458/#5460),建议合并它们并复用 packages/core/src/utils/paths.ts 中的共享 isSubpath 辅助函数,而不是逐文件添加检查。不过现有的 isSubpath 操作的是已解析路径,而 isSafeTarEntryPath 处理的是原始 tar 条目字符串(可能不存在于文件系统中)——因此直接复用并不简单。这值得讨论,但不影响代码审查。

进入代码审查和测试 🔍

Qwen Code · qwen3.7-max

@qwen-code-ci-bot

Copy link
Copy Markdown
Collaborator

Code Review

The implementation is clean and correct. No blockers found.

isSafeTarEntryPath does the right thing: empty check → absolute check (both POSIX and Windows) → segment-level .. check via split on / or \. The segment-level check is the key improvement — it distinguishes .. as a directory traversal segment from .. appearing inside a filename. The function is exported for testability.

The test suite covers the right cases: double dots inside filename segments (allowed), parent-directory segments (blocked), absolute paths on both platforms (blocked), empty paths (blocked), and backslash separators (blocked).

One thing worth noting: the new filter is actually more secure than the old one. The old p.startsWith('/') || p.includes('..') missed absolute Windows paths like C:\tmp\... and empty paths — both of which the new filter correctly blocks. Nice side effect of the refactor.

No AGENTS.md violations. Scope is minimal — only what's needed for the fix.

Unit Tests

All 18 tests pass:

 ✓ src/utils/standalone-update.test.ts (18 tests) 23ms
 Test Files  1 passed (1)
      Tests  18 passed (18)

Tmux Verification

Ran a comparison of old vs new filter against representative tar entry paths:

Path filter comparison:
================================================================================
Test case                           Description                  Old   New
--------------------------------------------------------------------------------
qwen-code/release..notes.md         double dots in filename      BLOCK ALLOW ← CHANGED
qwen-code/node/lib/foo..bar         double dots in nested filename BLOCK ALLOW ← CHANGED
qwen-code/.../file.txt              triple dots as directory name BLOCK ALLOW ← CHANGED
../qwen-code/manifest.json          parent-directory traversal   BLOCK BLOCK
qwen-code/../manifest.json          mid-path traversal           BLOCK BLOCK
qwen-code\..\manifest.json          backslash traversal          BLOCK BLOCK
/tmp/qwen-code/manifest.json        absolute POSIX path          BLOCK BLOCK
C:\tmp\qwen-code\manifest.json      absolute Windows path        ALLOW BLOCK ← CHANGED
                                    empty path                   ALLOW BLOCK ← CHANGED
qwen-code/normal.txt                normal filename              ALLOW ALLOW

The ← CHANGED rows confirm:

  1. Intended fix: safe filenames with .. are now allowed (3 cases)
  2. Bonus security hardening: absolute Windows paths and empty paths are now blocked (2 cases the old filter missed)
  3. No regressions: all previously blocked traversal paths remain blocked, normal files remain allowed
中文说明

代码审查

实现干净且正确,未发现阻塞问题。

isSafeTarEntryPath 逻辑正确:空值检查 → 绝对路径检查(POSIX 和 Windows)→ 按段 .. 检查(通过 /\ 分割)。段级检查是关键改进——它区分了作为目录穿越段的 .. 和出现在文件名内的 ..。函数已导出以便测试。

测试套件覆盖了正确的场景:文件名段内的双点(允许)、上级目录段(阻止)、两个平台的绝对路径(阻止)、空路径(阻止)、反斜杠分隔符(阻止)。

值得注意的是:新过滤器实际上比旧版更安全。旧的 p.startsWith('/') || p.includes('..') 遗漏了绝对 Windows 路径(如 C:\tmp\...)和空路径——新过滤器都正确阻止了。重构的附带好处。

无 AGENTS.md 违规。范围最小——仅修复所需的内容。

单元测试

全部 18 个测试通过。

Tmux 验证

运行了旧过滤器与新过滤器在代表性 tar 条目路径上的对比,确认:

  1. 预期修复:包含 .. 的安全文件名现在被允许(3 个场景)
  2. 额外安全加固:绝对 Windows 路径和空路径现在被阻止(旧过滤器遗漏的 2 个场景)
  3. 无回归:所有之前阻止的穿越路径仍然被阻止,普通文件仍然被允许

Qwen Code · qwen3.7-max

@qwen-code-ci-bot

Copy link
Copy Markdown
Collaborator

Stepping back: this is a clean, minimal bug fix that does exactly what it says. The old filter was too blunt — p.includes('..') can't tell the difference between a directory traversal and a filename that happens to have consecutive dots. The new isSafeTarEntryPath gets it right with segment-level checking, and as a bonus closes two gaps the old filter had (absolute Windows paths, empty paths).

The code is 7 lines of logic plus 19 lines of tests. Every change in the diff is necessary. The implementation matches what I'd have written independently. All 18 tests pass, and the tmux verification confirms no regressions.

The DRY consolidation point from @wenshao is worth considering — if the path-boundary checks across #5515, #5458/#5460, and this PR can share a common utility, that's a good follow-up. But the existing isSubpath works on resolved filesystem paths while isSafeTarEntryPath works on raw tar entry strings, so a direct merge isn't straightforward. Suggest treating consolidation as a follow-up rather than blocking this fix.

Verdict: Ship it. ✅

中文说明

总结:这是一个干净、最小化的 bug 修复,完全做到了它所声称的事情。旧过滤器太粗暴——p.includes('..') 无法区分目录穿越和恰好包含连续点号的文件名。新的 isSafeTarEntryPath 通过段级检查正确处理了这个问题,并且附带修复了旧过滤器遗漏的两个漏洞(绝对 Windows 路径、空路径)。

代码是 7 行逻辑加 19 行测试。diff 中的每个改动都是必要的。实现与我会独立编写的一致。全部 18 个测试通过,tmux 验证确认无回归。

@wenshao 提出的 DRY 合并建议值得考虑——如果 #5515#5458/#5460 和本 PR 中的路径边界检查可以共享一个通用工具函数,那是一个好的后续工作。但现有的 isSubpath 操作的是已解析的文件系统路径,而 isSafeTarEntryPath 操作的是原始 tar 条目字符串,所以直接合并并不简单。建议将合并工作作为后续任务,而不是阻塞此修复。

结论: 可以合并 ✅

Qwen Code · qwen3.7-max

@qwen-code-ci-bot qwen-code-ci-bot left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM, looks ready to ship. ✅

@wenshao

wenshao commented Jun 21, 2026

Copy link
Copy Markdown
Collaborator

✅ Local verification report — real build + adversarial tar-extraction E2E

I verified this PR locally with real tooling (no mocks for the extraction path). Driven in tmux. Recommendation: safe to merge. The fix resolves #5520, all traversal protections are preserved, and the new filter is actually stricter than the old one on Windows-absolute paths.

Environment

Tested tree clean merge of origin/main (d0ba8534f) + PR head (469ea52) → 603997631 (no conflicts)
OS / runtime Linux x86_64 · Node v22.22.2 · npm 10.9.7 · tar@7.5.2

1. The PR's own test plan — all green

Step Result
vitest run standalone-update.test.ts 20/20 pass, incl. the 2 new isSafeTarEntryPath cases
eslint (both changed files) ✅ clean (rc=0)
prettier --check (both files) ✅ "All matched files use Prettier code style!"
git diff --check ✅ clean
tsc --noEmit (cli typecheck) no error on the changed file — see note¹
esbuild bundle of changed file ✅ bundles clean (22.7 kb, helper present)

¹ tsc reports exactly 2 errors, both TS6305 in packages/core/src/utils/toml-to-markdown-converter.ts(.test.ts) — a file this PR does not touch. They are pre-existing stale cross-package dist artifacts of my isolated worktree, unrelated to this change.


2. Real end-to-end tar extraction (the part the PR marked "out of scope")

The PR body lists "manual end-to-end standalone update against a real release archive" as not validated. I covered exactly that: I built real tar archives containing adversarial entry names and ran the real exported isSafeTarEntryPath inside a faithful copy of extractArchive's tar.extract({preservePaths:false, filter}) call, then A/B'd the old filter vs the new filter on the actual filesystem.

Filter decisions (ALLOW = extract / REJECT = skip):

archive entry OLD filter NEW (PR) class
qwen-code/release..notes.md REJECT ALLOW safe — the bug
qwen-code/node/lib/foo..bar REJECT ALLOW safe — the bug
qwen-code/.../file.txt REJECT ALLOW safe — the bug
qwen-code/normal.txt ALLOW ALLOW control
../escaped-root.txt REJECT REJECT traversal
qwen-code/../../escaped-deep.txt REJECT REJECT traversal
qwen-code/../sibling-escape.txt REJECT REJECT traversal
/tmp/qwen5521-abs-escape.txt REJECT REJECT absolute (posix)
C:\Windows\System32\winabs-evil.txt ALLOW ⚠️ REJECT absolute (windows)
qwen-code\..\winparent-evil.txt REJECT REJECT traversal (backslash)

On-disk outcome of the real tar.extract A/B:

  • PR extracted the 3 safe double-dot files + normal.txt; nothing escaped the destination.
  • Base dropped all 3 safe files (the bug), and — notably — allowed the Windows-absolute C:\… entry through the string filter.
  • Malicious symlink entries (→ /etc/passwd, → ../../../escape-via-link) were rejected on both branches (the symlink sub-check still fires even though the new filter lets more names reach it).

What this proves:

  1. Fix works — safe filenames containing .. inside a segment are now extracted (was the false-positive in standalone updater rejects safe archive filenames containing double dots #5520).
  2. No traversal regression — every real ..-segment / absolute / symlink-escape entry is still rejected; nothing lands outside the destination.
  3. Bonus hardening — the old p.startsWith('/') only caught POSIX-absolute paths; the new path.win32.isAbsolute also rejects Windows drive paths (C:\…) that the old filter let through. The PR is stricter here, not looser.

3. Defense-in-depth is intact (unchanged by the PR)

The diff only swaps the one filter line + adds the exported helper. The two deeper guards are untouched and still active: node-tar's own preservePaths:false stripping, and the post-extraction validateExtractedPaths() realpath check that rejects anything resolving outside the destination.

Non-blocking note

  • The new filter splits on [\\/]+, so a backslash is treated as a separator on every platform. A name like a\..\b (a legal single filename on Linux) is therefore rejected as traversal. This is harmless for real release archives and is defensible defense-in-depth — and the old filter rejected it too — so it's not a behavior change. Just flagging for awareness.

Verdict

Approve. Correct, narrowly scoped, fully covered by the new unit tests, and confirmed against real archives end-to-end. Traversal protection is preserved (and slightly improved).

🇨🇳 中文版验证报告(点击展开)

✅ 本地验证报告 —— 真实构建 + 对抗性 tar 解压端到端测试

我在本地使用真实工具链(解压路径无任何 mock)验证了本 PR,全程在 tmux 中驱动。结论:可以合并。 该修复解决了 #5520,所有路径穿越防护均得以保留,并且新过滤器在 Windows 绝对路径上实际比旧的更严格

环境

被测代码树 origin/maind0ba8534f)与 PR head(469ea52)的干净合并 → 603997631(无冲突)
系统 / 运行时 Linux x86_64 · Node v22.22.2 · npm 10.9.7 · tar@7.5.2

1. PR 自带的测试计划 —— 全绿

步骤 结果
vitest run standalone-update.test.ts 20/20 通过,含 2 个新增的 isSafeTarEntryPath 用例
eslint(两个改动文件) ✅ 干净(rc=0)
prettier --check(两个文件) ✅ "All matched files use Prettier code style!"
git diff --check ✅ 干净
tsc --noEmit(cli 类型检查) 改动文件零报错 —— 见注¹
改动文件的 esbuild 打包 ✅ 打包成功(22.7 kb,含新函数)

¹ tsc 仅报 2 个 TS6305 错误,均位于 packages/core/src/utils/toml-to-markdown-converter.ts(.test.ts),而本 PR 并未触及该文件。这是我隔离 worktree 中跨包 dist 过期导致的既有问题,与本次改动无关。

2. 真实端到端 tar 解压(PR 中标注为"范围之外"的部分)

PR 描述将"针对真实发布归档的手动端到端更新"列为未验证。我恰好补上了这一块:构造了包含对抗性条目名的真实 tar 归档,并在忠实复刻的 extractArchivetar.extract({preservePaths:false, filter}) 调用中运行真实导出的 isSafeTarEntryPath,然后在真实文件系统上对旧过滤器 vs 新过滤器做 A/B 对比。

过滤决策(ALLOW = 解压 / REJECT = 跳过):

归档条目 旧过滤器 新过滤器(PR) 类别
qwen-code/release..notes.md REJECT ALLOW 安全 —— 即此 bug
qwen-code/node/lib/foo..bar REJECT ALLOW 安全 —— 即此 bug
qwen-code/.../file.txt REJECT ALLOW 安全 —— 即此 bug
qwen-code/normal.txt ALLOW ALLOW 对照
../escaped-root.txt REJECT REJECT 穿越
qwen-code/../../escaped-deep.txt REJECT REJECT 穿越
qwen-code/../sibling-escape.txt REJECT REJECT 穿越
/tmp/qwen5521-abs-escape.txt REJECT REJECT 绝对路径(posix)
C:\Windows\System32\winabs-evil.txt ALLOW ⚠️ REJECT 绝对路径(windows)
qwen-code\..\winparent-evil.txt REJECT REJECT 穿越(反斜杠)

真实 tar.extract A/B 的落盘结果:

  • PR 解压出 3 个安全的双点文件 + normal.txt没有任何文件逃逸目标目录。
  • 基线 丢弃了全部 3 个安全文件(即此 bug),并且——值得注意——放行了 Windows 绝对路径 C:\… 条目。
  • 恶意软链接条目(→ /etc/passwd→ ../../../escape-via-link)在两侧均被拒绝(即便新过滤器放更多条目名进入软链接子检查,该子检查仍然生效)。

由此证明:

  1. 修复有效 —— 段内部包含 .. 的安全文件名现在能被解压(即 standalone updater rejects safe archive filenames containing double dots #5520 的误拒)。
  2. 无穿越回归 —— 所有真实的 .. 段 / 绝对路径 / 软链接逃逸条目仍被拒绝;没有任何文件落到目标目录之外。
  3. 额外加固 —— 旧的 p.startsWith('/') 只能拦截 POSIX 绝对路径;新的 path.win32.isAbsolute 还能拒绝旧过滤器放行的 Windows 盘符路径(C:\…)。此处 PR 是更严格,而非更松。

3. 纵深防御保持完好(本 PR 未改动)

diff 仅替换了那一行过滤判断 + 新增导出的辅助函数。两层更深的防护均未触及且仍然生效:node-tar 自身的 preservePaths:false 剥离,以及解压后 validateExtractedPaths()realpath 检查(拒绝任何解析到目标目录之外的路径)。

非阻塞提示

  • 新过滤器按 [\\/]+ 切分,因此反斜杠在所有平台都被当作分隔符。像 a\..\b 这样在 Linux 上合法的单一文件名会因此被当作穿越而拒绝。对真实发布归档无害,作为纵深防御也合理(且旧过滤器同样会拒绝它),故不算行为变更,仅作提示。

结论

赞成合并。 实现正确、范围聚焦、由新增单元测试完整覆盖,并已针对真实归档做端到端确认。路径穿越防护得以保留(并略有增强)。


Verification method: isolated merge worktree, real tar@7.5.2 archives with adversarial entry names, real exported isSafeTarEntryPath driven through tar.extract, old-vs-new filter A/B on the live filesystem.

@wenshao
wenshao merged commit 7a4f080 into QwenLM:main Jun 21, 2026
41 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

standalone updater rejects safe archive filenames containing double dots

3 participants