普通视图

发现新文章,点击刷新页面。
今天 — 2026年3月7日首页

Tauri 应用安全从开发到发布的威胁防御指南

作者 HelloReader
2026年3月7日 16:52

一、安全的核心原则:木桶效应

Tauri 官方文档开篇就点明了一个关键原则:

你的应用安全性,由生命周期中最薄弱的环节决定。

这意味着即便你的运行时防护做得再好,如果开发机器被攻陷、依赖链被污染,或者 CI/CD 系统不可信,最终产物的安全性依然无从保证。因此,我们需要以全链路视角审视安全问题,而不是只盯着某一个环节。


二、开发阶段威胁

2.1 上游依赖风险(Supply Chain Attack)

供应链攻击是近年来增长最快的攻击向量之一。NPM 和 crates.io 上的第三方包,任何一个都可能成为攻击者的入口。

防御建议:

  • 使用 npm auditcargo audit 定期扫描已知漏洞
  • 优先从 Git 仓库以哈希版本命名 Tag方式引入关键依赖,而非浮动版本号
  • 借助 cargo-vetcargo crev 等工具进行依赖审计
  • 使用 cargo supply-chain 可视化依赖图谱,了解你的代码究竟"站在谁的肩膀上"
# 检查 npm 依赖漏洞
npm audit

# 检查 Rust 依赖漏洞
cargo audit

# 查看供应链依赖
cargo supply-chain

2.2 开发服务器安全

Tauri 前端通常通过 Web 框架的开发服务器提供热重载能力。默认情况下,这个连接既不加密,也没有认证,这意味着:

  • 同一局域网内的攻击者可以监听前端资源
  • 攻击者甚至可以向你的开发设备推送恶意前端代码

防御建议:

  • 只在可信网络环境下进行开发
  • 在不可信网络(如咖啡馆、会议室 WiFi)中,必须为开发服务器配置双向 TLS(mTLS)

⚠️ 注意:Tauri 内置开发服务器目前尚不支持 mTLS,请勿在不可信网络中使用。

2.3 开发机器加固

开发机器本身也是攻击面。以下是一些通用的加固建议:

  • 日常编码等工作不要使用管理员账户
  • 开发机器上不要存放生产环境密钥
  • 使用硬件安全令牌(如 YubiKey)降低账户被盗风险
  • 最小化安装原则:只安装必要的应用程序
  • 保持系统和工具链持续更新

2.4 源代码版本控制安全

确保代码仓库的访问控制配置正确,防止未授权修改。同时,建议要求所有常规贡献者对提交进行签名(GPG Sign) ,避免恶意提交被伪装成合法贡献者的名义。


三、构建阶段威胁

现代工程团队普遍使用 CI/CD 系统自动化构建流程。然而,这些远程构建系统(通常由第三方托管)拥有对源码、密钥的完整访问权,且你无法从外部验证构建产物与本地代码是否完全一致。

防御建议:

  • 使用可信赖的 CI/CD 提供商,或自托管在受控硬件上
  • 对 CI 流程中使用的第三方 Action / 插件,必须锁定版本(使用 commit hash 而非浮动 tag)
  • 对发布产物进行代码签名,让用户能验证软件来源
  • 将加密密钥存储在硬件令牌中,即便构建系统被攻陷,也无法泄露签名私钥

3.1 可重现构建(Reproducible Builds)

理想情况下,可重现构建能让你验证 CI 产出的二进制与本地构建完全一致,从而检测构建时注入的后门。

然而现实是:Rust 默认并不保证完全可重现的构建(存在已知 bug),许多前端打包工具同样如此。

这意味着目前你仍需要充分信任你的构建系统,在此之上才能谈其他安全措施。这是当前整个生态的一个客观局限,值得持续关注。


四、发布与分发阶段威胁

Tauri 提供了相对完善的热更新机制。但如果你失去了对以下任一系统的控制:

  • Manifest 服务器(更新清单)
  • 构建服务器
  • 二进制文件托管服务

那么攻击者就可以向你的用户推送恶意更新,一切防护形同虚设。

防御建议:

  • 如果自建分发系统,务必咨询专业运维架构师,从设计层面保证安全性
  • 可以考虑使用 Tauri 官方合作伙伴 CrabNebula Cloud 提供的分发解决方案

五、运行时威胁

Tauri 的设计哲学是:假设 WebView 是不安全的

