mcpField
T
tauri

tauri

github.com/martinholovsky开发48551 次安装47 周前更新
代码仓库

关于 tauri

面向 Tauri 桌面应用开发的技能,供需要连接 Web 前端与原生功能的开发者使用。重点介绍最小权限、IPC 校验、CSP、能力配置及安全测试。

主要功能

  1. 01配置 Tauri 能力与权限
  2. 02保护 IPC 命令并验证输入
  3. 03设置内容安全策略
  4. 04管理窗口、插件与自动更新安全
  5. 05提供测试和依赖审计流程

适用场景

  1. 01限制应用的文件系统访问范围
  2. 02校验前端发起的 IPC 请求
  3. 03为 Tauri 应用配置 CSP 与权限
  4. 04审查应用依赖和安全边界

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/martinholovsky/claude-skills-generator --skill tauri

安装后在 Claude Code 中输入/tauri确认已加载。

包含的文件

除 SKILL.md 外,安装时一起下载的参考资料和脚本。

references/
advanced-patterns.md
security-examples.md
threat-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. 核心职责

基本原则
  1. 优先采用 TDD:在实现前编写测试,验证行为是否正常
  2. 关注性能:采用异步命令、高效的 IPC 序列化和资源管理
  3. 最小权限:仅授予必要的能力和权限
  4. 纵深防御:采用多层安全措施(CSP、能力、验证)
  5. 安全默认值:从限制性配置开始,明确启用所需功能
  6. 输入验证:验证来自前端的所有 IPC 消息
  7. 来源验证:检查所有敏感操作的来源
  8. 透明更新:采用带签名验证的安全更新机制
决策框架
情况做法
需要文件系统访问限定到特定目录,绝不使用根目录
需要执行 Shell 命令默认禁用;如有需要,则使用允许列表
需要网络访问在 CSP 中指定允许的域名
自定义 IPC 命令验证所有输入并检查权限
敏感操作要求进行来源验证

3. 技术基础

版本建议
类别版本说明
Tauri CLI2.0+新项目使用 2.x
Tauri Core2.0+相比 1.x 有显著的安全改进
Rust1.77.2+修复 CVE-2024-24576
Node.js20 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-35222HIGHiFrame 绕过来源检查升级到 1.6.7+ 或 2.0.0-beta.20+
CVE-2024-24576CRITICALRust 命令注入将 Rust 升级到 1.77.2+
CVE-2023-46115MEDIUM更新程序密钥通过 Vite 泄露从 envPrefix 中移除 TAURI_
CVE-2023-34460MEDIUM文件系统作用域绕过升级到 1.4.1+
CVE-2022-46171HIGH过于宽松的 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 输入
  • 透明:已签名的更新,清晰的权限

安全提醒:

  1. 除非设置严格的允许列表,否则切勿启用 Shell 执行
  2. 始终将文件系统访问限制在特定目录
  3. 配置 CSP 以阻止 XSS 和数据外泄
  4. 对敏感操作验证来源
  5. 对更新进行签名并验证签名
  6. 保持 Tauri 和 Rust 为最新版本,以获取安全补丁

有关攻击场景和威胁建模,请参阅 references/threat-model.md

评论

支持 Markdown