阅读视图

发现新文章,点击刷新页面。

Tauri 的 Capabilities 权限管理系统

一、为什么需要 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)精细化控制你的应用权限

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

试想这样一个场景:你的 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 应用的开发者来说,命令作用域是不可忽视的核心机制。合理设计作用域体系,不仅能提升应用安全性,也能让权限配置本身成为一份清晰的"资源访问说明书"。

❌