基于这一前提,Tauri 实现了多层防护机制:

  • 内容安全策略(CSP) :限制 WebView 可发起的通信类型,防止 XSS 等注入攻击
  • 能力系统(Capabilities) :细粒度控制 WebView 中的脚本对系统 API 的访问权限,不可信内容和脚本无法调用敏感接口

最佳实践:

// tauri.conf.json - 最小化权限原则示例
{
  "security": {
    "csp": "default-src 'self'; script-src 'self'"
  }
}

此外,建议参考 Tauri 官方的漏洞报告流程,为你自己的应用也建立一套易于使用且安全的漏洞披露机制


六、安全生命周期总览

阶段 主要威胁 关键措施
开发前 上游依赖污染 cargo audit、依赖审计、锁定版本
开发中 开发服务器暴露、机器被攻陷 可信网络、mTLS、最小权限账户
构建时 CI/CD 被篡改、后门注入 锁定 Action 版本、代码签名、硬件密钥
分发时 更新劫持 保护分发基础设施、使用可信分发平台
运行时 WebView 注入 CSP、Capabilities 最小权限

七、总结

Tauri 提供了出色的安全基础设施,但安全最终是开发者、框架和用户三方共同的责任

作为开发者,你需要:

  1. 将安全意识融入每一个开发决策
  2. 定期审计依赖,保持工具链更新
  3. 信任但验证你的构建系统
  4. 遵循最小权限原则配置运行时能力
  5. 建立漏洞响应机制

安全没有终点,只有持续的演进与防御。希望本文能帮助你在 Tauri 应用的整个生命周期中建立起更稳固的安全体系。


参考资料:Tauri 官方安全文档

昨天 — 2026年3月6日首页

Tauri 的 Capabilities 权限管理系统

作者 HelloReader
2026年3月6日 13:49

一、为什么需要 Capabilities?

Tauri 应用的前端运行在系统 WebView 中,而后端则是 Rust 编写的原生代码。前端通过 Tauri 提供的 API 与后端通信,从而访问文件系统、窗口管理、系统托盘等原生能力。

问题在于:如果前端代码被攻破(比如 XSS 攻击),攻击者就可能利用这些 API 对用户系统造成危害。Capabilities 系统正是为了应对这类场景而设计的——它让开发者可以精确控制每个窗口或 WebView 能使用哪些权限,将"最小权限原则"落到实处。

二、核心概念

Capabilities 本质上是一组声明式的权限配置,用来定义哪些窗口(window)或 WebView 被授予或拒绝了哪些权限。几个关键特性值得注意:

  • 一个 Capability 可以同时作用于多个窗口或 WebView。
  • 一个窗口也可以被多个 Capability 引用。当窗口属于多个 Capability 时,所有相关 Capability 的权限会合并生效——这意味着安全边界会扩大,配置时需要格外小心。

三、配置方式

Capability 文件以 JSON 或 TOML 格式存放在 src-tauri/capabilities 目录下。Tauri 提供了两种主要的配置方式。

方式一:独立文件 + 引用标识符

这是推荐的做法。在 capabilities 目录下定义独立的 Capability 文件,然后在 tauri.conf.json 中通过标识符引用它们。

首先,定义一个 Capability 文件:

// src-tauri/capabilities/default.json
{
  "$schema": "../gen/schemas/desktop-schema.json",
  "identifier": "main-capability",
  "description": "Capability for the main window",
  "windows": ["main"],
  "permissions": [
    "core:path:default",
    "core:event:default",
    "core:window:default",
    "core:app:default",
    "core:resources:default",
    "core:menu:default",
    "core:tray:default",
    "core:window:allow-set-title"
  ]
}

然后在配置文件中引用:

// src-tauri/tauri.conf.json
{
  "app": {
    "security": {
      "capabilities": ["my-capability", "main-capability"]
    }
  }
}

这种方式的好处是保持 tauri.conf.json 的简洁,同时让权限配置模块化、易于维护。

方式二:内联定义

对于简单场景,也可以直接在 tauri.conf.json 中内联定义 Capability,甚至将内联定义和引用混合使用:

{
  "app": {
    "security": {
      "capabilities": [
        {
          "identifier": "my-capability",
          "description": "My application capability used for all windows",
          "windows": ["*"],
          "permissions": ["fs:default", "allow-home-read-extended"]
        },
        "my-second-capability"
      ]
    }
  }
}

需要注意的是,capabilities 目录下的所有 Capability 文件默认自动启用。但一旦在 tauri.conf.json 中显式指定了 Capability,就只有被指定的那些会生效。

四、自定义命令的权限控制

