4.5 权限系统与安全边界

原文链接: Capabilities 与 Permissions

4.5 权限系统与安全边界

这一节可能是 Tauri v2 与很多“Web 套壳”框架最大的区别:前端不是默认什么都能用。

三个核心名词

先区分三个容易混淆的词:

词类比定义
Command一个“动作”Rust 或插件提供的可调用函数
Permission一张“通行证”是否允许某个命令、某些参数范围
Capability一份“门禁名单”把一组 Permission 绑定到某个窗口/WebView

把它们组合起来:哪个窗口,能用哪些命令,参数允许到什么程度。

默认文件的例子

新项目 src-tauri/capabilities/default.json:

1
2
3
4
5
6
7
{
  "$schema": "../gen/schemas/desktop-schema.json",
  "identifier": "default",
  "description": "主窗口的默认能力",
  "windows": ["main"],
  "permissions": ["core:default"]
}

含义:

  • 这份能力名叫 default;
  • 它作用在 main 窗口;
  • 它放行了 core:default(核心默认权限集)。

src-tauri/capabilities/ 目录中的能力文件默认都会参与构建。如果你在 tauri.conf.json 的 app.security.capabilities 中显式列出 identifier,则只会启用你列出的那些能力,适合需要精细控制多个配置的场景。

谁默认可用、谁默认不可用?

规则可以记成一句话:

你自己注册的自定义 Rust 命令默认全部窗口可用;核心 API 与插件命令默认关闭,必须用 Permission 显式放行。

更严谨地说:如果一个窗口/WebView 没有匹配任何 capability,它连 IPC 层都无法使用。在“至少匹配一个 capability”的前提下,你通过 invoke_handler 注册的自定义命令默认可用;核心 API 与插件命令则仍需要显式权限。

因此刚建好项目时,你也许能调用 invoke('my_command'),但直接调用 window.setTitle 或文件插件却会被拒绝,直到你在 capabilities 里加上对应权限。

给窗口放行某 API

假设希望 main 窗口能设置窗口标题:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
{
  "$schema": "../gen/schemas/desktop-schema.json",
  "identifier": "main-capability",
  "description": "主窗口的能力",
  "windows": ["main"],
  "permissions": [
    "core:default",
    "core:window:allow-set-title"
  ]
}

权限标识符常形如 插件名:命令组:动作:

  • core:event:default:核心事件默认权限;
  • core:window:allow-set-title:允许设置标题;
  • fs:allow-read-text-file:文件插件允许读文本文件;
  • shell:allow-execute:允许执行外部程序。

平台差异:一份能力可以只作用于某些平台

移动端与桌面端能力差异很大,可以加 platforms 数组:

1
2
3
4
5
6
{
  "identifier": "mobile-only",
  "windows": ["main"],
  "platforms": ["iOS", "android"],
  "permissions": ["nfc:allow-scan", "biometric:allow-authenticate"]
}

支持的值:linux、macOS、windows、iOS、android。

Scope:给命令再套一层参数限制

仅“允许调用命令”还不够时,Tauri 还提供 Scope(作用域)。例如文件插件允许 read,但你可以进一步限制只能读 $HOME/notes/**:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
{
  "identifier": "fs-read-notes",
  "windows": ["main"],
  "permissions": [
    {
      "identifier": "fs:allow-read-text-file",
      "allow": [{ "path": "$HOME/notes/**/*" }]
    }
  ]
}

Scope 分 allow 与 deny,deny 优先于 allow。用它可以做到“能读文件,但读不了密钥目录”。

安全边界能做什么、不能做什么

权限系统能:

  • 缩小前端被攻破后的“爆炸半径”;
  • 防止不小心暴露系统接口与数据;
  • 阻止前端越权调用插件/核心命令。

它不能防:

  • Rust 代码本身的漏洞;
  • 你写得太宽松的 scope;
  • 命令实现里的错误检查;
  • 系统 WebView 的 0-day;
  • 供应链攻击与开发者电脑被攻破。

因此最佳实践是:

  1. Rust 端永远校验参数;
  2. 前端只获取最小必要权限;
  3. 不在前端存密钥;
  4. 定时更新依赖。

Brownfield 与 Isolation 两种安全模式

官方还定义了两种 IPC 安全模式:

  • Brownfield(默认):前端与 Tauri Core 之间直接走 IPC,最接近浏览器开发体验。
  • Isolation:在两者之间插入一个独立、加密的沙箱脚本,先过滤/校验再发给 Core,适合对供应链安全要求极高的应用。

大多数项目先用默认 Brownfield 即可;项目进入严肃安全阶段后再了解 Isolation。

建议阅读顺序

概念学完后,可以进入 第 5 章:跨平台开发与工程布局。