Skip to content

fix: HOU-15 安全高危:capabilities 收窄 + open_reader 作用域校验(H1/H2) - #28

Merged
hehetoshang merged 6 commits into
mainfrom
fix/hou14-security
Aug 10, 2026
Merged

fix: HOU-15 安全高危:capabilities 收窄 + open_reader 作用域校验(H1/H2)#28
hehetoshang merged 6 commits into
mainfrom
fix/hou14-security

Conversation

@hehetoshang

@hehetoshang hehetoshang commented Aug 9, 2026

Copy link
Copy Markdown
Collaborator

修复 HOU-15(H1/H2)并收尾 HOU-30:default.json 收窄 fs/opener 任意路径、open_reader 放行前校验路径、移除 get_environment_variable、统一 fs allow 列表、收窄 ohos opener、修复 readest E0308 编译错误。含 readest 子模块指针更新(hehetoshang/readest fix/hou14-security 1ea7ca3)。

改动

  • readest 子模块hehetoshang/readest @ 1ea7ca3,分支 fix/hou14-security):
    • H2:open_reader_window 放行前用 is_reader_scope_grantable 校验路径(app_data_dir()/books 内或已在 fs_scope),否则拒绝持久化 scope 授权;纯函数 + 单元测试。
    • H1:get_environment_variable 从嵌入式(Moke 使用的)handler 移除;standalone 入口仍注册。
    • HOU-30:E0308 修复 — app.path().app_data_dir()Result::ok()Option 后传入 is_reader_scope_grantable(fail-closed:app_data_dir() 失败时仅在路径已在 fs_scope 才放行,拒绝分支仅告警并正常开窗)。
  • src-tauri/capabilities/default.json / ohos.json
    • fs allow 列表统一为应用私有/临时目录 $APPDATA/$APPCONFIG/$APPCACHE/$APPLOG/$TEMP/** 通配,不含裸目录条目(防止对 app-data 根执行 remove/rename/mkdir);opener:allow-open-path 移除;六个 fs 列表(read/write/write-text-file/write-file/mkdir/scope)在两个平台文件中逐一一致。
    • 有意放宽(记录):fs 路径形态从 $APPDATA/**/* 调整为 $APPDATA/**。readest 的根级 settings.json 直接位于 app-data/app-config 根(safeSaveJSON 写根级 settings.json.bak),/** 才可写;$APPLOG 供 readest 日志(BaseDirectory.AppLog)。范围仍严格限定在应用私有/临时目录,相较修复前 ** 是大幅收窄。
    • readest 的“打开本地书籍/目录”走 @tauri-apps/plugin-dialog,所选路径由 dialog 插件运行时加入 fs/asset scope(再经 allow_paths_in_scopes),不依赖静态任意路径授权,收窄不影响。
  • AGENTS.md:修正 readest/ 描述 —— 是 git submodule(fork hehetoshang/readest),非已扁平化的普通文件夹。
  • tests/capabilities-scope.test.mjs:回归守卫(fs/opener allow 必须落在应用私有/临时目录内,拒绝裸根、** 全盘通配、.. 穿越、Windows 反斜杠路径全覆盖)+ default/ohos 六个 fs 列表逐一相等(assert.deepEqual)。

验证

  • Rust(CI Rust tests 同款命令):cargo test --lib --locked(src-tauri)17 passed;readestlib 78 passed(含 open_reader_scope_tests)。

  • pnpm test 33 passed(含 capabilities-scope 回归测试);pnpm lint 0 errors;pnpm typecheck 通过。

  • get_environment_variable:生产前端无调用,standalone 入口保留,嵌入式 handler 移除,无运行时影响。

  • $TEMP/** 保留理由:readest 前端需要任意 temp 路径读写 —— bookService.ts:865-866$TEMP/<filename>(根级临时副本)、nativeAppService.ts:799-805/:859-863 分享/存图写 $TEMP/shared/CacheManagerWindow.tsx:44 清缓存对 $TEMP 根 readDir+remove。收窄为 bundle-id 子目录会破坏这些流程;已限定 $TEMP/** 且不含裸根。

  • 裸根防护已用真实 glob 匹配测试固化:moke fs_scope_tests50c92c5)用 vendored glob 0.3.4 + Tauri 的 require_literal_separator: true 断言 $APPDATA/** 不匹配裸根,并解析 default/ohos.json 逐条校验 fs/opener allow 不匹配其裸根;随 CI Rust tests 运行。

Comment thread readest Outdated
Comment thread readest Outdated
Comment thread src-tauri/capabilities/default.json
Comment thread src-tauri/capabilities/default.json
Comment thread src-tauri/capabilities/default.json
Comment thread src-tauri/capabilities/default.json
Comment thread tests/capabilities-scope.test.mjs Outdated
Comment thread src-tauri/capabilities/default.json
Comment thread src-tauri/capabilities/default.json Outdated
Comment thread src-tauri/capabilities/ohos.json
Comment thread src-tauri/capabilities/default.json Outdated
Comment thread src-tauri/capabilities/default.json Outdated
Comment thread tests/capabilities-scope.test.mjs Outdated
Comment thread tests/capabilities-scope.test.mjs Outdated
Comment thread readest Outdated
Comment thread src-tauri/capabilities/default.json
Comment thread tests/capabilities-scope.test.mjs Outdated
Comment thread tests/capabilities-scope.test.mjs
Comment thread tests/capabilities-scope.test.mjs Outdated
Comment thread src-tauri/capabilities/default.json
Comment thread src-tauri/capabilities/default.json
{ "path": "$APPCONFIG/**" },
{ "path": "$APPCACHE/**" },
{ "path": "$APPLOG/**" },
{ "path": "$TEMP/**" }

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

安全考量(Medium):$TEMP/** 是五个基目录中唯一非「应用私有」的目录。

$TEMP 指向系统临时目录(std::env::temp_dir();Windows 为 %TEMP%,Linux 为 /tmp)。同一用户的其他进程、甚至其他用户的临时文件都位于其中。由于 Tauri v2 的 fs 插件把所有 fs allow 列表合并成一个全局 scope,而 fs:allow-remove / fs:allow-rename / fs:allow-open(本文件里是裸字符串权限,作用于合并后的 scope)都在范围内,这意味着 WebView 内任何脚本(阅读器渲染不可信的 EPUB/HTML,本身就是一个注入面)可以对整个 $TEMP 下的文件做读取、删除、重命名——这超出了「应用私有/临时目录」的语义。

建议:

  1. $TEMP 收窄为应用专用子目录(如 $TEMP/<bundle-id>/**),或确认 readest 真的需要在任意 temp 路径上读写;
  2. 至少把「为何需要 $TEMP、为何是宽泛授予」写进注释,避免后续被误当安全边界。

(ohos.json 第 67/107/117 行同样存在该问题。)

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

已评估,保留 $TEMP/** 并说明理由(收窄到 $TEMP/<bundle-id>/** 不可行)。已核实 readest 前端确实需要任意 temp 路径读写:bookService.ts:865-866 把书籍临时副本写到 $TEMP/<filename>(根级);nativeAppService.ts:799-805:859-863 分享/存图把临时副本写到 $TEMP/shared/CacheManagerWindow.tsx:44 清缓存时对 $TEMP 根做 readDir+remove。若收窄为 bundle-id 子目录,这些流程会 NotAllowedError。收窄后仍严格限定在应用私有/临时目录($TEMP/**),且不含裸根(上一轮已移除),相较原 ** 是大幅收窄;理由已写入 PR 描述。ohos.json 与 default.json 保持一致。

"identifier": "fs:allow-read",
"allow": [{ "path": "**" }]
"allow": [
{ "path": "$APPDATA/**" },

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

核心安全假设需要测试固化(Medium):$APPDATA/** 是否真的不匹配裸根 $APPDATA

本次「去掉裸目录条目」的硬化(1843fa1)完全依赖于 $APPDATA/** 匹配 $APPDATA 本身。但按 glob crate 0.3.4(src-tauri/Cargo.lock 中 tauri 2.11.5 / tauri-utils 2.9.3 依赖的版本)的语义,** 匹配零个或多个路径段——Pattern::new("$APPDATA/**").matches("$APPDATA") 实际会返回 true。这与 PR 离线验证得出的「匹配 $APPDATA/settings.json 但不匹配裸根」结论相反。

如果 $APPDATA/** 运行时能匹配裸根,那么:

  • remove(appDataDir(), { recursive: true }) 依旧可行(配合 fs:allow-remove);
  • rename / mkdir(appDataDir()) 同理;
  • 本 PR 声称的「裸根不再在 scope 内」的硬化就不成立。

建议:在 readest(或 moke)的 Rust 单元测试中直接断言真实 scope 匹配行为——例如在 is_reader_scope_grantable 的测试旁边,加一个针对合并后 fs scope 的测试:remove(app_data_dir()) / read(app_data_dir()) 被拒绝。若确认 $APPDATA/** 确实匹配裸根,则需要保留对该路径的显式 deny 或调整 glob 形态。

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

已核实并以真实测试固化50c92c5)。用 vendored glob crate 0.3.4(moke Cargo.lock 中 tauri 解析的版本)按 Tauri Scope::is_allowed 的确切参数(require_literal_separator: true,tauri/src/scope/fs.rs)实测:$APPDATA/** 匹配 $APPDATA/books$APPDATA/settings.json不匹配裸根 $APPDATAmatches_path_with 返回 false)。review 中“** 匹配零个或多个段故裸根也匹配”的推断在 matches_path_with+require_literal_separator 下不成立(该选项使 ** 需越过至少一个分隔符)。已新增两个 Rust 测试(moke fs_scope_tests,随 CI cargo test --lib --locked 运行):double_star_does_not_match_bare_root 直接断言该语义;capability_fs_allow_entries_keep_bare_roots_out_of_scope 解析提交的 default.json/ohos.json,断言无 fs/opener allow 条目匹配其裸根。若将来 glob 形态改变导致匹配裸根,测试即红。

function isRestrictedToPrivateDirs(path) {
if (hasParentDirTraversal(path)) return false;
return ALLOWED_FS_OPEN_PREFIXES.some(
(prefix) => path.startsWith(`${prefix}/`) || path.startsWith(`${prefix}\\`)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

回归守卫只验证「字符串形态」,覆盖不了运行时 scope 语义。

isRestrictedToPrivateDirs 只检查 allow 路径的字面前缀($APPDATA/$APPDATA\),它无法验证 glob 在真实 Tauri scope 里是否匹配裸根。也就是说,本 PR 最关键的安全属性——「$APPDATA/** 不匹配裸根,因此裸根不再可被 remove/rename/mkdir」——没有任何自动化测试兜底;将来若有人把 scope 改回包含裸根的写法(字符串形式上仍可能是 $APPDATA/**),这个测试不会失败。

结合 default.json 第 19 行的评论:建议在 Rust 侧(readest 的 open_reader_scope_tests 旁边)增加一个真正执行 Tauri fs scope 匹配的测试,断言 remove(app_data_dir(), { recursive: true })read(app_data_dir()) 被拒绝。JS 侧保留字符串守卫即可,但要意识到它的边界。

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

已采纳(50c92c5)。在 moke src-tauri/src/lib.rs 新增 fs_scope_tests,用与 Tauri Scope::is_allowed 相同的 glob 版本+require_literal_separator: true 做真实匹配断言:裸根不被 $APPDATA/** 匹配(remove/read 裸根被拒),并解析提交的 capability 文件逐条校验。该测试随 CI Rust tests job(cargo test --lib --locked)运行,19/19 通过。JS 字符串守卫保留作第一道防线。

return permission.allow ?? [];
};
const [defaultFile, ohosFile] = CAPABILITY_FILES;
assert.deepEqual(getList(defaultFile), getList(ohosFile));

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

小建议:对比对数组顺序敏感,且未覆盖全部 fs 列表。

  1. assert.deepEqual 对数组是带顺序比较。当前 default/ohos 两张文件的六张列表顺序恰好一致;将来若有人仅调整顺序(内容不变),测试会误报失败。可改为先 sort 再比较,或比较归一化后的集合。
  2. 这里只对比了六张 fs 列表,fs:scope-appdata-recursive / fs:scope-appconfig-recursive 这两张递归 scope 列表(default.json 第 67-73 行)没有纳入对比。目前两个文件恰好一致,但它们不在守卫范围内,未来 drift 不会被发现。

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

已采纳(50c92c5)。assert.deepEqual 改为先提取 path 列表并 sort 后比较(顺序无关);对比列表从 6 个扩展为 8 个,纳入 fs:scope-appdata-recursivefs:scope-appconfig-recursive。两文件该两项现均为 $APPDATA/**/* / $APPCONFIG/**/*,IDENTICAL;pnpm test 35/35 通过。

