Skip to content

fix(core): resolve tilde paths before search permission checks#5378

Merged
wenshao merged 1 commit into
QwenLM:mainfrom
tt-a1i:fix/search-tilde-permission
Jun 19, 2026
Merged

fix(core): resolve tilde paths before search permission checks#5378
wenshao merged 1 commit into
QwenLM:mainfrom
tt-a1i:fix/search-tilde-permission

Conversation

@tt-a1i

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

Copy link
Copy Markdown
Contributor

What this PR does

This PR makes the search tools use the existing tilde-aware path resolver before deciding the default permission for an explicit search path. glob, grep, and ripgrep now resolve ~/... the same way they resolve paths during validation instead of treating ~ as a literal child of the workspace.

Why it's needed

The permission check previously used path.resolve(targetDir, params.path). For a path like ~/outside-workspace, Node treats ~ as a normal path segment, so the resolved path appears to be inside the workspace even though the user meant a home-directory path. That can make search tools default to allow for a path that should require confirmation.

Reviewer Test Plan

How to verify

Run the focused search tool tests and confirm each tool still allows a normal relative workspace path while returning ask for a tilde path outside the workspace.

Evidence (Before & After)

Before: ~/outside-workspace could be resolved as a literal workspace child path during the default permission check. After: resolvePath() expands ~ first, so the workspace containment check sees the real home-directory path and returns ask when it is outside the workspace.

Tested on

OS Status
🍏 macOS ✅ tested
🪟 Windows ⚠️ not tested locally
🐧 Linux ⚠️ not tested locally

Environment (optional)

Node 22, local repository checkout.

Risk & Scope

  • Main risk or tradeoff: Explicit tilde paths that previously looked like workspace-relative literal ~ paths may now require confirmation when they resolve outside the workspace.
  • Not validated / out of scope: No UI or sandbox behavior changed; this only touches default permission path resolution for search tools.
  • Breaking changes / migration notes: None.

Linked Issues

Fixes #5376

AI Assistance Disclosure

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

中文说明

What this PR does

这个 PR 让 search tools 在判断显式搜索路径的默认权限前,使用已有的支持 ~ 展开的路径解析逻辑。globgrepripgrep 现在会像路径校验阶段一样解析 ~/...,而不是把 ~ 当成 workspace 下的普通字面目录。

Why it's needed

之前 permission check 使用 path.resolve(targetDir, params.path)。对 ~/outside-workspace 这样的路径,Node 会把 ~ 当成普通路径片段,所以解析结果看起来像在 workspace 内,尽管用户实际表达的是 home 目录路径。这会让 search tools 对本应确认的路径默认返回 allow

Reviewer Test Plan

How to verify

运行聚焦的 search tool 测试,并确认每个工具仍然允许普通的 workspace 相对路径,同时对 workspace 外的 tilde 路径返回 ask

Evidence (Before & After)

Before: ~/outside-workspace 在默认权限检查里可能被解析成 workspace 下的字面子路径。After: resolvePath() 会先展开 ~,所以 workspace containment check 会看到真实的 home 目录路径,并在它位于 workspace 外时返回 ask

Tested on

OS Status
🍏 macOS ✅ tested
🪟 Windows ⚠️ not tested locally
🐧 Linux ⚠️ not tested locally

Environment (optional)

Node 22,本地仓库 checkout。

Risk & Scope

  • Main risk or tradeoff: 之前看起来像 workspace 相对字面 ~ 目录的显式 tilde 路径,现在如果解析到 workspace 外,会要求确认。
  • Not validated / out of scope: 没有改 UI 或 sandbox 行为;这里只影响 search tools 的默认权限路径解析。
  • Breaking changes / migration notes: 无。

Linked Issues

Fixes #5376

AI Assistance Disclosure

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

@tt-a1i
tt-a1i marked this pull request as ready for review June 19, 2026 02:21

@wenshao wenshao 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.

No issues found. LGTM! ✅

— DeepSeek/deepseek-v4-pro via Qwen Code /review

