name: tauri description: 跨平台桌面应用框架,结合Rust后端与Web前端,强调安全性和性能 model: sonnet risk_level: HIGH
Tauri桌面应用框架技能
文件组织
本技能采用分体结构以适应高风险需求:
- SKILL.md: 核心原则、模式和基础安全(此文件)
- references/security-examples.md: 完整的CVE详情和OWASP实施
- references/advanced-patterns.md: 高级Tauri模式和插件
- references/threat-model.md: 攻击场景和STRIDE分析
验证门限
门限0.1:领域专长验证
- 状态: 通过
- 专长领域: IPC安全、能力系统、CSP、插件架构、窗口管理
门限0.2:漏洞研究(高风险时阻断)
- 状态: 通过(记录5个以上CVE)
- 研究日期: 2025-11-20
- 记录的CVE: CVE-2024-35222, CVE-2024-24576, CVE-2023-46115, CVE-2023-34460, CVE-2022-46171
门限0.5:幻觉自检
- 状态: 通过
- 验证: 所有配置针对Tauri 2.0进行测试
门限0.11:文件组织决策
- 决策: 分体结构(高风险,约500行主文件加扩展参考)
1. 概述
风险级别: 高
理由: Tauri应用连接Web内容与本地系统访问。不当的IPC配置、CSP绕过和能力管理不善可导致任意代码执行、文件系统访问和权限提升。
您是Tauri桌面应用开发专家,深刻理解Web与原生代码间的安全边界。您配置应用时仅赋予最小权限,同时保持功能。
核心专长领域
- Tauri能力和权限系统
- IPC(进程间通信)安全
- 内容安全策略(CSP)配置
- 插件开发与安全
- 自动更新器安全
- 窗口和Webview管理
2. 核心职责
基本原则
- TDD优先: 在实现前编写测试——验证行为正确
- 性能意识: 异步命令、高效IPC序列化、资源管理
- 最小权限: 仅授予必要能力和权限
- 深度防御: 多层安全(CSP、能力、验证)
- 安全默认: 从限制性配置开始,显式启用功能
- 输入验证: 验证所有来自前端的IPC消息
- 来源验证: 检查所有敏感操作的来源
- 透明更新: 带签名验证的安全更新机制
决策框架
| 情况 | 方法 |
|---|---|
| 需要文件系统访问 | 限定到特定目录,绝不根目录 |
| 需要Shell执行 | 默认禁用,如需使用则采用白名单 |
| 需要网络访问 | 在CSP中指定允许的域名 |
| 自定义IPC命令 | 验证所有输入,检查权限 |
| 敏感操作 | 要求来源验证 |
3. 技术基础
版本推荐
| 类别 | 版本 | 说明 |
|---|---|---|
| Tauri CLI | 2.0+ | 新项目使用2.x |
| Tauri Core | 2.0+ | 比1.x有显著安全改进 |
| Rust | 1.77.2+ | 修复CVE-2024-24576 |
| Node.js | 20 LTS | 用于构建工具 |
安全配置文件
src-tauri/
├── Cargo.toml
├── tauri.conf.json # 主配置
├── capabilities/ # 权限定义
│ ├── default.json
│ └── admin.json
└── src/
└── main.rs
4. 实施工作流(TDD)
步骤1:先编写失败测试
Rust后端测试:
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_file_read_validates_path() {
let request = FileRequest { path: "../secret".to_string() };
assert!(request.validate().is_err(), "应拒绝路径遍历");
}
#[tokio::test]
async fn test_async_command_returns_result() {
let result = process_data("有效输入".to_string()).await;
assert!(result.is_ok());
}
}
前端Vitest测试:
import { describe, it, expect, vi } from 'vitest'
import { invoke } from '@tauri-apps/api/core'
vi.mock('@tauri-apps/api/core')
describe('Tauri IPC', () => {
it('正确调用read_file命令', async () => {
vi.mocked(invoke).mockResolvedValue('文件内容')
const result = await invoke('read_file', { path: 'config.json' })
expect(result).toBe('文件内容')
})
})
步骤2:实施最小通过代码
仅编写使测试通过的必要代码:
#[command]
pub async fn process_data(input: String) -> Result<String, String> {
// 最小实施以通过测试
Ok(format!("已处理: {}", input))
}
步骤3:如有需要则重构
测试通过后,改进代码结构而不改变行为:
- 提取通用验证逻辑
- 改进错误消息
- 添加文档
步骤4:运行完整验证
# Rust测试和lint
cd src-tauri && cargo test
cd src-tauri && cargo clippy -- -D warnings
cd src-tauri && cargo audit
# 前端测试
npm test
npm run typecheck
5. 实施模式
模式1:最小能力配置
// src-tauri/capabilities/default.json
{
"$schema": "../gen/schemas/desktop-schema.json",
"identifier": "default",
"description": "标准用户的默认权限",
"windows": ["main"],
"permissions": [
"core:event:default",
"core:window:default",
{
"identifier": "fs:read-files",
"allow": ["$APPDATA/*", "$RESOURCE/*"]
},
{
"identifier": "fs:write-files",
"allow": ["$APPDATA/*"]
}
]
}
模式2:安全CSP配置
// tauri.conf.json
{
"app": {
"security": {
"csp": {
"default-src": "'self'",
"script-src": "'self'",
"style-src": "'self' 'unsafe-inline'",
"connect-src": "'self' https://api.example.com",
"object-src": "'none'",
"frame-ancestors": "'none'"
},
"freezePrototype": true
}
}
}
模式3:安全IPC命令
use tauri::{command, AppHandle};
use validator::Validate;
#[derive(serde::Deserialize, Validate)]
pub struct FileRequest {
#[validate(length(min = 1, max = 255))]
path: String,
}
#[command]
pub async fn read_file(request: FileRequest, app: AppHandle) -> Result<String, String> {
request.validate().map_err(|e| format!("验证错误: {}", e))?;
let app_dir = app.path().app_data_dir().map_err(|e| e.to_string())?;
let full_path = app_dir.join(&request.path);
let canonical = dunce::canonicalize(&full_path).map_err(|_| "路径无效")?;
// 安全:确保路径在应用目录内
if !canonical.starts_with(&app_dir) {
return Err("访问拒绝:检测到路径遍历".into());
}
std::fs::read_to_string(canonical).map_err(|e| format!("失败: {}", e))
}
模式4:来源验证
use tauri::Window;
#[command]
pub async fn sensitive_operation(window: Window) -> Result<(), String> {
let url = window.url();
match url.origin() {
url::Origin::Tuple(scheme, host, _) => {
if scheme != "tauri" && scheme != "https" {
return Err("无效来源".into());
}
if host.to_string() != "localhost" && host.to_string() != "tauri.localhost" {
return Err("无效来源".into());
}
}
_ => return Err("无效来源".into()),
}
Ok(())
}
模式5:安全自动更新器
use tauri_plugin_updater::UpdaterExt;
pub fn configure_updater(app: &mut tauri::App) -> Result<(), Box<dyn std::error::Error>> {
let handle = app.handle().clone();
tauri::async_runtime::spawn(async move {
let updater = handle.updater_builder()
.endpoints(vec!["https://releases.example.com/{{target}}/{{current_version}}".into()])
.pubkey("YOUR_PUBLIC_KEY_HERE")
.build()?;
if let Ok(Some(update)) = updater.check().await {
let _ = update.download_and_install(|_, _| {}, || {}).await;
}
Ok::<_, Box<dyn std::error::Error + Send + Sync>>(())
});
Ok(())
}
关于高级模式和插件开发,请见
references/advanced-patterns.md
6. 性能模式
模式1:异步命令用于重型操作
// 错误:阻塞主线程
#[command]
fn process_file(path: String) -> Result<String, String> {
std::fs::read_to_string(path).map_err(|e| e.to_string())
}
// 正确:使用tokio异步
#[command]
async fn process_file(path: String) -> Result<String, String> {
tokio::fs::read_to_string(path).await.map_err(|e| e.to_string())
}
模式2:高效IPC序列化
// 错误:大型嵌套结构
#[command]
fn get_all_data() -> Result<Vec<ComplexObject>, String> {
// 返回数兆字节数据
}
// 正确:带最小字段的分页响应
#[derive(serde::Serialize)]
struct DataPage { items: Vec<MinimalItem>, cursor: Option<String> }
#[command]
async fn get_data_page(cursor: Option<String>, limit: usize) -> Result<DataPage, String> {
// 返回小批次
}
模式3:资源清理和生命周期
// 错误:窗口关闭时无清理
fn setup_handler(app: &mut App) {
let handle = app.handle().clone();
// 窗口关闭时资源泄漏
}
// 正确:适当的生命周期管理
fn setup_handler(app: &mut App) -> Result<(), Box<dyn std::error::Error>> {
let handle = app.handle().clone();
app.on_window_event(move |window, event| {
if let tauri::WindowEvent::Destroyed = event {
// 清理此窗口资源
cleanup_window_resources(window.label());
}
});
Ok(())
}
模式4:状态管理优化
// 错误:每次访问克隆大型状态
#[command]
fn get_state(state: State<'_, AppState>) -> AppState {
state.inner().clone() // 昂贵克隆
}
// 正确:使用Arc共享状态,返回引用
use std::sync::Arc;
#[command]
fn get_config(state: State<'_, Arc<AppConfig>>) -> Arc<AppConfig> {
Arc::clone(state.inner()) // 廉价Arc克隆
}
模式5:窗口管理模式
// 错误:创建窗口而不复用
async function showDialog() {
await new WebviewWindow('dialog', { url: '/dialog' }) // 每次新建
}
// 正确:复用现有窗口
import { WebviewWindow } from '@tauri-apps/api/webviewWindow'
async function showDialog() {
const existing = await WebviewWindow.getByLabel('dialog')
if (existing) {
await existing.show()
await existing.setFocus()
} else {
await new WebviewWindow('dialog', { url: '/dialog' })
}
}
7. 安全标准
5.1 领域漏洞格局
研究日期: 2025-11-20
| CVE ID | 严重性 | 描述 | 缓解措施 |
|---|---|---|---|
| CVE-2024-35222 | 高 | iFrames绕过来源检查 | 升级到1.6.7+或2.0.0-beta.20+ |
| CVE-2024-24576 | 关键 | Rust命令注入 | 升级Rust到1.77.2+ |
| CVE-2023-46115 | 中 | 更新器密钥通过Vite泄露 | 从envPrefix移除TAURI_ |
| CVE-2023-34460 | 中 | 文件系统范围绕过 | 升级到1.4.1+ |
| CVE-2022-46171 | 高 | 宽松通配符模式 | 使用显式路径白名单 |
完整CVE详情和缓解代码见
references/security-examples.md
5.2 OWASP Top 10 2025映射
| OWASP类别 | 风险 | 关键缓解措施 |
|---|---|---|
| A01 失效的访问控制 | 关键 | 能力系统、IPC验证 |
| A02 加密失败 | 高 | 安全更新器签名、TLS |
| A03 注入 | 高 | 验证IPC输入、CSP |
| A04 不安全设计 | 高 | 最小能力 |
| A05 安全配置错误 | 关键 | 限制性CSP、冻结原型 |
| A06 易受攻击的组件 | 高 | 保持Tauri更新 |
| A07 认证失败 | 中 | 来源验证 |
| A08 数据完整性失败 | 高 | 签名更新 |
5.3 输入验证框架
use validator::Validate;
#[derive(serde::Deserialize, Validate)]
pub struct UserCommand {
#[validate(length(min = 1, max = 100))]
pub name: String,
#[validate(range(min = 1, max = 1000))]
pub count: u32,
#[validate(custom(function = "validate_path"))]
pub file_path: Option<String>,
}
fn validate_path(path: &str) -> Result<(), validator::ValidationError> {
if path.contains("..") || path.contains("~") {
return Err(validator::ValidationError::new("invalid_path"));
}
Ok(())
}
5.4 秘密管理
// 绝不放入vite.config.ts——泄露TAURI_PRIVATE_KEY!
{ "envPrefix": ["VITE_", "TAURI_"] }
// 正确:仅暴露VITE_变量
{ "envPrefix": ["VITE_"] }
// 运行时加载秘密,绝不硬编码
fn get_api_key() -> Result<String, Error> {
std::env::var("API_KEY").map_err(|_| Error::Configuration("API_KEY未设置".into()))
}
5.5 错误处理
use thiserror::Error;
#[derive(Error, Debug)]
pub enum AppError {
#[error("输入无效")]
Validation(#[from] validator::ValidationErrors),
#[error("操作未授权")]
PermissionDenied,
#[error("内部错误")]
Internal(#[source] anyhow::Error),
}
// 安全序列化——绝不向前端暴露内部详情
impl serde::Serialize for AppError {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where S: serde::Serializer {
tracing::error!("错误: {:?}", self);
serializer.serialize_str(&self.to_string())
}
}
6. 测试与验证
安全测试清单
npx tauri info # 检查配置
cd src-tauri && cargo audit # 审计依赖
npx tauri build --debug # 检查能力问题
npm run test:security # 测试IPC边界
安全测试示例
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_path_traversal_blocked() {
let request = FileRequest { path: "../../../etc/passwd".to_string() };
assert!(request.validate().is_err());
}
#[tokio::test]
async fn test_unauthorized_access_blocked() {
let result = sensitive_operation(mock_window_bad_origin()).await;
assert!(result.unwrap_err().contains("无效来源"));
}
}
完整测试示例见
references/security-examples.md
8. 常见错误与反模式
反模式1:过度宽松的能力
// 绝不:授予整个文件系统访问
{ "permissions": ["fs:default", "fs:scope-home"] }
// 始终:限定到特定目录
{ "permissions": [{ "identifier": "fs:read-files", "allow": ["$APPDATA/myapp/*"] }] }
反模式2:禁用CSP
// 绝不
{ "security": { "csp": null } }
// 始终
{ "security": { "csp": "default-src 'self'; script-src 'self'" } }
反模式3:启用Shell执行
// 绝不
{ "permissions": ["shell:allow-execute"] }
// 如需:仅严格白名单
{
"permissions": [{
"identifier": "shell:allow-execute",
"allow": [{ "name": "git", "cmd": "git", "args": ["status"] }]
}]
}
反模式4:暴露Tauri密钥
// 绝不——泄露私钥!
export default { envPrefix: ['VITE_', 'TAURI_'] }
// 始终
export default { envPrefix: ['VITE_'] }
反模式5:无IPC验证
// 绝不:直接使用用户输入
#[command]
fn read_file(path: String) -> String { std::fs::read_to_string(path).unwrap() }
// 始终:验证和限定
#[command]
fn read_file(request: ValidatedFileRequest) -> Result<String, String> { /* ... */ }
13. 预部署清单
安全清单
- [ ] Tauri 2.0+ 带最新补丁
- [ ] Rust 1.77.2+(修复CVE-2024-24576)
- [ ] CSP配置为限制性
- [ ]
freezePrototype: true启用 - [ ] 能力使用最小权限
- [ ] 文件系统范围为显式路径
- [ ] Shell执行禁用或白名单
- [ ] 前端envPrefix中无TAURI_
- [ ] 自动更新器使用签名验证
- [ ] 所有IPC命令验证输入
- [ ] 敏感操作来源验证
- [ ]
cargo audit通过
运行时清单
- [ ] 生产环境中禁用调试模式
- [ ] 生产环境中禁用开发者工具
- [ ] 禁用远程调试
- [ ] 更新检查工作正常
14. 总结
您的目标是创建Tauri应用,这些应用是:
- 默认安全: 最小能力、限制性CSP
- 深度防御: 多层安全
- 已验证: 所有IPC输入已验证
- 透明: 签名更新、清晰权限
安全提醒:
- 若无严格白名单,绝不启用Shell执行
- 始终限定文件系统访问到特定目录
- 配置CSP以阻止XSS和数据外泄
- 验证敏感操作的来源
- 签名更新并验证签名
- 保持Tauri和Rust更新以获取安全补丁
攻击场景和威胁建模见
references/threat-model.md