hehetoshang and others added 5 commits August 10, 2026 09:14
…reader path grants (HOU-15)

H1: default.json no longer grants opener:allow-open-path / fs:allow-read /
fs:allow-write on '**'. fs:allow-read and fs:allow-write are narrowed to
$APPDATA / $APPCONFIG / $APPCACHE / $APPLOG / $TEMP (aligned with
ohos.json and fs:scope); opener:allow-open-path '**' is removed entirely
(openPath is unused by the frontend).

H2: bump readest submodule to 4733e44, which gates open_reader_window's
fs/asset scope grants behind app_data_dir()/books or an already-allowed
fs_scope path (mirrors allow_paths_in_scopes) and drops get_environment_variable
from the embedded reader handler.

Tests: moke pnpm lint/typecheck/test pass; default.json validated with no '**'
path entries; open_reader scope-grant logic covered by rustc unit tests.

Co-authored-by: multica-agent <github@multica.ai>
…** scope regression test (HOU-30)

- readest: bump submodule to 1ea7ca3 (E0308 fix: Result->Option for
  app_data_dir in open_reader scope check)
- src-tauri/capabilities/ohos.json: remove opener:allow-open-path with
  path ** (no frontend openPath usage, mirrors desktop narrowing)
- src-tauri/capabilities/default.json: add $APPLOG + bare-dir entries to
  fs:allow-write-text-file / fs:allow-write-file / fs:allow-mkdir /
  fs:scope so all fs allow lists match the merged Tauri v2 global scope