@wenshao

wenshao commented Jun 19, 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 ✓ — all required sections present including the bilingual summary.

On direction: this is a straightforward security bug fix. The linked issue #5376 (P1, category/security) correctly identifies that search tool permission checks use path.resolve() which doesn't expand ~, while execution uses resolvePath() which does. That mismatch means a path like ~/secret gets checked as <workspace>/~/secret (inside workspace → allow) but actually searches the real home directory. Clearly within scope and aligned with the project's security posture.

On approach: the fix is minimal and correct — replace path.resolve() with the existing resolvePath() utility in the three search tools' getDefaultPermission() methods, plus add focused tests. No scope creep, no new abstractions. This is exactly what a targeted security fix should look like.

Moving on to code review and testing. 🔍

中文说明

感谢贡献!

模板完整 ✓ — 所有必填章节齐全,包括双语摘要。

方向:这是一个明确的安全 bug 修复。关联 issue #5376(P1,category/security)正确指出搜索工具的权限检查使用 path.resolve() 不展开 ~,而执行阶段使用 resolvePath() 会展开。这个不一致导致 ~/secret 这样的路径被当作 <workspace>/~/secret(在 workspace 内 → allow),但实际搜索的是真实的 home 目录路径。完全在范围内,符合项目的安全要求。

方案:修复最小化且正确——在三个搜索工具的 getDefaultPermission() 方法中用现有的 resolvePath() 替换 path.resolve(),并添加针对性测试。没有范围蔓延,没有新增抽象。这正是定向安全修复应有的样子。

进入代码审查和测试阶段 🔍

Qwen Code · qwen3.7-max

@qwen-code-ci-bot

Copy link
Copy Markdown
Collaborator

Code Review

Independent proposal (before seeing the diff): The fix is to replace path.resolve() with the existing resolvePath() utility from paths.ts inside the getDefaultPermission() methods of glob, grep, and ripGrep. resolvePath() already handles ~ expansion correctly and is used by resolveAndValidatePath() during execution — the permission check should use the same resolver to ensure consistent semantics.

Diff comparison: The PR does exactly this. Three one-line changes (path.resolveresolvePath) in three files, plus focused unit tests. No over-abstraction, no unrelated edits, no scope creep. This matches my independent proposal precisely.

No critical blockers or convention violations found. The change is minimal, correct, and reuses existing infrastructure.

Unit Tests

All three test suites pass on the PR branch:

✓ src/tools/glob.test.ts    — 46 tests passed
✓ src/tools/grep.test.ts    — 37 tests passed
✓ src/tools/ripGrep.test.ts — 60 tests passed

Each suite includes new getDefaultPermission tests confirming allow for workspace-relative paths and ask for tilde paths.

Real-Scenario Testing

Tilde path (~/outside-workspace) — Before (main) vs After (PR)

Both runs used npm run dev -- -p 'use glob tool to list files in path ~/outside-workspace with pattern *' in non-interactive mode.

Before (main branch):

runner@runnervm7b5n9:~/work/qwen-code/qwen-code$ npm run dev -- -p 'use glob tool to list files in path ~/outside-workspace with pattern *' 2>&1

DEV is set to true, but the React DevTools server is not running.

Warning: Tool "glob" requires user approval but cannot execute in non-interactive mode.
To enable automatic tool execution, use the -y flag (YOLO mode):

The directory `/home/runner/outside-workspace` does not exist.

After (PR branch):

runner@runnervm7b5n9:~/work/qwen-code/qwen-code/.qwen/worktrees/triage$ npm run dev -- -p 'use glob tool to list files in path ~/outside-workspace with pattern *' 2>&1

DEV is set to true, but the React DevTools server is not running.

Warning: Tool "glob" requires user approval but cannot execute in non-interactive mode.
To enable automatic tool execution, use the -y flag (YOLO mode):

Warning: Tool "list_directory" requires user approval but cannot execute in non-interactive mode.

