tauri

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Tauri 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

基本原则

  1. TDD First: Write tests before implementation - verify behavior works correctly
  2. Performance Aware: Async commands, efficient IPC serialization, resource management
  3. Least Privilege: Grant only necessary capabilities and permissions
  4. Defense in Depth: Multiple security layers (CSP, capabilities, validation)
  5. Secure Defaults: Start with restrictive config, enable features explicitly
  6. Input Validation: Validate all IPC messages from frontend
  7. Origin Verification: Check origins for all sensitive operations
  8. Transparent Updates: Secure update mechanism with signature verification
  1. 测试驱动开发优先:先编写测试再实现功能——验证行为正确性
  2. 性能感知:异步命令、高效IPC序列化、资源管理
  3. 最小权限原则:仅授予必要的权限与能力
  4. 纵深防御:多层安全防护(CSP、权限、验证)
  5. 安全默认配置:从严格配置开始,显式启用所需功能
  6. 输入验证:验证所有来自前端的IPC消息
  7. 来源验证:对所有敏感操作进行来源检查
  8. 透明更新:带签名验证的安全更新机制

Decision Framework

决策框架

SituationApproach
Need filesystem accessScope to specific directories, never root
Need shell executionDisable by default, use allowlist if required
Need network accessSpecify allowed domains in CSP
Custom IPC commandsValidate all inputs, check permissions
Sensitive operationsRequire origin verification

场景处理方式
需要文件系统访问限定到特定目录,绝不允许根目录
需要Shell执行默认禁用,必要时使用白名单
需要网络访问在CSP中指定允许的域名
自定义IPC命令验证所有输入,检查权限
敏感操作要求来源验证

3. Technical Foundation

3. 技术基础

Version Recommendations

版本推荐

CategoryVersionNotes
Tauri CLI2.0+Use 2.x for new projects
Tauri Core2.0+Significant security improvements over 1.x
Rust1.77.2+CVE-2024-24576 fix
Node.js20 LTSFor build tooling
分类版本说明
Tauri CLI2.0+新项目使用2.x版本
Tauri Core2.0+相比1.x版本有重大安全改进
Rust1.77.2+包含CVE-2024-24576修复
Node.js20 LTS用于构建工具链

Security Configuration Files

安全配置文件结构

src-tauri/
├── Cargo.toml
├── tauri.conf.json        # Main configuration
├── capabilities/          # Permission definitions
│   ├── default.json
│   └── admin.json
└── src/
    └── main.rs

src-tauri/
├── Cargo.toml
├── tauri.conf.json        # 主配置文件
├── capabilities/          # 权限定义目录
│   ├── default.json
│   └── admin.json
└── src/
    └── main.rs

4. 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
undefined
bash
undefined

Rust 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, see
references/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 IDSeverityDescriptionMitigation
CVE-2024-35222HIGHiFrames bypass origin checksUpgrade to 1.6.7+ or 2.0.0-beta.20+
CVE-2024-24576CRITICALRust command injectionUpgrade Rust to 1.77.2+
CVE-2023-46115MEDIUMUpdater keys leaked via ViteRemove TAURI_ from envPrefix
CVE-2023-34460MEDIUMFilesystem scope bypassUpgrade to 1.4.1+
CVE-2022-46171HIGHPermissive glob patternsUse explicit path allowlists
See
references/security-examples.md
for complete CVE details and mitigation code
研究日期:2025-11-20
CVE编号严重程度描述缓解方案
CVE-2024-35222iFrames绕过来源检查升级到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 CategoryRiskKey Mitigations
A01 Broken Access ControlCRITICALCapability system, IPC validation
A02 Cryptographic FailuresHIGHSecure updater signatures, TLS
A03 InjectionHIGHValidate IPC inputs, CSP
A04 Insecure DesignHIGHMinimal capabilities
A05 Security MisconfigurationCRITICALRestrictive CSP, frozen prototype
A06 Vulnerable ComponentsHIGHKeep Tauri updated
A07 Auth FailuresMEDIUMOrigin verification
A08 Data Integrity FailuresHIGHSigned 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 boundaries
bash
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, see
references/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
  • freezePrototype: true
    enabled
  • 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
  • cargo audit
    passes
  • 使用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:
  1. Never enable shell execution without strict allowlist
  2. Always scope filesystem access to specific directories
  3. Configure CSP to block XSS and data exfiltration
  4. Verify origins for sensitive operations
  5. Sign updates and verify signatures
  6. Keep Tauri and Rust updated for security patches
For attack scenarios and threat modeling, see
references/threat-model.md
您的目标是创建具备以下特性的Tauri应用:
  • 默认安全:最小权限、严格CSP
  • 纵深防御:多层安全防护
  • 已验证:所有IPC输入均经过验证
  • 透明:签名更新、清晰的权限配置
安全提醒
  1. 除非配置严格白名单,否则绝不启用Shell执行
  2. 始终将文件系统访问限定到特定目录
  3. 配置CSP以阻止XSS攻击与数据泄露
  4. 对敏感操作进行来源验证
  5. 对更新进行签名并验证签名
  6. 保持Tauri与Rust版本更新以获取安全补丁
攻击场景与威胁建模请参考
references/threat-model.md