默认情况下,通过 tauri::Builder::invoke_handler 注册的所有命令对所有窗口开放。如果你希望更精细地控制,可以在 build.rs 中使用 AppManifest::commands 来声明:

// src-tauri/build.rs
fn main() {
    tauri_build::try_build(
        tauri_build::Attributes::new()
            .app_manifest(
                tauri_build::AppManifest::new()
                    .commands(&["your_command"])
            ),
    )
    .unwrap();
}

五、平台特定配置

Capabilities 支持通过 platforms 字段限定作用的目标平台。可选值包括 linuxmacOSwindowsiOSandroid

一个面向桌面端的配置示例:

// src-tauri/capabilities/desktop.json
{
  "$schema": "../gen/schemas/desktop-schema.json",
  "identifier": "desktop-capability",
  "windows": ["main"],
  "platforms": ["linux", "macOS", "windows"],
  "permissions": ["global-shortcut:allow-register"]
}

以及面向移动端的配置:

// src-tauri/capabilities/mobile.json
{
  "$schema": "../gen/schemas/mobile-schema.json",
  "identifier": "mobile-capability",
  "windows": ["main"],
  "platforms": ["iOS", "android"],
  "permissions": [
    "nfc:allow-scan",
    "biometric:allow-authenticate",
    "barcode-scanner:allow-scan"
  ]
}

这种设计让你可以为不同平台启用不同的插件能力,同时避免在不支持某些硬件的平台上引入无意义的权限。

六、远程 API 访问

默认情况下,Tauri API 只对随应用打包的本地代码开放。但在某些场景下,你可能需要让远程加载的页面也能调用部分 Tauri 命令。这可以通过 remote 配置实现:

// src-tauri/capabilities/remote-tags.json
{
  "$schema": "../gen/schemas/remote-schema.json",
  "identifier": "remote-tag-capability",
  "windows": ["main"],
  "remote": {
    "urls": ["https://*.tauri.app"]
  },
  "platforms": ["iOS", "android"],
  "permissions": ["nfc:allow-scan", "barcode-scanner:allow-scan"]
}

这里有一个重要的安全提示:在 Linux 和 Android 上,Tauri 无法区分来自嵌入式 <iframe> 的请求和窗口本身的请求。因此在使用远程 API 访问功能时,务必仔细评估安全影响。

七、安全边界:能做什么,不能做什么

理解 Capabilities 系统的安全边界至关重要。

它能防护的场景包括:最小化前端被攻破后的影响、防止或减少本地系统接口和数据的意外暴露、防止从前端到后端/系统的权限提升。

它无法防护的场景包括:恶意或不安全的 Rust 后端代码、过于宽松的 scope 配置、命令实现中未正确检查 scope、来自 Rust 代码的故意绕过、系统 WebView 的零日漏洞、供应链攻击或开发者环境被入侵。

另外,安全边界依赖于窗口的 label(标签),而非 title(标题)。建议只对高权限窗口开放窗口创建功能。

八、Schema 文件与 IDE 支持

Tauri 通过 tauri-build 自动生成 JSON Schema 文件,其中包含了应用可用的所有权限定义。在 Capability 配置文件中设置 $schema 属性后,你的 IDE 就能提供自动补全,大幅提升开发体验:

{
  "$schema": "../gen/schemas/desktop-schema.json"
}

Schema 文件位于 gen/schemas 目录下,通常使用 desktop-schema.jsonmobile-schema.json,也可以为特定平台定义专属的 Schema。

九、项目结构概览

一个典型的 Tauri 应用目录结构如下:

tauri-app
├── index.html
├── package.json
├── src/
├── src-tauri/
│   ├── Cargo.toml
│   ├── capabilities/
│   │   └── <identifier>.json/toml
│   ├── src/
│   └── tauri.conf.json

capabilities 目录存放所有的权限配置文件,每个文件以其 identifier 命名,职责清晰,便于团队协作和代码审查。

十、最佳实践总结

在实际项目中使用 Capabilities 系统时,有几条经验值得参考。首先,遵循最小权限原则,只为每个窗口授予它实际需要的权限。其次,善用独立文件管理——将 Capability 定义为独立文件,通过标识符引用,保持配置清晰。第三,谨慎处理多 Capability 窗口,因为权限会合并,可能意外扩大安全边界。第四,利用平台特定配置,避免在不适用的平台上暴露无意义的权限。最后,对远程 API 访问保持警惕,仔细评估安全影响,尤其是在 Linux 和 Android 上。

