gyugyu86/tauri-audit

GitHub: gyugyu86/tauri-audit

tauri-audit 是一款针对 Tauri v1/v2 应用的静态配置安全分析工具,通过解析配置文件和 Lockfile 来检测不安全的权限、CSP 和依赖问题,并区分严重性与置信度以减少误报。

Stars: 0 | Forks: 0

# tauri-audit 针对 [Tauri](https://tauri.app) v2 和 v1 应用的静态安全分析器。它会在**不运行应用**的情况下解析你的配置和源代码,因此非常快速、安全且对 CI 友好。 ## 适用场景 tauri-audit 是你现有依赖扫描工具的**补充**,而不是替代品: | 关注点 | 工具 | | --- | --- | | 不安全的**配置** —— 危险的标志、过于宽泛的 capability 权限范围、缺失的 CSP | **tauri-audit** | | 存在漏洞的 **Rust 依赖** | [`cargo-audit`](https://github.com/rustsec/rustsec) / RustSec | | 存在漏洞的 **npm 依赖** | `npm audit` | | 跨任意语言的通用代码模式 | [Semgrep](https://semgrep.dev), [CodeQL](https://codeql.github.com) | Semgrep 和 CodeQL 用于分析代码;而 tauri-audit 用于分析决定代码被允许执行哪些操作的配置。它们都不知道 `dangerousDisableAssetCspModification` 是什么意思,也不知道 `$CONFIG/**` 与 `$APPCONFIG/**` 是不同的授权。 tauri-audit 确实检查了少量 Tauri 特有的安全公告,这些漏洞只有在与你的配置结合时才具有威胁(因此单纯的版本更新检查可能会产生误导),但全面的依赖审计并不是它的工作。 **支持 Lockfile。** 清单文件记录的是版本*范围*;只有 lockfile 记录了实际安装的内容。Cargo 是最典型的例子:`tauri-plugin-shell = "2.2.0"` 意味着 `^2.2.0`,并且几乎肯定会安装已修复的 2.2.1 版本,因此如果将其读取为“2.2.0,存在漏洞”,就会错误地标记出正常的项目。 | Lockfile | 支持情况 | | --- | --- | | `Cargo.lock`, `package-lock.json`, `pnpm-lock.yaml` (v5/v6/v9) | 读取已解析版本 —— 检测结果是**已确认的** | | `yarn.lock`, `bun.lock*`, 无法识别的 `pnpm-lock.yaml` 版本 | 未解析 —— 检测结果回退到清单范围,并报告为**可能的** | | 无 lockfile | 仅参考清单范围 —— **可能的** | 这种回退机制倾向于报告:如果某个范围*可能*包含受影响的版本,就会被标记,并且检测结果会说明已安装的版本未知。没有任何问题会被隐藏。 **它宁可接受漏报也要避免误报。** 那些无法通过静态分析验证其豁免条件的规则会被报告为 `heuristic`(启发式),并且默认情况下绝不会导致你的构建失败。请参阅[诚实的局限性](#honest-limitations)。 ## 安装 ``` npm install -g tauri-audit # 或者在不安装的情况下运行它 npx tauri-audit ./my-tauri-app ``` 需要 Node.js >= 22.12。 ## 用法 ``` tauri-audit [options] ``` | 选项 | 效果 | | --- | --- | | `--json` | 机器可读的 JSON 输出 | | `--markdown` | Markdown 报告 | | `--sarif` | SARIF 2.1.0,用于 GitHub 代码扫描 | | `--category ` | SARIF 类别 (`run.automationDetails.id`) | | `--strict` | 遇到 `heuristic` 的 critical/high 检测结果时也会失败 | | `--no-fail` | 遇到检测结果时不失败(但操作错误仍会以 exit code 2 退出) | ### 严重性和置信度是分开的 每个检测结果都包含两个独立的值: - **severity(严重性)** —— `critical` / `high` / `medium` / `low` / `info`:*如果属实*,问题有多严重。 - **confidence(置信度)** —— `high` / `heuristic`:我们有多确信它*确实*是真的。 它们不会合并为一个数字。如果我们只能怀疑存在某个 critical 问题,它的置信度将保持为 `heuristic`。这一点很重要,因为几个 Tauri CVE 带有无法通过静态交叉检查的豁免条件(“如果……则不受影响”)—— 这些规则被永久固定为 `heuristic`,并且它们的检测文本会告诉你如何手动确认。 对于衍生自 CVE 的规则,severity 以 **GitHub Security Advisory 标签**为主要来源。当评分来源出现分歧时 —— 例如 CVE-2023-46115 在 GHSA 中为 `Low`,在 NVD 中为 `5.5 Medium`,而在 CNA 中为 `8.4 High` —— 检测结果会引用所有这些评分,而不是随意挑选一个。 ### 退出代码(CI 门禁) | 代码 | 含义 | 是否被 `--no-fail` 抑制? | | --- | --- | --- | | `0` | 分析完成;无任何门禁触发 | — | | `1` | 存在触发门禁的检测结果 | 是 | | `2` | 操作错误,**或分析无法完全覆盖该项目** | 否 | “触发门禁”默认意味着 **高置信度的 `critical` 或 `high`**;`--strict` 会将其扩大到包含这些严重程度的 `heuristic` 检测结果。 这个精确的判定条件也是干净语料库回归测试的通过条件,因此“该工具不会破坏此应用的 CI”只有一个定义,且只在一处进行检查。 #### 语料库结果实际说明了什么 这里有两个声明,且它们并不相同: - **十个真实的第三方应用程序未产生任何高置信度的检测结果。** 这就是“没有误报”的含义,并且对所有十个应用都成立。 - **其中十个应用里有八个被完全分析。** 另外两个 —— Tauri 官方自身的 v1 `helloworld` 和 `isolation` 示例 —— 包含 `tauri.conf.json` 但没有 `Cargo.toml`,因为上游将它们的 Rust 清单保存在了别处。因此,针对它们的依赖规则未检查任何内容,这些运行会以 `2` 而不是 `0` 退出,并说明了原因。 掩盖这种差异,就等同于这个工具拒绝在其自身输出中犯下的错误。每个应用的覆盖率都记录在 `tests/corpus/` 中,与检测结果放在一起。 #### 无法分析并不代表安全 退出代码 `2` 涵盖的范围不仅仅是错误的标志。如果无法解析某个配置、无法将其归类为 v1 或 v2、因体积过大被跳过,或者根本不存在,那么**就不会有任何规则对其进行检查** —— 此时的“零检测结果”意味着沉默,而不是安全。tauri-audit 拒绝将这种情况报告为成功:它会打印出无法分析的内容并以 `2` 退出。 `--no-fail` 是针对检测结果的声明(“不要因为你发现的问题而阻止我的构建”),而不是声称运行成功,因此它无法抑制这种情况。如果你将 tauri-audit 指向一个没有 Tauri 项目的目录,你会得到 `2`,而不是一份健康证明。 ## GitHub Action ``` - uses: gyugyu86/tauri-audit@v0 with: path: . category: tauri-audit # give each run its own category ``` 扫描步骤本身绝不会失败,因此 SARIF 总是能到达 Security 标签页;门禁是一个独立的步骤。 ## 规则 规则文档存在于检测文本本身中 —— 每个检测结果都会解释为什么该设置很危险,以及如何确认或修复它,因此无需此表也可阅读报告。 | ID | 适用于 | 检测内容 | Severity / confidence | Evidence | | --- | --- | --- | --- | --- | | `TA-CONF-001` | v1 + v2 | `security.csp` 未设置或为 `null` —— 未强制执行任何策略 | medium / heuristic | real-world | | `TA-CONF-002` | v1 + v2 | `security.dangerousDisableAssetCspModification` | `true` → high / high · 指令列表 → medium / heuristic | synthetic | | `TA-V1-001` | v1 | `tauri.allowlist.all: true` —— 同时启用所有 v1 API | high / high | real-world | | `TA-V1-002` | v1 | `tauri.security.dangerousRemoteDomainIpcAccess` —— 远程源被授予 IPC 权限 | `enableTauriAPI` 或插件 → high / high · 仅域名 → medium / heuristic | synthetic | | `TA-V1-003` | v1 | `tauri.security.dangerousUseHttpScheme: true` | medium / high | synthetic | | `TA-DEP-001` | v2 | shell plugin ≤ 2.2.0 且具有未设置的 `open` 范围 (CVE-2025-31477) | high / heuristic | synthetic | | `TA-VITE-001` | 任意 | Vite `envPrefix` 覆盖了 Tauri 签名变量 (CVE-2023-46115) | low / heuristic | synthetic | | `TA-CAP-003` | v2 | capability 文件系统范围超出了应用程序 | medium / heuristic | real-world | **Evidence** 记录了该规则是否已在真实的第三方代码上触发,并作为规则元数据(而非文档)携带,因此测试可以对其进行检查。`real-world` 意味着该规则会在 `tests/corpus/` 中引入的未修改配置上触发;`synthetic` 意味着它仅在为本存储库编写的测试夹具上得到过验证。规则可能会低估其证据,但如果没有语料库应用程序作为支撑,就不能声称是 `real-world` —— 这种强制约束在测试套件中得到了断言。 在*编写正确*的应用程序上触发也算数,并且值得仔细阅读:它证明了该规则能匹配真实代码,同时也证明了该规则必须保持为 `heuristic`,因为在那里的高置信度检测结果会导致那些项目的构建失败。这就是 `TA-CONF-001` 目前所处的状态。 大多数规则是 `synthetic` 的,因为它们所寻找的设置在已发布的代码中很罕见 —— 这正是它们被称为危险的原因。这种不对称是预料之中的,而不是需要填补的空白,并且 `tests/corpus/README.md` 确切列出了哪些规则仍缺乏真实世界的素材。 ### 说实话,这在 v2 上有什么价值 上面大多数确定性规则仅适用于 v1,这是 Tauri 本身的事实,而不是覆盖范围的问题。v1 的 allowlist 是 opt-in(可选择加入)的但较为粗糙,并且带有项目可以开启的显式 `dangerous*` 开关。v2 用 capabilities 替换了它,它们是默认拒绝且基于每个命令的 —— 因此在任何解读下都属于错误的设置变少了。 因此,在 v2 上的价值在性质上是不同的。它不是“寻找危险标志”;而是**让 capability 授权变得可审查**:注意到文件系统范围超出了应用程序、某个 CVE 的豁免无法被确认、或者没有设置 CSP。这些都是为人类提供的判断依据,这就是为什么 v2 规则是 `heuristic` 的,也是为什么它们不会触发门禁。如果你使用的是 v2,并且这个工具没有报告任何触发门禁的内容,那就是预期的结果,并且很大程度上归功于 v2 的设计。 ### 关于 `true-positive/` 中的应用程序 `tests/corpus/true-positive/` 包含了触发规则的真实配置。它们作为规则确实能检测到问题的证据而存在,这也是干净语料库无法做出的声明。 这两个条目都不是关于应用程序不安全的报告。 Tauri 官方自身的 `examples/api` 通过设置 `allowlist.all: true` 触发了 `TA-V1-001`。这**不是 Tauri 的缺陷**:它是一个 API 演示,其目的是在一个地方测试每个 API,因此启用所有 API 对于它的用途来说是正确的,只有在被复制到产品中时才是错误的。 KiwiTalk 触发了相同的规则。它被包含在内是作为**此规则检测到的模式出现在真实应用程序中的一个案例** —— 仅此而已。检测结果陈述的是关于公开配置文件的一个事实:`allowlist.all: true` 同时启用了每个 v1 API 家族。在该应用程序中这是否可被访问取决于它的前端(此工具不分析前端),并且对此不做任何声明。 两者出现在这里的原因相同:由了解该框架的人编写的、许可宽松的配置,远比由规则编写者编写的测试夹具是更好的证据。 **极性是根据主要来源针对每个规则决定的。** 对于上面的多数规则,危险状态是显式开启的设置,因此缺失的键是安全的。有两个规则则是完全相反的。`TA-DEP-001` 将**未设置**的 `plugins.shell.open` 视为受影响,因为未设置值所选择的默认验证正是出问题的部分。`TA-CONF-001` 在未配置 CSP 时触发,因为该字段默认为 `null`,而 `null` 的 CSP 不强制执行任何操作。在上述任何一种情况下,假设采用熟悉的极性都会漏掉整个受影响的目标群体。 因此,`TA-CONF-001` 会在很大一部分真实应用程序上触发,这是有意为之的:它是 `medium`/`heuristic` 的,绝不会导致构建失败。十个语料库应用程序中有五个在没有 CSP 的情况下发布,报告这一点是正确的,而不是误报。 其中有两个规则通过内容而不是存在与否来对进行评级。 `dangerousDisableAssetCspModification: true` 完全关闭了 Tauri 的 CSP 重写,而指令数组则对其进行了收窄 —— 同一个键,两种不同的设置,因此它们的报告方式也不同。同样地,授予 `enableTauriAPI` 的 remote-IPC 条目交出了整个 API 接口,而仅命名域名和窗口的条目则启用了该机制,但不会仅通过该设置授予命令权限,并且这些窗口在其他情况下暴露了什么在配置中是不可见的。 更多规则正在推出 —— 请参阅[路线图](#roadmap)。 ## 诚实的局限性 这些都是刻意为之的。一个因为自身的误报而导致 CI 失败的静态分析器,是不会有人把它留在流水线里的。 - **应用永远不会被执行。** 配置是被解析的,而不是被评估的。任何在运行时决定的内容都是不可见的。 - **豁免条件通过降级来遵循,而不是通过猜测。** 如果公告说“如果 X 则不受影响”,而 X 无法进行静态检查,则该规则保持为 `heuristic`,并且检测结果会告诉你如何自己验证 X。 - **行号对于 JSON 是精确的,对于 JSON5 和 TOML 是近似的。** `tauri.conf.json` 和 `capabilities/*.json` 解析时带有位置信息;`tauri.conf.json5` 和 `Tauri.toml` 会回退到键扫描,并且可能指向包含该键的整个区域。 - **混合或无法识别的配置会被跳过,而不是靠猜测。** 如果无法自信地将某个文件归类为 v1 或 v2,则不会对其应用任何配置规则,并会记录一条警告 —— 将 v1 规则应用于 v2 配置(或反之)是纯粹产生误报的根源。 - **`vite.config.*` 是通过文本扫描读取的,而不是被解析的。** 这个包中还没有 JavaScript 解析器,因此由变量构建的、从另一个对象展开的或作为模板字面量编写的 `envPrefix` **不会被分析**,也不会产生检测结果。在查找该选项之前,注释和字符串字面量会被屏蔽掉,因此注释掉的或带引号的内容提及也不会触发它。 - **目前还没有数据流分析。** 没有任何机制跟踪值从源到接收端的过程,因此规则无法判断攻击者可控的输入是否到达了危险的调用。当它实现时(Phase 3),它将仅限于单函数作用域:跨函数流、返回值和重新赋值将不会被追踪,这是为了以漏报换取避免误报而刻意设计的。 - **Rust 源码分析尚未实现**(已计划,基于 tree-sitter)。 - **Lockfile**:`Cargo.lock`、`package-lock.json` 和 `pnpm-lock.yaml` (v5/v6/v9) 提供已确认的版本;`yarn.lock` 和 `bun.lock*` 会被识别但不会被解析,因此这些项目会回退到清单范围,并且它们的依赖项检测结果会说明这一点。 - **证据强度因规则而异**,每个规则都会说明它拥有哪种证据 —— 请参阅上面的 `Evidence` 列。 ## 路线图 - **Phase 1 (MVP)** —— 配置规则,仅限 TypeScript。首先推出确定性规则以建立零误报流水线,然后将 CVE 和上下文相关规则作为 `heuristic` 推出。 - **Phase 2** —— JavaScript/TypeScript AST 规则(`invoke`、`shell.open`)。 - **Phase 3** —— 通过 tree-sitter 进行 Rust 分析,单函数作用域数据流,仅限 `heuristic`。 ## 开发 ``` npm ci npm run build # tsc; no bundler npm run lint npm test # all layers, fully offline ``` 分析引擎(`src/core/`)对 CLI(`src/cli/`)一无所知;依赖关系是单向的,因此引擎保持可嵌入式。 ## 许可证 MIT。请参阅 [LICENSE](LICENSE)。 `tests/corpus/` 下的测试夹具是保留在其自身许可证下的第三方配置文件;每个目录都包含一个 `PROVENANCE.md`,记录了其来源、提交和许可证。它们是测试数据,不是分发包的一部分(`npm` 仅发布 `dist/`)。
标签:MITM代理, Rust, Tauri, 暗色界面, 网络流量审计, 自动化攻击, 配置安全, 错误基检测, 静态代码分析