关于 tauri
面向 Tauri 桌面应用开发的技能,供需要连接 Web 前端与原生功能的开发者使用。重点介绍最小权限、IPC 校验、CSP、能力配置及安全测试。
主要功能
- 01配置 Tauri 能力与权限
- 02保护 IPC 命令并验证输入
- 03设置内容安全策略
- 04管理窗口、插件与自动更新安全
- 05提供测试和依赖审计流程
适用场景
- 01限制应用的文件系统访问范围
- 02校验前端发起的 IPC 请求
- 03为 Tauri 应用配置 CSP 与权限
- 04审查应用依赖和安全边界
安装
复制下面的命令,在终端中运行。
$
npx skills add https:/ / github.com/ martinholovsky/ claude-skills-generator --skill tauri安装后在 Claude Code 中输入/tauri确认已加载。
包含的文件
除 SKILL.md 外,安装时一起下载的参考资料和脚本。
references/advanced-patterns.mdsecurity-examples.mdthreat-model.md详细介绍
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 应用会将网页内容与原生系统访问连接起来。IPC 配置不当、CSP 绕过以及能力管理不善,可能导致任意代码执行、文件系统访问和权限提升。
你是 Tauri 桌面应用开发专家,深入了解网页代码与原生代码之间的安全边界。你会在维持功能的同时,将应用权限配置到最低限度。
核心专业领域
- 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 # Main configuration
├── capabilities/ # Permission definitions
│ ├── 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(), "Should reject path traversal");
}
#[tokio::test]
async fn test_async_command_returns_result() {
let result = process_data("valid input".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('invokes read_file command correctly', async () => {
vi.mocked(invoke).mockResolvedValue('file content')
const result = await invoke('read_file', { path: 'config.json' })
expect(result).toBe('file content')
})
})
步骤 2:实现最少代码以通过测试
只编写通过测试所需的代码:
#[command]
pub async fn process_data(input: String) -> Result<String, String> {
// Minimum implementation to pass test
Ok(format!("Processed: {}", input))
}
步骤 3:按需重构
测试通过后,在不改变行为的前提下改进代码结构:
- 提取通用验证逻辑
- 改进错误消息
- 添加文档
步骤 4:运行完整验证
# Rust tests and linting
cd src-tauri && cargo test
cd src-tauri && cargo clippy -- -D warnings
cd src-tauri && cargo audit
# Frontend tests
npm test
npm run typecheck
5. 实现模式
模式 1:最小能力配置
// src-tauri/capabilities/default.json
{
"$schema": "../gen/schemas/desktop-schema.json",
"identifier": "default",
"description": "Default permissions for standard users",
"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!("Validation error: {}", 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(|_| "Invalid path")?;
// Security: ensure path is within app directory
if !canonical.starts_with(&app_dir) {
return Err("Access denied: path traversal detected".into());
}
std::fs::read_to_string(canonical).map_err(|e| format!("Failed: {}", 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("Invalid origin".into());
}
if host.to_string() != "localhost" && host.to_string() != "tauri.localhost" {
return Err("Invalid origin".into());
}
}
_ => return Err("Invalid origin".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:用于繁重操作的异步命令
// BAD: Blocking the main thread
#[command]
fn process_file(path: String) -> Result<String, String> {
std::fs::read_to_string(path).map_err(|e| e.to_string())
}
// GOOD: Async with 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 序列化
// BAD: Large nested structures
#[command]
fn get_all_data() -> Result<Vec<ComplexObject>, String> {
// Returns megabytes of data
}
// GOOD: Paginated responses with minimal fields
#[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> {
// Returns small batches
}
模式 3:资源清理与生命周期
// BAD: No cleanup on window close
fn setup_handler(app: &mut App) {
let handle = app.handle().clone();
// Resources leak when window closes
}
// GOOD: Proper lifecycle management
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 resources for this window
cleanup_window_resources(window.label());
}
});
Ok(())
}
模式 4:状态管理优化
// BAD: Cloning large state on every access
#[command]
fn get_state(state: State<'_, AppState>) -> AppState {
state.inner().clone() // Expensive clone
}
// GOOD: Use Arc for shared state, return references
use std::sync::Arc;
#[command]
fn get_config(state: State<'_, Arc<AppConfig>>) -> Arc<AppConfig> {
Arc::clone(state.inner()) // Cheap Arc clone
}
模式 5:窗口管理模式
// BAD: Creating windows without reuse
async function showDialog() {
await new WebviewWindow('dialog', { url: '/dialog' }) // Creates new each time
}
// GOOD: Reuse existing windows
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 | HIGH | iFrame 绕过来源检查 | 升级到 1.6.7+ 或 2.0.0-beta.20+ |
| CVE-2024-24576 | CRITICAL | Rust 命令注入 | 将 Rust 升级到 1.77.2+ |
| CVE-2023-46115 | MEDIUM | 更新程序密钥通过 Vite 泄露 | 从 envPrefix 中移除 TAURI_ |
| CVE-2023-34460 | MEDIUM | 文件系统作用域绕过 | 升级到 1.4.1+ |
| CVE-2022-46171 | HIGH | 过于宽松的 glob 模式 | 使用明确的路径允许列表 |
完整的 CVE 详情和缓解代码,请参阅
references/security-examples.md
5.2 OWASP Top 10 2025 映射
| OWASP 类别 | 风险 | 主要缓解措施 |
|---|---|---|
| A01 访问控制失效 | CRITICAL | 能力系统、IPC 验证 |
| A02 加密失效 | HIGH | 安全的更新程序签名、TLS |
| A03 注入 | HIGH | 验证 IPC 输入、CSP |
| A04 不安全设计 | HIGH | 最小化能力 |
| A05 安全配置错误 | CRITICAL | 严格的 CSP、冻结原型 |
| A06 易受攻击的组件 | HIGH | 保持 Tauri 为最新版本 |
| A07 身份验证失败 | MEDIUM | 来源验证 |
| A08 数据完整性失效 | HIGH | 已签名的更新 |
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 密钥管理
// NEVER in vite.config.ts - leaks TAURI_PRIVATE_KEY!
{ "envPrefix": ["VITE_", "TAURI_"] }
// GOOD: Only expose VITE_ variables
{ "envPrefix": ["VITE_"] }
// Load secrets at runtime, never hardcode
fn get_api_key() -> Result<String, Error> {
std::env::var("API_KEY").map_err(|_| Error::Configuration("API_KEY not set".into()))
}
5.5 错误处理
use thiserror::Error;
#[derive(Error, Debug)]
pub enum AppError {
#[error("Invalid input")]
Validation(#[from] validator::ValidationErrors),
#[error("Operation not permitted")]
PermissionDenied,
#[error("Internal error")]
Internal(#[source] anyhow::Error),
}
// Safe serialization - never expose internal details to frontend
impl serde::Serialize for AppError {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where S: serde::Serializer {
tracing::error!("Error: {:?}", self);
serializer.serialize_str(&self.to_string())
}
}
6. 测试与验证
安全测试清单
npx tauri info # Check configuration
cd src-tauri && cargo audit # Audit dependencies
npx tauri build --debug # Check capability issues
npm run test:security # Test IPC boundaries
安全测试示例
#[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("Invalid origin"));
}
}
完整的测试示例,请参阅
references/security-examples.md
8. 常见错误与反模式
反模式 1:权限过于宽松的能力
// NEVER: Grants access to entire filesystem
{ "permissions": ["fs:default", "fs:scope-home"] }
// ALWAYS: Scope to specific directories
{ "permissions": [{ "identifier": "fs:read-files", "allow": ["$APPDATA/myapp/*"] }] }
反模式 2:禁用 CSP
// NEVER
{ "security": { "csp": null } }
// ALWAYS
{ "security": { "csp": "default-src 'self'; script-src 'self'" } }
反模式 3:启用 Shell 执行
// NEVER
{ "permissions": ["shell:allow-execute"] }
// IF NEEDED: Strict allowlist only
{
"permissions": [{
"identifier": "shell:allow-execute",
"allow": [{ "name": "git", "cmd": "git", "args": ["status"] }]
}]
}
反模式 4:暴露 Tauri 密钥
// NEVER - leaks private keys!
export default { envPrefix: ['VITE_', 'TAURI_'] }
// ALWAYS
export default { envPrefix: ['VITE_'] }
反模式 5:不验证 IPC
// NEVER: Direct use of user input
#[command]
fn read_file(path: String) -> String { std::fs::read_to_string(path).unwrap() }
// ALWAYS: Validate and scope
#[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检查通过
运行时清单
- 生产环境中禁用调试模式
- 生产环境中禁用 DevTools
- 禁用远程调试
- 更新检查正常工作
14. 总结
你的目标是创建符合以下特征的 Tauri 应用:
- 默认安全:最小化能力,严格的 CSP
- 纵深防御:多层安全防护
- 经过验证:验证所有 IPC 输入
- 透明:已签名的更新,清晰的权限
安全提醒:
- 除非设置严格的允许列表,否则切勿启用 Shell 执行
- 始终将文件系统访问限制在特定目录
- 配置 CSP 以阻止 XSS 和数据外泄
- 对敏感操作验证来源
- 对更新进行签名并验证签名
- 保持 Tauri 和 Rust 为最新版本,以获取安全补丁
有关攻击场景和威胁建模,请参阅
references/threat-model.md
评论
支持 Markdown