- AGENTS.md: readest is a git submodule (fork hehetoshang/readest), not a
  flattened plain folder
- tests/capabilities-scope.test.mjs: guard against re-introducing
  fs/opener allow entries with path ** in default.json and ohos.json

Co-authored-by: multica-agent <github@multica.ai>
…pe regression test (HOU-30 review)

- src-tauri/capabilities/ohos.json: fs:scope now uses the same app-private
  dir list as default.json ($APPDATA/$APPCONFIG/$APPCACHE/$APPLOG/$TEMP
  with bare-dir + ** entries), closing the default/ohos fs:scope drift the
  review flagged.
- tests/capabilities-scope.test.mjs: guard extended beyond the exact
  path === "**" form — every fs/opener allow path must be restricted to
  app-private/temp dirs ($APPDATA/$APPCONFIG/$APPCACHE/$APPLOG/$TEMP/
  $RESOURCE), so **/*, $HOME/**, C:\** etc. are rejected too; plus an
  assertion that default.json and ohos.json fs:scope lists are identical.

Co-authored-by: multica-agent <github@multica.ai>
…e guard (HOU-30 review 2nd round)

- default.json + ohos.json: remove bare-dir entries ($APPDATA/$APPCONFIG/
  $APPCACHE/$APPLOG/$TEMP alone) from every fs allow list. Glob
  $APPDATA/** already covers everything under the root (verified against
  the vendored glob crate: matches $APPDATA/settings.json but not the bare
  root), so the app-dir root itself is no longer in scope — a malicious
  reader script can no longer remove/rename/mkdir the whole app-data root
  via the merged global fs scope (remove(appDataDir(), recursive)).
- tests/capabilities-scope.test.mjs: drop $RESOURCE from the safe-prefix
  whitelist (install dir is never granted and writing it = binary
  overwrite; packaged assets go via the asset protocol), and reject any
  allow path containing a '..' segment (no more relying on the framework
  to normalize $APPDATA/../** traversal patterns).

Co-authored-by: multica-agent <github@multica.ai>
…ile fs parity (HOU-30 review 3rd round)

- default.json fs:allow-mkdir: remove the 5 remaining bare-dir entries
  ($APPDATA/$APPCONFIG/$APPCACHE/$APPLOG/$TEMP alone) missed in
  1843fa1 — now identical to ohos.json (5 /** wildcards), so no platform
  can mkdir the app-data root itself.
- tests/capabilities-scope.test.mjs:
  * fix dead backslash branch: match a single backslash (what JSON.parse
    produces) instead of two, so Windows-style paths are actually covered
    and a valid single-backslash entry is not misreported.
  * drop path === prefix from the allow check — a bare app-dir root in any
    fs list is now an offender (prevents remove/rename/mkdir-on-root regressions).
  * compare all six fs allow lists (allow-read/write/write-text-file/
    write-file/mkdir/scope) between default.json and ohos.json with
    assert.deepEqual, not just fs:scope.

Co-authored-by: multica-agent <github@multica.ai>
…atching (HOU-30 review 4th round)

- src-tauri/src/lib.rs (fs_scope_tests): add two Rust tests that exercise the
  same glob matching tauri::fs::Scope::is_allowed performs (glob crate 0.3.4
  + require_literal_separator: true):
  * double_star_does_not_match_bare_root — $APPDATA/** matches children and
    settings.json but NOT the bare $APPDATA root, so
    remove/rename/mkdir(appDataDir()) stay rejected (this is the core HOU-30
    hardening premise; empirically verified against the vendored glob crate).
  * capability_fs_allow_entries_keep_bare_roots_out_of_scope — parses the
    committed default.json and ohos.json and asserts no fs/opener allow entry
    matches its bare base root.
- tests/capabilities-scope.test.mjs: compare fs allow lists order-independently
  (sort paths) and include the two recursive scope lists
  (fs:scope-appdata-recursive / fs:scope-appconfig-recursive) in the
  default-vs-ohos parity check.
- src-tauri/Cargo.toml: add glob = "0.3" dev-dependency (already pinned at
  0.3.4 in Cargo.lock via tauri; lockfile only gains the moke->glob edge).

Co-authored-by: multica-agent <github@multica.ai>
Comment thread AGENTS.md
[hehetoshang/readest](https://github.com/hehetoshang/readest) (it was briefly flattened into a plain
folder on 2026-06-29, but is a submodule again — the `readest` entry in `.gitmodules` and the
`160000` gitlink are authoritative). To change reader code, commit on a branch inside the
`readest/` submodule checkout and push it to the fork, then bump the submodule gitlink in this

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

重要:PR 描述与提交信息声称 bump 了 readest 子模块,但实际 diff 未包含任何子模块指针变更。

验证结果:merge-base(5141a77,即当前 main 的 HEAD)与本分支全部 6 个提交(b3b8d643e40880)的 readest gitlink 均为 933628cgit diff 5141a77...3e40880 只涉及 9 个文件、不含 readest。提交信息里写的 "bump readest submodule to 4733e44 / 1ea7ca3" 与实际 tree 不符(疑似 rebase 到含 933628c 的新 main 后被消解)。

这意味着 H2(open_reader_window 放行前用 is_reader_scope_grantable 校验路径)存在两种可能,请确认其一:

  1. 933628c(readest fork,来自 HOU-32 Set A merges)已经包含该作用域校验 → H2 已随 chore(readest): bump readest submodule to 933628c (HOU-32 Set A merges) #37 先期就位,但本 PR 的描述/提交信息需要更正,避免误导。
  2. 933628c 未包含该校验 → H2 修复实际缺失,需要真正 bump 子模块指针并按本文件 112-114 行的流程提交,否则本 PR 只交付了 H1(capabilities 收窄),H2 未落地。

由于 readest 子模块未检出、且生成物不可见,无法在本地直接核对 open_reader_window 源码,建议合入前确认 fork 侧 933628c 的提交内容。

{ "path": "$APPCONFIG/**" },
{ "path": "$APPCACHE/**" },
{ "path": "$APPLOG/**" },
{ "path": "$TEMP/**" }

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

$TEMP/** 仍是全部授权中最宽的一条,建议明确风险边界。

配合已授予的 fs:allow-remove / fs:allow-rename / fs:allow-write$TEMP/** 允许对整个系统临时目录内的任意文件执行读写、删除与重命名。阅读器渲染不可信的 EPUB/HTML(恶意电子书)时,其 JS 可以读取同一用户其它进程写入的临时文件,或删除/篡改其它应用的临时数据(跨进程完整性/隐私风险)。

PR 描述已记录这是有意保留(readest 的 bookService.ts$TEMP/<filename>nativeAppService.ts$TEMP/shared/CacheManagerWindow.tsx$TEMP 根 readDir+remove),方向可以理解。

建议:

  • 在能力文件或代码注释中显式记录这条残余风险(当前仅在 PR 描述里说明,合入后无人可见);
  • 后续评估把 readest 的临时路径收敛到 bundle-id 子目录(如 $TEMP/moke-<id>/**),或至少让 $TEMP 只读、写入收敛到子目录,缩小恶意内容可破坏的范围。

{ "path": "$APPCONFIG/**/*" },
{ "path": "$APPCACHE/**/*" },
{ "path": "$TEMP/**/*" }
{ "path": "$APPDATA/**" },

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

