在桌面应用中,文件系统是最敏感的资源之一。一旦被恶意利用,攻击者可能读取用户私密文件、覆盖系统配置,甚至执行任意代码。Tauri 从设计层面提供了两层防护:权限最小化和路径遍历防护,让开发者能安全地暴露文件操作能力给前端。
1. 权限最小化:只给应用真正需要的目录
在 tauri.conf.json 中,你可以通过 fs 配置项明确声明应用允许读写的目录范围(也称为 fs scope)。例如,一个笔记应用只需要访问用户的文档目录和应用自己的数据目录:
{
"tauri": {
"allowlist": {
"fs": {
"scope": {
"allow": [
"$DOCUMENT/**",
"$APPDATA/myapp/**"
],
"deny": [
"$DOCUMENT/secret/**"
]
}
}
}
}
}
关键点:
allow定义了白名单路径,支持通配符**递归匹配,以及预设变量(如$DOCUMENT、$APPDATA、$HOME)。deny可以在白名单内进一步排除敏感子目录。- 未明确声明的路径操作(如尝试读取
C:\Windows\System32\config\SAM)会被 Rust 后端直接拒绝,前端收到的只是一个权限拒绝错误,不会触及底层文件系统。
这种“先声明再使用”的模式,确保即使前端代码中存在任意文件读取漏洞,攻击者也无法越界访问。
2. 路径遍历防护:堵死 ../ 绕过
权限声明只是第一道防线。恶意输入仍可能借助 ../ 或绝对路径符号链接来尝试绕过白名单。Tauri 的 Rust 后端在每次文件操作前都会执行严格的路径规范化与边界检查,核心逻辑如下:
- 解析为绝对路径:将请求的相对路径(来自前端)与白名单允许的基路径结合,并通过
std::fs::canonicalize解析出无..、无符号链接的最终真实路径。 - 验证是否在白名单内:检查解析后的真实路径,是否以某个允许的目录为前缀。如果不是,立即拒绝。
- 处理符号链接:即使攻击者试图在允许的目录内创建一个指向系统文件的符号链接,
canonicalize也会解析到目标文件,从而暴露它不在允许范围内。
示例(Rust 端自定义检查)
如果你需要更精细的控制,可以在自定义命令中手动调用 tauri::api::path::resolve_path 并获得安全上下文,但通常直接使用 Tauri 提供的 fs API 就足够了,因为它们已经内置了上述校验。
3. 最佳实践与常见陷阱
- 永远不要在
allow中使用$HOME/或$ROOT/
这等于把整个用户目录或系统根目录暴露给前端,形同虚设。始终限定到具体应用数据子目录。
- 使用变量而非硬编码路径
$DOCUMENT、$DOWNLOAD 等标准化变量能跨平台自动映射到正确位置,避免写死 Windows 的 C:\Users\... 或 macOS 的 /Users/...。
- 区分读写权限
在 scope 中,你可以进一步限制某条路径只允许读取(read: true)或写入(write: true),避免不必要的写入权限。
- 警惕前端拼接路径
如果允许用户输入文件名,前端要简单过滤 .. 和 /,但不要依赖前端校验,Rust 后端的边界检查才是最终防线。
- 测试边界情况
用 ../../etc/passwd、./..\..\Windows\System32\config\SAM 等典型攻击向量测试你的应用,确保返回的是“Permission denied”而不是文件内容。
4. 一个真实的安全示例
假设你的应用需要让用户选择并读取 $DOCUMENT 下的一个文本文件,你可以这样写前端调用:
import { readTextFile } from '@tauri-apps/api/fs';
// 用户选择的文件名(可能被篡改)
const userInput = "../../secret/data.txt";
try {
const content = await readTextFile(`$DOCUMENT/${userInput}`);
console.log(content);
} catch (error) {
console.error("读取被拒绝:", error); // 路径遍历会被拦截
}
在 Rust 后端,Tauri 会检测到解析后的路径超出了 $DOCUMENT 范围,抛出错误,不会执行实际读取。
总结:
文件系统安全不能靠“信任前端”来保证。Tauri 通过 scope 权限声明实现最小化授权,并在底层强制路径规范化与白名单比对,彻底阻断路径遍历攻击。你只需要在配置中精确声明应用所需目录,剩下的安全检查就交给了框架。