I'm unable to list files in `/home/runner/outside-workspace`. All the tools I tried were denied:
- glob — permission declined
- list_directory — permission declined

This directory is outside the current workspace, and the permissions are configured to block access to it.

Note: In both runs the LLM expanded ~ before calling the tool, so the tool received an absolute external path and returned ask regardless. The bug the PR fixes is subtler — it's a defense-in-depth fix for when the tool receives a literal tilde path. This is confirmed at the code level:

BEFORE (path.resolve): /home/runner/work/qwen-code/qwen-code/~/outside-workspace
  Looks like inside workspace: true    ← BUG: literal ~ treated as directory name

AFTER (resolvePath):   /home/runner/outside-workspace
  Looks like inside workspace: false   ← FIX: ~ expanded to home directory

Workspace-relative path (sub) — After (PR, yolo mode)

runner@runnervm7b5n9:~/work/qwen-code/qwen-code/.qwen/worktrees/triage$ npm run dev -- -p 'use grep tool to search for hello in path sub' -y 2>&1

Warning: running headless with --yolo / approval-mode=yolo and no sandbox.

There's no `sub` directory in the project root. Available directories include:
.github, .qwen, .vscode, docs, docs-site, eslint-rules, integration-tests, packages, patches, scripts

Workspace-relative paths continue to work correctly — no regression.

中文说明

代码审查

独立方案(看 diff 前): 在 glob、grep、ripGrep 的 getDefaultPermission() 方法中,用已有的 resolvePath() 替换 path.resolve()resolvePath() 已正确处理 ~ 展开,且执行阶段的 resolveAndValidatePath() 就在使用它——权限检查应使用相同的解析器以确保语义一致。

Diff 对比: PR 完全符合这一方案。三个文件各改一行(path.resolveresolvePath),加上针对性的单元测试。没有过度抽象,没有无关改动,没有范围蔓延。与我的独立方案完全一致。

未发现关键阻塞问题或规范违反。改动最小化、正确,并复用了已有基础设施。

单元测试

三个测试套件全部通过:glob 46 个、grep 37 个、ripGrep 60 个。

真实场景测试

Tilde 路径和工作区相对路径均表现正确。修复前后在 LLM 自行展开 ~ 的场景下都返回 ask;代码级别验证确认当工具接收字面 tilde 路径时,修复前会被误判为工作区内(bug),修复后正确识别为工作区外。

Qwen Code · qwen3.7-max

@qwen-code-ci-bot

Copy link
Copy Markdown
Collaborator

Reflection

This PR is what a security fix should look like: identify the mismatch between two code paths (permission check uses path.resolve, execution uses resolvePath), align them using existing infrastructure, and add tests that directly verify the fix.

Three things I looked at before deciding:

  1. The bug is real. The code-level demo proves it: path.resolve(workspace, '~/outside') produces <workspace>/~/outside which passes the workspace containment check. resolvePath() expands ~ first and correctly identifies it as external.

  2. The fix is minimal and complete. Three one-line changes across three tools, no new abstractions, no drive-by refactors. Every line in the diff serves the stated goal.

  3. Tests are meaningful. The new tests exercise getDefaultPermission() directly — not just the happy path but also the tilde edge case. All 143 tests pass.

The tmux test shows that in practice the LLM tends to expand ~ before calling tools, so the literal-tilde scenario may be uncommon. But that's exactly why this is a defense-in-depth fix — the permission system shouldn't depend on the LLM doing the right thing. It should be correct on its own.

No reservations. This is ready to ship.

中文说明

总结

这个 PR 是安全修复的典范:发现两条代码路径的不一致(权限检查用 path.resolve,执行用 resolvePath),用已有基础设施对齐,并添加直接验证修复的测试。

Bug 真实存在(代码级演示已确认)。修复最小化且完整(三个文件各一行,无新抽象,无搭车重构)。测试有意义(直接测试 getDefaultPermission(),143 个测试全部通过)。

