tauri
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseTauri Desktop Framework Skill
Tauri桌面应用框架技能指南
File Organization
文件组织结构
This skill uses a split structure for HIGH-RISK requirements:
- SKILL.md: Core principles, patterns, and essential security (this file)
- references/security-examples.md: Complete CVE details and OWASP implementations
- references/advanced-patterns.md: Advanced Tauri patterns and plugins
- references/threat-model.md: Attack scenarios and STRIDE analysis
本技能指南针对高风险需求采用拆分结构:
- SKILL.md:核心原则、模式及基础安全内容(本文档)
- references/security-examples.md:完整CVE详情及OWASP实现方案
- references/advanced-patterns.md:高级Tauri模式与插件开发
- references/threat-model.md:攻击场景与STRIDE分析
Validation Gates
验证关卡
Gate 0.1: Domain Expertise Validation
关卡0.1:领域专业度验证
- Status: PASSED
- Expertise Areas: IPC security, capabilities system, CSP, plugin architecture, window management
- 状态:已通过
- 专业领域:IPC安全、权限系统、CSP、插件架构、窗口管理
Gate 0.2: Vulnerability Research (BLOCKING for HIGH-RISK)
关卡0.2:漏洞研究(高风险需求必填)
- Status: PASSED (5+ CVEs documented)
- Research Date: 2025-11-20
- CVEs Documented: CVE-2024-35222, CVE-2024-24576, CVE-2023-46115, CVE-2023-34460, CVE-2022-46171
- 状态:已通过(记录5个以上CVE)
- 研究日期:2025-11-20
- 已记录CVE:CVE-2024-35222、CVE-2024-24576、CVE-2023-46115、CVE-2023-34460、CVE-2022-46171
Gate 0.5: Hallucination Self-Check
关卡0.5:幻觉自我检查
- Status: PASSED
- Verification: All configurations tested against Tauri 2.0
- 状态:已通过
- 验证方式:所有配置均针对Tauri 2.0测试
Gate 0.11: File Organization Decision
关卡0.11:文件组织结构决策
- Decision: Split structure (HIGH-RISK, ~500 lines main + extensive references)
- 决策:拆分结构(高风险需求,主文档约500行+扩展参考文档)
1. Overview
1. 概述
Risk Level: HIGH
Justification: Tauri applications bridge web content with native system access. Improper IPC configuration, CSP bypasses, and capability mismanagement can lead to arbitrary code execution, file system access, and privilege escalation.
You are an expert in Tauri desktop application development with deep understanding of the security boundaries between web and native code. You configure applications with minimal permissions while maintaining functionality.
风险等级:高
理由:Tauri应用连接Web内容与本地系统权限。不当的IPC配置、CSP绕过及权限管理失误可能导致任意代码执行、文件系统访问及权限提升。
您是Tauri桌面应用开发专家,深入理解Web与原生代码间的安全边界。您能在保持功能的同时,以最小权限配置应用。
Core Expertise Areas
核心专业领域
- Tauri capability and permission system
- IPC (Inter-Process Communication) security
- Content Security Policy (CSP) configuration
- Plugin development and security
- Auto-updater security
- Window and webview management
- Tauri权限与许可系统
- IPC(进程间通信)安全
- 内容安全策略(CSP)配置
- 插件开发与安全
- 自动更新器安全
- 窗口与WebView管理
2. Core Responsibilities
2. 核心职责
Fundamental Principles
基本原则
- TDD First: Write tests before implementation - verify behavior works correctly
- Performance Aware: Async commands, efficient IPC serialization, resource management
- Least Privilege: Grant only necessary capabilities and permissions
- Defense in Depth: Multiple security layers (CSP, capabilities, validation)
- Secure Defaults: Start with restrictive config, enable features explicitly
- Input Validation: Validate all IPC messages from frontend
- Origin Verification: Check origins for all sensitive operations
- Transparent Updates: Secure update mechanism with signature verification
- 测试驱动开发优先:先编写测试再实现功能——验证行为正确性
- 性能感知:异步命令、高效IPC序列化、资源管理
- 最小权限原则:仅授予必要的权限与能力
- 纵深防御:多层安全防护(CSP、权限、验证)
- 安全默认配置:从严格配置开始,显式启用所需功能
- 输入验证:验证所有来自前端的IPC消息
- 来源验证:对所有敏感操作进行来源检查
- 透明更新:带签名验证的安全更新机制
Decision Framework
决策框架
| Situation | Approach |
|---|---|
| Need filesystem access | Scope to specific directories, never root |
| Need shell execution | Disable by default, use allowlist if required |
| Need network access | Specify allowed domains in CSP |
| Custom IPC commands | Validate all inputs, check permissions |
| Sensitive operations | Require origin verification |
| 场景 | 处理方式 |
|---|---|
| 需要文件系统访问 | 限定到特定目录,绝不允许根目录 |
| 需要Shell执行 | 默认禁用,必要时使用白名单 |
| 需要网络访问 | 在CSP中指定允许的域名 |
| 自定义IPC命令 | 验证所有输入,检查权限 |
| 敏感操作 | 要求来源验证 |
3. Technical Foundation
3. 技术基础
Version Recommendations
版本推荐
| Category | Version | Notes |
|---|---|---|
| Tauri CLI | 2.0+ | Use 2.x for new projects |
| Tauri Core | 2.0+ | Significant security improvements over 1.x |
| Rust | 1.77.2+ | CVE-2024-24576 fix |
| Node.js | 20 LTS | For build tooling |
| 分类 | 版本 | 说明 |
|---|---|---|
| Tauri CLI | 2.0+ | 新项目使用2.x版本 |
| Tauri Core | 2.0+ | 相比1.x版本有重大安全改进 |
| Rust | 1.77.2+ | 包含CVE-2024-24576修复 |
| Node.js | 20 LTS | 用于构建工具链 |
Security Configuration Files
安全配置文件结构
src-tauri/
├── Cargo.toml
├── tauri.conf.json # Main configuration
├── capabilities/ # Permission definitions
│ ├── default.json
│ └── admin.json
└── src/
└── main.rssrc-tauri/
├── Cargo.toml
├── tauri.conf.json # 主配置文件
├── capabilities/ # 权限定义目录
│ ├── default.json
│ └── admin.json
└── src/
└── main.rs4. Implementation Workflow (TDD)
4. 实现工作流(测试驱动开发)
Step 1: Write Failing Test First
步骤1:先编写失败的测试
Rust Backend Test:
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());
}
}Frontend Vitest Test:
typescript
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')
})
})Rust后端测试:
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测试:
typescript
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')
})
})Step 2: Implement Minimum to Pass
步骤2:实现最小代码使测试通过
Write only the code necessary to make the test pass:
rust
#[command]
pub async fn process_data(input: String) -> Result<String, String> {
// Minimum implementation to pass test
Ok(format!("Processed: {}", input))
}仅编写让测试通过的必要代码:
rust
#[command]
pub async fn process_data(input: String) -> Result<String, String> {
// 仅实现满足测试的最小代码
Ok(format!("Processed: {}", input))
}Step 3: Refactor if Needed
步骤3:按需重构
After tests pass, improve code structure without changing behavior:
- Extract common validation logic
- Improve error messages
- Add documentation
测试通过后,在不改变行为的前提下优化代码结构:
- 提取通用验证逻辑
- 优化错误提示信息
- 添加文档注释
Step 4: Run Full Verification
步骤4:运行完整验证
bash
undefinedbash
undefinedRust tests and linting
Rust测试与代码检查
cd src-tauri && cargo test
cd src-tauri && cargo clippy -- -D warnings
cd src-tauri && cargo audit
cd src-tauri && cargo test
cd src-tauri && cargo clippy -- -D warnings
cd src-tauri && cargo audit
Frontend tests
前端测试
npm test
npm run typecheck
---npm test
npm run typecheck
---5. Implementation Patterns
5. 实现模式
Pattern 1: Minimal Capability Configuration
模式1:最小权限配置
json
// 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/*"]
}
]
}json
// 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/*"]
}
]
}Pattern 2: Secure CSP Configuration
模式2:安全CSP配置
json
// 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
}
}
}json
// 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
}
}
}Pattern 3: Secure IPC Commands
模式3:安全IPC命令
rust
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))
}rust
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))
}Pattern 4: Origin Verification
模式4:来源验证
rust
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(())
}rust
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(())
}Pattern 5: Secure Auto-Updater
模式5:安全自动更新器
rust
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(())
}For advanced patterns and plugin development, seereferences/advanced-patterns.md
rust
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. Performance Patterns
6. 性能优化模式
Pattern 1: Async Commands for Heavy Operations
模式1:异步命令处理重型操作
rust
// 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())
}rust
// 错误示例:阻塞主线程
#[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())
}Pattern 2: Efficient IPC Serialization
模式2:高效IPC序列化
rust
// 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
}rust
// 错误示例:大型嵌套结构
#[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> {
// 返回小批量数据
}Pattern 3: Resource Cleanup and Lifecycle
模式3:资源清理与生命周期管理
rust
// 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(())
}rust
// 错误示例:窗口关闭时未清理资源
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(())
}Pattern 4: State Management Optimization
模式4:状态管理优化
rust
// 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
}rust
// 错误示例:每次访问克隆大型状态
#[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克隆
}Pattern 5: Window Management Patterns
模式5:窗口管理模式
typescript
// 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' })
}
}typescript
// 错误示例:每次创建新窗口不重用
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. Security Standards
7. 安全标准
5.1 Domain Vulnerability Landscape
7.1 领域漏洞现状
Research Date: 2025-11-20
| CVE ID | Severity | Description | Mitigation |
|---|---|---|---|
| CVE-2024-35222 | HIGH | iFrames bypass origin checks | Upgrade to 1.6.7+ or 2.0.0-beta.20+ |
| CVE-2024-24576 | CRITICAL | Rust command injection | Upgrade Rust to 1.77.2+ |
| CVE-2023-46115 | MEDIUM | Updater keys leaked via Vite | Remove TAURI_ from envPrefix |
| CVE-2023-34460 | MEDIUM | Filesystem scope bypass | Upgrade to 1.4.1+ |
| CVE-2022-46171 | HIGH | Permissive glob patterns | Use explicit path allowlists |
Seefor complete CVE details and mitigation codereferences/security-examples.md
研究日期:2025-11-20
| CVE编号 | 严重程度 | 描述 | 缓解方案 |
|---|---|---|---|
| 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 Mapping
7.2 OWASP Top 10 2025映射
| OWASP Category | Risk | Key Mitigations |
|---|---|---|
| A01 Broken Access Control | CRITICAL | Capability system, IPC validation |
| A02 Cryptographic Failures | HIGH | Secure updater signatures, TLS |
| A03 Injection | HIGH | Validate IPC inputs, CSP |
| A04 Insecure Design | HIGH | Minimal capabilities |
| A05 Security Misconfiguration | CRITICAL | Restrictive CSP, frozen prototype |
| A06 Vulnerable Components | HIGH | Keep Tauri updated |
| A07 Auth Failures | MEDIUM | Origin verification |
| A08 Data Integrity Failures | HIGH | Signed updates |
| OWASP分类 | 风险等级 | 核心缓解措施 |
|---|---|---|
| A01 访问控制失效 | 关键 | 权限系统、IPC验证 |
| A02 加密失败 | 高 | 安全更新器签名、TLS |
| A03 注入攻击 | 高 | IPC输入验证、CSP |
| A04 不安全设计 | 高 | 最小权限配置 |
| A05 安全配置错误 | 关键 | 严格CSP、冻结原型 |
| A06 易受攻击的组件 | 高 | 保持Tauri版本更新 |
| A07 认证失败 | 中 | 来源验证 |
| A08 数据完整性失败 | 高 | 签名更新 |
5.3 Input Validation Framework
7.3 输入验证框架
rust
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(())
}rust
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 Secrets Management
7.4 密钥管理
json
// NEVER in vite.config.ts - leaks TAURI_PRIVATE_KEY!
{ "envPrefix": ["VITE_", "TAURI_"] }
// GOOD: Only expose VITE_ variables
{ "envPrefix": ["VITE_"] }rust
// 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()))
}json
// 绝对不要在vite.config.ts中配置 - 会泄漏TAURI_PRIVATE_KEY!
{ "envPrefix": ["VITE_", "TAURI_"] }
// 正确配置:仅暴露VITE_前缀的变量
{ "envPrefix": ["VITE_"] }rust
// 在运行时加载密钥,绝对不要硬编码
fn get_api_key() -> Result<String, Error> {
std::env::var("API_KEY").map_err(|_| Error::Configuration("未设置API_KEY".into()))
}5.5 Error Handling
7.5 错误处理
rust
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())
}
}rust
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. Testing & Validation
8. 测试与验证
Security Testing Checklist
安全测试清单
bash
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 boundariesbash
npx tauri info # 检查配置
cd src-tauri && cargo audit # 审计依赖
npx tauri build --debug # 检查权限问题
npm run test:security # 测试IPC边界Security Test Examples
安全测试示例
rust
#[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"));
}
}For comprehensive test examples, seereferences/security-examples.md
rust
#[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. Common Mistakes & Anti-Patterns
9. 常见错误与反模式
Anti-Pattern 1: Overly Permissive Capabilities
反模式1:过度宽松的权限配置
json
// 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/*"] }] }json
// 绝对不要这样配置:授予整个文件系统访问权限
{ "permissions": ["fs:default", "fs:scope-home"] }
// 正确配置:限定到特定目录
{ "permissions": [{ "identifier": "fs:read-files", "allow": ["$APPDATA/myapp/*"] }] }Anti-Pattern 2: Disabled CSP
反模式2:禁用CSP
json
// NEVER
{ "security": { "csp": null } }
// ALWAYS
{ "security": { "csp": "default-src 'self'; script-src 'self'" } }json
// 绝对不要这样配置
{ "security": { "csp": null } }
// 正确配置
{ "security": { "csp": "default-src 'self'; script-src 'self'" } }Anti-Pattern 3: Shell Execution Enabled
反模式3:启用Shell执行
json
// NEVER
{ "permissions": ["shell:allow-execute"] }
// IF NEEDED: Strict allowlist only
{
"permissions": [{
"identifier": "shell:allow-execute",
"allow": [{ "name": "git", "cmd": "git", "args": ["status"] }]
}]
}json
// 绝对不要这样配置
{ "permissions": ["shell:allow-execute"] }
// 必要时配置:仅使用严格白名单
{
"permissions": [{
"identifier": "shell:allow-execute",
"allow": [{ "name": "git", "cmd": "git", "args": ["status"] }]
}]
}Anti-Pattern 4: Exposing Tauri Keys
反模式4:暴露Tauri密钥
typescript
// NEVER - leaks private keys!
export default { envPrefix: ['VITE_', 'TAURI_'] }
// ALWAYS
export default { envPrefix: ['VITE_'] }typescript
// 绝对不要这样配置 - 会泄漏私钥!
export default { envPrefix: ['VITE_', 'TAURI_'] }
// 正确配置
export default { envPrefix: ['VITE_'] }Anti-Pattern 5: No IPC Validation
反模式5:未验证IPC输入
rust
// 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> { /* ... */ }rust
// 绝对不要这样:直接使用用户输入
#[command]
fn read_file(path: String) -> String { std::fs::read_to_string(path).unwrap() }
// 正确做法:验证并限定范围
#[command]
fn read_file(request: ValidatedFileRequest) -> Result<String, String> { /* ... */ }13. Pre-Deployment Checklist
13. 部署前检查清单
Security Checklist
安全检查项
- Tauri 2.0+ with latest patches
- Rust 1.77.2+ (CVE-2024-24576 fix)
- CSP configured restrictively
- enabled
freezePrototype: true - Capabilities use minimal permissions
- Filesystem scopes are explicit paths
- Shell execution disabled or allowlisted
- No TAURI_ in frontend envPrefix
- Auto-updater uses signature verification
- All IPC commands validate input
- Origin verification for sensitive ops
- passes
cargo audit
- 使用Tauri 2.0+及最新补丁
- 使用Rust 1.77.2+(修复CVE-2024-24576)
- 配置严格的CSP
- 启用
freezePrototype: true - 权限配置遵循最小权限原则
- 文件系统范围为明确路径
- Shell执行已禁用或配置严格白名单
- 前端envPrefix中不包含TAURI_
- 自动更新器使用签名验证
- 所有IPC命令均验证输入
- 敏感操作已配置来源验证
- 检查通过
cargo audit
Runtime Checklist
运行时检查项
- Debug mode disabled in production
- DevTools disabled in production
- Remote debugging disabled
- Update checks working
- 生产环境禁用调试模式
- 生产环境禁用开发者工具
- 禁用远程调试
- 更新检查功能正常
14. Summary
14. 总结
Your goal is to create Tauri applications that are:
- Secure by Default: Minimal capabilities, restrictive CSP
- Defense in Depth: Multiple security layers
- Validated: All IPC inputs validated
- Transparent: Signed updates, clear permissions
Security Reminder:
- Never enable shell execution without strict allowlist
- Always scope filesystem access to specific directories
- Configure CSP to block XSS and data exfiltration
- Verify origins for sensitive operations
- Sign updates and verify signatures
- Keep Tauri and Rust updated for security patches
For attack scenarios and threat modeling, seereferences/threat-model.md
您的目标是创建具备以下特性的Tauri应用:
- 默认安全:最小权限、严格CSP
- 纵深防御:多层安全防护
- 已验证:所有IPC输入均经过验证
- 透明:签名更新、清晰的权限配置
安全提醒:
- 除非配置严格白名单,否则绝不启用Shell执行
- 始终将文件系统访问限定到特定目录
- 配置CSP以阻止XSS攻击与数据泄露
- 对敏感操作进行来源验证
- 对更新进行签名并验证签名
- 保持Tauri与Rust版本更新以获取安全补丁
攻击场景与威胁建模请参考references/threat-model.md