Tauri 的 Capabilities 系统体现了"安全默认"的设计哲学——默认情况下,前端的能力是受限的,开发者需要显式地授予权限。这种设计虽然增加了一些配置工作,但换来的是更可控、更安全的应用架构。对于任何关注用户安全的桌面/移动应用项目来说,花时间理解和正确配置这套系统,都是值得的。

昨天以前首页

Tauri 命令作用域(Command Scopes)精细化控制你的应用权限

作者 HelloReader
2026年3月5日 13:38

一、为什么需要命令作用域?

试想这样一个场景:你的 Tauri 应用需要读取用户 $APPLOCALDATA 目录下的某些配置文件,于是你开放了文件读取命令。但问题来了——这个目录下同时存放着 WebView 的运行时数据(如 Cookies、IndexedDB、Session 信息),一旦被恶意前端代码读取,将造成严重的隐私泄露。

如果权限粒度只能精确到"命令级别",你只能在"全部放开"和"全部禁止"之间二选一。命令作用域(Command Scopes) 正是为解决这一问题而生——它允许你在开放某个命令的同时,精确约束这个命令能操作的资源边界。

二、作用域的核心概念

2.1 allow 与 deny

作用域分为两类,规则简洁而明确:

类型 含义
allow 显式允许命令操作的资源范围
deny 显式拒绝命令操作的资源范围

核心规则:deny 的优先级永远高于 allow 无论 allow 范围有多宽泛,只要资源命中了 deny 规则,访问就会被拒绝,没有任何例外。

2.2 作用域的类型系统

