Tauri 权限系统和安全机制是如何工作的?
Tauri 的安全模型可以用一句话概括:前端默认不可信,系统能力默认不给,所有越过 WebView 边界的操作都要显式授权。它不像传统网页只能访问浏览器沙箱,也不像一些桌面框架默认把 Node 能力暴露给页面。Tauri 把系统调用放在 Rust 侧,通过权限、命令、CSP、作用域和插件能力组合控制风险。做得好,应用可以很轻;做得粗糙,前端 XSS 也可能变成读文件、开进程这种桌面级事故。
权限系统的核心逻辑
Tauri 2 更推荐用 capability 文件描述窗口能使用哪些能力。比如只允许主窗口读取用户选择目录下的文件,而不是开放整个文件系统:
json{ "$schema": "../gen/schemas/desktop-schema.json", "identifier": "main-capability", "windows": ["main"], "permissions": [ "core:default", "dialog:default", { "identifier": "fs:allow-read-text-file", "allow": [{ "path": "$HOME/Documents/*.txt" }] } ] }
旧版项目常见的是 allowlist,例如只打开 readFile、writeFile,并限制 scope。无论版本如何,原则都一样:不要为了省事写 all: true,更不要把 $HOME/** 当作默认配置。
CSP 负责限制前端能加载什么
CSP 不是摆设,它能降低 XSS 后继续扩大攻击面的概率。一个保守配置可以从下面开始:
json{ "app": { "security": { "csp": "default-src 'self'; img-src 'self' asset: https:; style-src 'self' 'unsafe-inline'; script-src 'self'" } } }
这里的取舍很现实:前端框架和样式库有时需要 inline style,完全禁掉会影响功能;但脚本最好保持 script-src 'self',不要随手加 unsafe-eval。如果确实要加载远程图片或接口,也应该精确到域名,而不是放开所有来源。
IPC 边界同样需要校验
很多人以为“权限没开就安全”,但自定义 #[tauri::command] 也可能绕开插件权限。比如前端传一个路径给 Rust,Rust 直接读文件,那权限系统并不会自动替你判断业务边界。Rust 端要自己做白名单、路径规范化和参数校验:
rustuse std::path::{Path, PathBuf}; #[tauri::command] fn read_note(base: String, name: String) -> Result<String, String> { if name.contains("..") || name.contains('/') { return Err("invalid file name".into()); } let path = Path::new(&base).join(name); std::fs::read_to_string(path).map_err(|e| e.to_string()) }
这段代码仍然只是示意,生产中还要校验 base 是否来自可信配置,而不是任由前端传入。
插件和 Shell 是高风险区域
Shell、文件系统、剪贴板、自动更新、深链协议都很有用,但权限边界也更敏感。尤其是 Shell,不要允许前端拼接任意命令:
json{ "permissions": [ { "identifier": "shell:allow-open", "allow": [{ "url": "https://example.com/*" }] } ] }
如果业务需要执行系统命令,优先把命令封装在 Rust 侧,并限制参数枚举。前端只传业务意图,不传完整命令行。
生产项目里的权限收口流程
比较稳的做法是先按功能列权限,而不是先写配置。比如“导入文件”需要 dialog 和只读文件权限,“导出报告”需要保存路径写入权限,“打开官网”只需要受限 URL 的 open 权限。列完后再把权限拆到 capability,确保不同窗口只拿到自己需要的能力。发布前可以做一次反向检查:删除某个权限后,是否只有对应功能失效,而不是整个应用都依赖它。
自动更新和签名也属于安全链路。更新包如果没有签名校验,攻击者一旦控制下载链路,权限配置再严格也没有意义。桌面应用还要小心日志,很多团队会把完整路径、用户输入甚至 token 打进日志里,最后通过“导出诊断信息”泄露出去。权限系统管不了这些业务习惯,只能靠代码审查和脱敏规则兜底。
依赖和插件要单独审
Tauri 插件提升开发效率,但插件本质上也是系统能力入口。引入插件前要看它暴露了哪些命令、默认权限是否过宽、是否还在维护。前端依赖同样不能放松,XSS、原型污染、富文本渲染漏洞都可能让攻击者触发已授权能力。一个实用边界是:凡是能碰文件、进程、网络代理、凭据和自动更新的依赖,都按高风险依赖处理,而不是普通 UI 包。
追问
Tauri 默认安全就代表不用管 XSS 吗?
不是。Tauri 限制的是系统能力暴露方式,XSS 仍然可能读取页面状态、调用已授权 API,甚至借助你的自定义命令做危险操作。边界在于:权限越小,XSS 之后能做的事越少。踩坑点是很多团队只审 Rust 代码,不审前端富文本、Markdown 渲染和远程内容注入,这些才是桌面应用里最常见的入口。
capabilities 和旧版 allowlist 有什么取舍?
capabilities 更细,可以按窗口、平台和插件声明能力,适合 Tauri 2 的长期维护。旧版 allowlist 更直观,但粒度和组织方式不如 capability 清晰。迁移时不要机械替换字段名,要重新梳理每个窗口到底需要什么能力。否则配置看起来升级了,实际权限仍然过大。
文件系统 scope 应该怎么设?
优先使用应用目录、用户选择的目录或明确的业务目录,不要开放整个家目录。比如笔记应用可以限定 $APPDATA 和用户导入目录,导出时再通过保存对话框拿到目标路径。取舍是用户第一次操作可能多一步授权,但换来的是事故半径更小。常见坑是开发阶段为了方便放开权限,发布前忘了收紧。
远程页面能不能直接放进 Tauri WebView?
可以加载,但安全风险明显更高,因为你无法完全控制远程脚本。除非业务就是浏览器壳,否则不建议让远程页面拥有 Tauri API 能力。更稳的做法是本地前端加载远端数据,把系统能力留给可信页面。边界是远程内容可以显示,但不要让它直接调用高权限命令。
自定义 Rust 命令怎么做安全审计?
先列出命令能碰到的系统资源:文件、网络、进程、凭据、窗口或剪贴板。再看参数是否来自前端、是否做了类型和范围校验、错误信息是否泄露敏感路径。最后检查命令是否能被任意窗口调用,必要时按窗口隔离能力。很多漏洞不是 Rust 不安全,而是业务命令太相信前端传来的参数。