顺带清理建议:fs:scope-appdata-recursive / fs:scope-appconfig-recursive 仍保留 /**/*,与本次统一为 /** 的约定不一致。

本次改动把六张 fs 列表统一为 $APPDATA/**/** 通配(以覆盖根级 settings.json),但下方 fs:scope-appdata-recursive / fs:scope-appconfig-recursive(未改动、仍为 $APPDATA/**/* / $APPCONFIG/**/*):

  • 语义差异:$APPDATA/**/* 不匹配根级文件$APPDATA/settings.json 无法命中),而 $APPDATA/** 可以;若这两条的本意是"整棵 app-data/app-config 递归",则 /**/* 与意图不符。
  • 由于全局作用域(fs:scope 及五个 allow 列表)已被 /** 覆盖,这两条在功能上已冗余。

建议对齐为 /** 或直接删除,避免后续维护者误读这两条与其它列表的差异。

Comment thread src-tauri/src/lib.rs
Some("$APPCACHE") => "/appcache",
Some("$APPLOG") => "/applog",
Some("$TEMP") => "/temp",
_ => continue,

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

测试覆盖盲区:_ => continue 会静默跳过非 $VAR 前缀的 allow 路径,裸根断言给出虚假的安全感。

path.split('/').next() 仅在首段命中 $APPDATA/$APPCONFIG/$APPCACHE/$APPLOG/$TEMP 之一时才计算 bare_root 并断言;其余形态(如 $HOME/**C:\**$RESOURCE/**,以及 Windows 反斜杠形式 $APPDATA\**——split('/') 会把它整体当成首段)全部落入 _ => continue静默跳过

目前 tests/capabilities-scope.test.mjs(字符串级前缀检查)能兜住这些形态,所以不是现实漏洞;但本测试作为"裸根防护"的 Rust 侧守卫,对未识别前缀的条目没有任何保护。建议改为 fail-closed:对无法识别基根的条目直接 panic!/assert!,或在注释中明确该测试只覆盖 $VAR/** 形态,避免后续添加 $HOME/** 之类条目时此测试仍然全绿。

// app-data root into scope, allowing `remove(appDataDir(), {recursive:true})`
// etc. (the exact regression this PR removed), so bare roots are rejected.
// Matching must use a single backslash (`\`), the form JSON.parse produces.
function isRestrictedToPrivateDirs(path) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

建议合并前确认运行时越界防护依赖的 Tauri 前提:scope 匹配前是否 canonicalize/剔除 ..

本测试(以及 lib.rs 的 Rust 测试)只校验静态能力文件中的 allow 模式:不含 .. 段、前缀落在私有目录内。但 HOU-30 "裸根防护"在运行时的成立,还取决于恶意阅读器脚本传入的运行时路径是否会被拦截,例如:

  • remove(appDataDir() + '/', { recursive: true }) —— 尾斜杠形态:glob /appdata/**/appdata/命中的** 可匹配空串);
  • readTextFile(appDataDir() + '/../somewhere') —— .. 越界:** 可跨越 /../ 段。

这两类路径是否被拒,取决于 vendored Tauri 2.11.5 的 Scope::is_allowed 是否在 glob 匹配前对路径做 canonicalize()(或显式拒绝 ParentDir 分量)。本环境无法检出 vendor/tauri 子模块核对源码,建议合入前在 CI/本地确认该前提成立(尤其是针对已存在目录的 remove 尾斜杠场景),否则"裸根不可删"的结论会被绕开。

@hehetoshang
hehetoshang merged commit 4d98bfb into main Aug 10, 2026
30 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.

1 participant