一、为什么需要 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 字段限定作用的目标平台。可选值包括 linux、macOS、windows、iOS 和 android。
一个面向桌面端的配置示例:
// 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.json 或 mobile-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 上。
十一、第二道防线:内容安全策略(CSP)
如果说 Capabilities 是从"原生 API 暴露面"维度构建的安全防线,那么 **CSP(Content Security Policy)**则是从 Web 前端层面加固应用安全的另一道屏障。
CSP 解决什么问题?
Web 应用面临的经典攻击之一是跨站脚本攻击(XSS) ——攻击者设法在页面中注入并执行恶意脚本。在传统浏览器中,CSP 已经是广泛使用的防御手段;Tauri 将这一机制引入了桌面应用场景。
Tauri 对 HTML 页面施加了 CSP 限制。具体来说,本地脚本会被哈希处理,而样式和外部脚本则通过**加密 nonce(一次性随机数)**来引用。这意味着即使攻击者成功注入了一段 <script> 标签,由于它没有正确的哈希值或 nonce,浏览器(WebView)也会拒绝执行它。
如何启用 CSP
CSP 保护不是默认开启的——你需要在 tauri.conf.json 中显式配置。在编译时,Tauri 会自动将 nonce 和哈希值附加到打包代码和资源对应的 CSP 属性中,因此你只需关注应用特有的配置部分。
以下是一个来自 Tauri 官方示例的 CSP 配置:
// tauri.conf.json
{
"csp": {
"default-src": "'self' customprotocol: asset:",
"connect-src": "ipc: http://ipc.localhost",
"font-src": ["https://fonts.gstatic.com"],
"img-src": "'self' asset: http://asset.localhost blob: data:",
"style-src": "'unsafe-inline' 'self' https://fonts.googleapis.com"
}
}
每个指令的含义如下:default-src 定义了资源加载的默认策略,这里只允许同源内容以及 Tauri 的自定义协议;connect-src 限制了网络请求的目标,仅允许 Tauri 的 IPC 通道;font-src 和 style-src 分别控制字体和样式的来源,这里允许了 Google Fonts;img-src 定义了图片加载策略,允许本地资源、blob URL 和 data URI。
请注意:这只是一个示例配置,每个项目都需要根据自身需求量身定制。核心原则是尽可能严格,只允许 WebView 从你信任(最好是你自己拥有)的来源加载资源。
特殊场景:WebAssembly
如果你使用 Rust 开发前端(比如 Leptos、Yew 等框架),或者前端中涉及 WebAssembly,需要在 script-src 中加入 'wasm-unsafe-eval',否则 Wasm 模块将无法正常执行。
关于远程内容的警告
Tauri 官方明确建议:避免加载远程内容,尤其是通过 CDN 引入的脚本。每一个来自不受你控制的来源的文件,都可能引入新的、隐蔽的攻击向量。即使是看似无害的第三方库,如果其 CDN 被入侵,后果也可能波及你的应用。
十二、Capabilities + CSP:纵深防御
回顾全文,Tauri 的安全设计体现了经典的**纵深防御(Defense in Depth)**思想:
Capabilities 系统工作在"前端与原生 API 之间",控制的是"前端能调用哪些系统能力"。而 CSP 工作在"Web 内容加载层",控制的是"哪些代码和资源可以在 WebView 中执行"。两者从不同层面构建安全边界,即使其中一层被突破,另一层仍能提供保护。
Tauri 的安全架构体现了"安全默认"的设计哲学——默认情况下,前端的能力是受限的,开发者需要显式地授予权限和配置策略。这种设计虽然增加了一些配置工作,但换来的是更可控、更安全的应用架构。对于任何关注用户安全的桌面/移动应用项目来说,花时间理解和正确配置这两套系统,都是值得的投入。