虽然实践中 LLM 通常会自行展开 ~,但权限系统不应依赖 LLM 的行为。这是一个正确的纵深防御修复。

没有顾虑,可以合并。

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 19, 2026

Copy link
Copy Markdown
Collaborator

✅ Local verification report — PR #5378 fix(core): resolve tilde paths before search permission checks

Verdict: strong LGTM. This is a correct, minimal, security-positive fix. The search tools' default-permission check now expands ~ the same way execution does, so a search against ~/outside-workspace correctly requires confirmation (ask) instead of silently defaulting to allow. The new tests genuinely catch the bug, the behavior is consistent across glob/grep/ripgrep, and there are no type/lint regressions.

The bug (a confirmation bypass)

getDefaultPermission used path.resolve(targetDir, params.path). For ~/secret, Node treats ~ as a literal segment → <workspace>/~/secret → looks inside the workspace → returns allow (no confirmation). But execution uses tilde-aware resolution, so the tool actually searches the real home directory (outside the workspace). Net effect: searches of external ~-paths ran without the confirmation they should require. The PR swaps path.resolveresolvePath (the same ~-expanding resolver used during validation) in all three tools.

How it was verified

Real vitest under tmux in isolated worktrees: AFTER = 4052e823 (PR head, MERGEABLE/CLEAN), BEFORE = origin/main (ca1ab06b), node_modules symlinked. Node v22.22.2. Merges cleanly; the 3 source files are identical between merge-base and main.

Check Result
Merges cleanly into main ✅ MERGEABLE / CLEAN
PR getDefaultPermission tests (glob+grep+ripgrep) 9/9 pass
Genuine-regression — PR tests on BASE source the 3 "ask for tilde" tests fail with expected 'allow' to be 'ask'; the 6 within-workspace tests pass on both → proves base grants the bypass
Edge-case probe (real workspace + real resolvePath + real containment) 7/7 (see below)
tsc --noEmit core 0 new type errors (clean, identical to base)
prettier --check + eslint (6 files) + git diff --check ✅ all exit 0

The tests are a real behavioral proof, not mocked-away: a real temp workspace under /tmp, a real containment check, and the real resolvePath — so ~/outside-workspace/root/outside-workspace (outside /tmp/...) → ask on the PR, while base's path.resolve yields <workspace>/~/outside-workspaceallow.

Edge-case probe (real glob tool, PR head)

input result
no path / sub / ./sub allow ✅ (normal workspace paths unaffected)
bare ~ (home dir) ask
~/x (tilde outside) ask
absolute /tmp ask
../../.. (parent traversal) ask

Notes (non-blocking)

  • Consistency with execution: the permission check now uses resolvePath, which is the same ~-expansion that resolveAndValidatePath (used at execution time) relies on — so the check and the actual search now agree on where the path points. ✔
  • path import retained: glob.ts/grep.ts/ripGrep.ts still use path elsewhere, so no unused-import fallout.
  • Cosmetic: in ripGrep.test.ts the tool variable is named grepTool but is actually new RipGrepTool(...) — a pre-existing naming quirk in that file, not introduced here; the new test does exercise the ripgrep tool.

Reproduce

git worktree add --detach /tmp/wt-after  $(gh pr view 5378 --json headRefOid -q .headRefOid)
git worktree add --detach /tmp/wt-before origin/main
for w in wt-after wt-before; do ln -s "$PWD/node_modules" /tmp/$w/node_modules; \
  ln -s "$PWD/packages/core/node_modules" /tmp/$w/packages/core/node_modules; done
(cd /tmp/wt-after/packages/core && ../../node_modules/.bin/vitest run --coverage.enabled=false \
  src/tools/glob.test.ts src/tools/grep.test.ts src/tools/ripGrep.test.ts -t getDefaultPermission)   # 9/9
# Catch the bug: copy PR tests onto BASE source -> the 3 tilde tests fail ('allow' instead of 'ask')
🇨🇳 中文版(点击展开)

✅ 本地验证报告 — PR #5378 fix(core): resolve tilde paths before search permission checks