作用域的值类型必须是可被 serde 序列化的 Rust 类型,具体类型由各插件或应用自行定义。不同插件使用不同类型来描述"资源"的概念:

  • fs 插件:使用 glob 路径字符串(如 $HOME/**)描述文件系统路径
  • http 插件:使用 URL 字符串描述允许访问的网络地址

作用域由命令实现层接收并强制执行。这意味着命令开发者必须自行实现作用域校验逻辑,框架本身不会自动过滤。

⚠️ 安全警告:命令开发者有责任确保作用域校验逻辑不存在绕过漏洞(例如路径穿越攻击)。所有校验代码都应经过安全审计。

三、实战:fs 插件的作用域配置

下面以 Tauri 官方 fs 插件为例,完整演示作用域的配置方式。在这个插件中,作用域类型统一为 glob 路径字符串

3.1 定义允许范围:递归访问 APPLOCALDATA

# plugins/fs/permissions/autogenerated/base-directories/applocaldata.toml

[[permission]]
identifier = "scope-applocaldata-recursive"
description = '''
This scope recursive access to the complete $APPLOCALDATA folder,
including sub directories and files.
'''

[[permission.scope.allow]]
path = "$APPLOCALDATA/**"

这里有两个细节值得注意:

  • $APPLOCALDATA 是 Tauri 内置的路径变量,会在运行时被解析为平台对应的目录(Windows 下为 %LOCALAPPDATA%,Linux 下为 ~/.local/share
  • /** 是 glob 通配符,表示递归匹配该目录下所有子目录和文件。若只写 /*,则只匹配一层,不会深入子目录

3.2 定义拒绝范围:保护 WebView 敏感数据

WebView 引擎会在 $APPLOCALDATA 下存储用户会话、缓存等敏感数据,不同平台的存储路径有所差异,因此需要分平台配置拒绝规则:

# plugins/fs/permissions/deny-webview-data.toml

# ---- Linux 平台 ----
[[permission]]
identifier = "deny-webview-data-linux"
description = '''
This denies read access to the $APPLOCALDATA folder on linux as the webview
data and configuration values are stored here.
Allowing access can lead to sensitive information disclosure.
'''
platforms = ["linux"]

[[scope.deny]]
path = "$APPLOCALDATA/**"

# ---- Windows 平台 ----
[[permission]]
identifier = "deny-webview-data-windows"
description = '''
This denies read access to the $APPLOCALDATA/EBWebView folder on windows
as the webview data and configuration values are stored here.
'''
platforms = ["windows"]

[[scope.deny]]
path = "$APPLOCALDATA/EBWebView/**"

platforms 字段是这里的关键——同一个 .toml 文件中可以定义多条权限,每条权限可以通过 platforms 声明其生效的操作系统,做到跨平台差异化配置,无需为每个平台单独维护文件。

两条规则的差异体现了 Linux 和 Windows WebView 实现的不同:

  • Linux:整个 $APPLOCALDATA 都用于存储 WebView 数据,因此整体拒绝
  • Windows:只有 EBWebView 子目录存储 Edge WebView2 的数据,精准拒绝即可

四、分层组合:用权限集构建作用域体系

单个作用域权限如同零件,真正的工程实践是将它们有机组合。Tauri 推荐通过权限集(Permission Set) 进行分层组合,每一层都应有清晰的语义。

第一层:合并拒绝规则,建立安全基线

# plugins/fs/permissions/deny-default.toml

[[set]]
identifier = "deny-default"
description = '''
This denies access to dangerous Tauri relevant files and
folders by default.
'''
permissions = [
    "deny-webview-data-linux",
    "deny-webview-data-windows"
]

deny-default 将两个平台的拒绝规则合并,形成一个平台无关的安全基线。无论应用运行在哪个系统,引用这一个标识符就能自动应用正确的拒绝规则。

第二层:allow + deny 合并,形成合理的访问策略

[[set]]
identifier = "scope-applocaldata-reasonable"
description = '''
This scope set allows access to the APPLOCALDATA folder and subfolders
except for linux, while it denies access to dangerous Tauri relevant
files and folders by default on windows.
'''
permissions = [
    "scope-applocaldata-recursive",  # 允许递归访问
    "deny-default"                   # 但屏蔽危险路径
]

scope-applocaldata-reasonable 的命名本身就是一种设计表达——"合理的(reasonable)APPLOCALDATA 访问策略",在放开访问的同时内置了安全保障,引用者无需关心底层细节。

第三层:作用域 + 命令权限合并,形成完整功能单元

[[set]]
identifier = "read-files-applocaldata"
description = '''
This set allows file read access to the APPLOCALDATA folder and
subfolders except for linux, while it denies access to dangerous
Tauri relevant files and folders by default on windows.
'''
permissions = [
    "scope-applocaldata-reasonable",  # 作用域策略
    "allow-read-file"                 # 开放读取命令
]

read-files-applocaldata 是最终对外暴露的功能级权限集,语义完整、开箱即用:调用者只需引用这一个标识符,就能获得"在 APPLOCALDATA 下安全读取文件"的完整能力。

五、整体设计思路图解

在这里插入图片描述

这种分层设计的好处在于:

  • 关注点分离:allow 规则和 deny 规则各自独立维护
  • 复用性强deny-default 可被所有涉及 APPLOCALDATA 的权限集复用
  • 语义清晰:每一层的命名都能准确表达其意图
  • 易于审计:安全相关的拒绝规则集中管理,不会散落在各处

六、作用域的两种应用场景

配置好的作用域权限集,可以用于两种不同的作用范围:

场景一:全局作用域

将作用域权限集应用于插件的全局 scope,该插件的所有命令都会受到约束。适用于对整个插件统一设定资源访问边界的场景。

场景二:命令级作用域

将作用域权限与特定命令权限组合(如上文 read-files-applocaldata 的做法),仅对该命令生效。适用于不同命令需要不同资源访问策略的场景。

七、实践建议

设计作用域时:

  • glob 路径要严谨/* 只匹配当前层,/** 才会递归,根据实际需要选择,避免无意间开放过宽的权限
  • 始终配套 deny:任何开放系统目录访问的 allow 规则,都应搭配针对敏感子路径的 deny 规则
  • 平台差异显式化:用 platforms 字段将平台逻辑内聚在权限文件中,不要依赖外部条件判断

实现命令时:

  • 作用域校验不能省:框架传入 scope 数据,但校验必须由命令实现层主动执行
  • 防止路径穿越:对用户传入的路径参数进行规范化(canonicalize)后再与 scope 比对
  • 安全审计要落实:校验逻辑上线前应经过独立的代码审查,尤其是涉及文件系统和网络的命令

总结

Tauri 的命令作用域机制提供了远超传统"开/关"粒度的访问控制能力。其核心设计哲学可以归纳为三点:

  1. 精确授权allow 明确放行,deny 兜底屏蔽,两者组合实现精准的资源边界
  2. 分层复用:从原子作用域到安全基线,再到功能权限集,每一层都可独立复用
  3. 平台感知platforms 字段让同一套配置体系优雅地处理跨平台差异

对于构建安全 Tauri 应用的开发者来说,命令作用域是不可忽视的核心机制。合理设计作用域体系,不仅能提升应用安全性,也能让权限配置本身成为一份清晰的"资源访问说明书"。

❌
❌