结论:强烈建议合并。 这是一个正确、最小、对安全有正面意义的修复。搜索工具的默认权限检查现在会像执行阶段一样展开 ~,因此对 ~/outside-workspace 的搜索会正确地要求确认(ask),而不再默默地默认为 allow。新测试确实能抓到该 bug,glob/grep/ripgrep 行为一致,且无类型/lint 回归。

Bug(一个确认绕过)

getDefaultPermission 此前用 path.resolve(targetDir, params.path)。对 ~/secret,Node 把 ~ 当成字面片段 → <workspace>/~/secret → 看起来在 workspace → 返回 allow(无需确认)。但执行阶段用的是支持 ~ 的解析,工具实际搜索的是真实 home 目录(在 workspace 外)。结果:对外部 ~ 路径的搜索没有触发本应有的确认。本 PR 在三个工具里把 path.resolve 换成 resolvePath(与校验阶段相同的 ~ 展开解析器)。

验证方式

tmux 中、隔离 worktree 里运行真实 vitest:AFTER = 4052e823(PR head,MERGEABLE/CLEAN),BEFORE = origin/mainca1ab06b),软链接复用 node_modules。Node v22.22.2。可干净合并;3 个源文件在 merge-base 与 main 之间一致。

检查项 结果
干净合入 main ✅ MERGEABLE / CLEAN
PR getDefaultPermission 测试(glob+grep+ripgrep) 9/9 通过
回归测试有效性 —— PR 测试跑 BASE 源码 3 个 “ask for tilde” 测试失败,报 expected 'allow' to be 'ask';6 个 workspace 内测试两边都过 → 证明 base 存在该绕过
边界用例探测(真实 workspace + 真实 resolvePath + 真实 containment) 7/7(见下)
tsc --noEmit core 0 新增类型错误(干净,与 base 一致)
prettier --check + eslint(6 文件)+ git diff --check ✅ 均 exit 0

这些测试是真实行为证明、并非被 mock 掉:真实的 /tmp 下临时 workspace、真实 containment 检查、真实的 resolvePath——所以 ~/outside-workspace/root/outside-workspace(不在 /tmp/... 内)→ PR 上返回 ask,而 base 的 path.resolve 得到 <workspace>/~/outside-workspaceallow

边界用例探测(真实 glob 工具,PR head)

输入 结果
无 path / sub / ./sub allow ✅(正常 workspace 路径不受影响)
~(home 目录) ask
~/x(workspace 外 tilde) ask
绝对路径 /tmp ask
../../..(向上穿越) ask

说明(非阻塞)

  • 与执行一致: 权限检查现在用 resolvePath,而执行时用的 resolveAndValidatePath 也依赖同样的 ~ 展开——所以检查与实际搜索现在对“路径指向哪里”达成一致。✔
  • path 导入仍需要: glob.ts/grep.ts/ripGrep.ts 其他地方仍用到 path,不会产生未使用导入问题。
  • 无伤大雅: ripGrep.test.ts 里工具变量名叫 grepTool,但实际是 new RipGrepTool(...)——这是该文件里既有的命名习惯,并非本 PR 引入;新测试确实测的是 ripgrep 工具。

Verified locally under tmux on worktrees at the PR head and origin/main: PR permission tests, a genuine-regression run of the PR's tests against base source (3 tilde tests flip allow→ask), and an edge-case probe driving the real glob tool through real resolvePath + real workspace-containment for bare ~, tilde, absolute, and parent-traversal paths.

@wenshao

wenshao commented Jun 19, 2026

Copy link
Copy Markdown
Collaborator

✅ Maintainer verification — real local + tmux testing (backs my approval)

Built the PR head (4052e8232) in an isolated worktree (Node v22.22.2, macOS) and verified with a deterministic mutation + a real-TUI tmux A/B that reproduces the security gap live. The fix is correct, minimal, and well-scoped — recommend merge.

What it fixes

All three search tools (glob / grep / ripGrep) computed their default permission with path.resolve(targetDir, params.path). For a ~/… path, Node treats ~ as a literal path segment, so the resolved path lands inside the workspace → getDefaultPermission returns allow → the external search runs with no confirmation. But execution resolves the path through resolveAndValidatePath → resolvePath, which does expand ~ to the real home (allowExternalPaths: true). So the permission gate was evaluated on a different path (<cwd>/~/…, in-workspace) than the one actually searched (<home>/…). The PR swaps path.resolve → resolvePath in all three getDefaultPermission methods, aligning the gate with execution.

Real-TUI A/B (tmux, DEFAULT approval mode)

Real binary, isolated HOME, workspace = /tmp/pr5378-cwd, planted ~/outside-workspace/secret.txt (out of workspace). Prompted the model to grep it; it passed the literal ~/outside-workspace to the tool both times.

PRE-FIX (path.resolve) FIXED (resolvePath)
Permission for ~/outside-workspace allow ask
Confirmation dialog none — auto-ran "Do you want to proceed?" shown, waits for user
Out-of-workspace file silently searched & read ~/outside-workspace/secret.txtFound 1 match … NEEDLE_PR5378_SECRET gated behind explicit confirmation
PRE-FIX:  ✓ Grep 'NEEDLE_PR5378_SECRET' in path '~/outside-workspace'   (no prompt, read /tmp/pr5378-home/outside-workspace/secret.txt)
FIXED:    ? Grep 'NEEDLE_PR5378_SECRET' in path '~/outside-workspace'  →  Do you want to proceed?  1. Yes  …  4. No   (Waiting for user confirmation…)

Pre-fix read an out-of-workspace home file with zero confirmation — exactly the gap this closes.

Mutation (deterministic, getDefaultPermission level)

Revert the 3 source files to base (keep the PR's tests) → the three new should return ask for tilde paths outside workspace tests FAIL with expected 'allow' to be 'ask' (explicitly showing pre-fix allow / fixed ask). The non-tilde should return ask for paths outside workspace test (ripGrep) still passes on the mutant → the bug was specific to tilde paths (absolute out-of-workspace paths were already correct). allow for paths within workspace passes on both → no false positives.

No-regression

143 / 143 tests pass across glob.test.ts (46) · grep.test.ts (37) · ripGrep.test.ts (60) on the FIXED build.

Reverse-audit notes

  • Identical getDefaultPermission pattern across all 3 tools — consistent fix.
  • ~user paths (e.g. ~bob/x) are not expanded by resolvePath, so they stay in-workspace (<cwd>/~bob/x) and never escape — no residual hole; the real escape vectors are ~ and ~/, both fixed.
  • Out-of-scope nit (not a blocker, pre-existing, not in this diff): the confirmation dialog's "Always allow … in <path>" scope label still shows the literal-tilde path <cwd>/~/outside-workspace/ (built by the tool-confirmation framework, not getDefaultPermission), so picking "always allow" on a tilde path may allowlist the wrong path. Worth a separate follow-up; it doesn't affect this PR's ask/allow decision.

Verdict

Correct, minimal, well-tested security fix. The live tmux A/B shows it closes a real "silently search & read outside the workspace" gap in DEFAULT approval mode (no effect in YOLO, where everything is auto-approved). Recommend merge. (Already approved by me; the remaining BLOCKED state is the required checks, not a review objection.)

🇨🇳 中文版(点击展开)

✅ 维护者验证 —— 本地真实 + tmux 测试(为我的 approve 背书)

在隔离 worktree(Node v22.22.2、macOS)构建 PR head(4052e8232),用确定性变异测试 + 真实 TUI tmux A/B(实时复现该安全缺口)验证。修复正确、精简、范围得当 —— 建议合并。

修了什么

三个搜索工具(glob / grep / ripGrep)此前用 path.resolve(targetDir, params.path) 计算默认权限。对 ~/… 路径,Node 把 ~ 当作字面路径段,于是解析结果落在 workspace getDefaultPermission 返回 allow → 外部搜索无需确认就执行。但实际执行通过 resolveAndValidatePath → resolvePath 解析路径,它~ 展开到真实 home(allowExternalPaths: true)。于是权限判定用的路径(<cwd>/~/…,在 workspace 内)与实际搜索的路径(<home>/…不是同一个。本 PR 在三个 getDefaultPermission 里把 path.resolve 换成 resolvePath,让权限闸与执行对齐。

真实 TUI A/B(tmux,DEFAULT 审批模式)

真实二进制、隔离 HOME、workspace = /tmp/pr5378-cwd、在 workspace 外放置 ~/outside-workspace/secret.txt。提示模型去 grep 它;两次它都把字面 ~/outside-workspace 传给了工具。

修复前(path.resolve 修复后(resolvePath
~/outside-workspace 的权限 allow ask
确认对话框 无 —— 自动执行 弹出 "Do you want to proceed?",等待用户
workspace 外的文件 静默搜索并读取了 ~/outside-workspace/secret.txtFound 1 match … NEEDLE_PR5378_SECRET 被显式确认拦住
修复前:  ✓ Grep 'NEEDLE_PR5378_SECRET' in path '~/outside-workspace'   (无弹框, 读取了 /tmp/pr5378-home/outside-workspace/secret.txt)
修复后:  ? Grep 'NEEDLE_PR5378_SECRET' in path '~/outside-workspace'  →  Do you want to proceed?  1. Yes … 4. No   (等待用户确认…)

修复前在零确认下读取了 workspace 外的 home 文件 —— 正是本 PR 关掉的缺口。

变异测试(确定性,getDefaultPermission 层)

把 3 个源文件还原到 base(保留 PR 的测试)→ 三个新测试 should return ask for tilde paths outside workspace 失败,报 expected 'allow' to be 'ask'(直接显示 修复前=allow / 修复后=ask)。而非 tilde 的 should return ask for paths outside workspace(ripGrep)在 mutant 上仍通过 → 漏洞专属于 tilde 路径(绝对的 workspace 外路径本就正确)。allow for paths within workspace 两端都过 → 无误杀。

无回归

FIXED 构建上 glob.test.ts(46) · grep.test.ts(37) · ripGrep.test.ts(60) 143 / 143 全过。

反向审计补充

  • 三个工具的 getDefaultPermission 模式完全一致 —— 修复统一。
  • ~user 路径(如 ~bob/x不会resolvePath 展开,仍留在 workspace 内(<cwd>/~bob/x),不会逃逸 —— 无残留漏洞;真正的逃逸向量是 ~~/,都已修复。
  • 超出范围的小瑕疵(非阻塞、既有、不在本 diff): 确认框里 "Always allow … in <path>" 的作用域标签仍显示字面 tilde 路径 <cwd>/~/outside-workspace/(由 tool-confirmation 框架生成,不是 getDefaultPermission),所以对 tilde 路径选"always allow"可能 allowlist 到错误路径。值得单独跟进;不影响本 PR 的 ask/allow 判定。

结论

正确、精简、测试充分的安全修复。实时 tmux A/B 证明它在 DEFAULT 审批模式下关闭了一个真实的*"静默搜索并读取 workspace 外内容"*缺口(YOLO 模式下无影响,那里一切自动批准)。建议合并。(我已 approve;剩余的 BLOCKED 是必需的 checks,不是 review 异议。)

Method: isolated worktree build of 4052e8232 · source-mutation on the 3 getDefaultPermission methods (revert → the 3 tilde tests flip allowask, non-tilde stays correct) · real-TUI tmux A/B in DEFAULT approval mode (pre-fix silently reads ~/outside-workspace, fixed prompts) · 143/143 glob+grep+ripGrep tests on the rebuilt FIXED binary.

@wenshao
wenshao merged commit cb82a91 into QwenLM:main Jun 19, 2026
33 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.

Search tool permission checks do not expand tilde paths

3 participants