tauri について
Web フロントエンドとネイティブ機能をつなぐ Tauri デスクトップアプリの開発スキルです。最小権限、IPC の入力検証、CSP、ケイパビリティ設定、セキュリティテストを中心に扱います。
主な機能
- 01Tauri のケイパビリティと権限の設定
- 02IPC コマンドの保護と入力検証
- 03コンテンツセキュリティポリシーの設定
- 04ウィンドウ、プラグイン、アップデーターの保護
- 05テストと依存関係監査の手順
活用シーン
- 01アプリのファイルシステムアクセス範囲の制限
- 02フロントエンドからの IPC リクエスト検証
- 03Tauri アプリの 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 デスクトップフレームワークスキル
ファイル構成
このスキルでは、HIGH-RISK 要件に対応するため、構成を分割しています。
- SKILL.md:基本原則、パターン、重要なセキュリティ対策(このファイル)
- references/security-examples.md:CVE の詳細と OWASP の実装
- references/advanced-patterns.md:高度な Tauri のパターンとプラグイン
- references/threat-model.md:攻撃シナリオと STRIDE 分析
検証ゲート
ゲート 0.1:ドメイン専門知識の検証
- ステータス:合格
- 専門分野:IPC セキュリティ、capabilities システム、CSP、プラグインアーキテクチャ、ウィンドウ管理
ゲート 0.2:脆弱性調査(HIGH-RISK の場合は必須)
- ステータス:合格(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:ファイル構成の決定
- 決定:分割構成(HIGH-RISK、メインファイル約 500 行+詳細なリファレンス)
1. 概要
リスクレベル:HIGH
理由:Tauri アプリケーションは、Web コンテンツとネイティブシステムへのアクセスを橋渡しします。IPC の不適切な設定、CSP の回避、capabilities の管理不備は、任意コード実行、ファイルシステムへのアクセス、権限昇格につながる可能性があります。
Web コードとネイティブコードのセキュリティ境界を深く理解した、Tauri デスクトップアプリケーション開発の専門家として対応します。機能を維持しながら、アプリケーションに必要最小限の権限を設定します。
主な専門分野
- Tauri の capabilities と権限システム
- IPC(プロセス間通信)セキュリティ
- コンテンツセキュリティポリシー(CSP)の設定
- プラグインの開発とセキュリティ
- 自動アップデーターのセキュリティ
- ウィンドウと WebView の管理
2. 主な責務
基本原則
- まず TDD:実装前にテストを作成し、動作が正しいことを検証します
- パフォーマンスを意識:非同期コマンド、効率的な IPC シリアライズ、リソース管理を行います
- 最小権限:必要な capabilities と権限のみを付与します
- 多層防御:複数のセキュリティ層(CSP、capabilities、検証)を設けます
- 安全なデフォルト設定:制限の厳しい設定から始め、機能は明示的に有効化します
- 入力検証:フロントエンドからのすべての IPC メッセージを検証します
- オリジン検証:すべての機密性の高い操作でオリジンを確認します
- 透明性のある更新:署名検証を用いた安全な更新メカニズムを採用します
判断基準
| 状況 | 対応 |
|---|---|
| ファイルシステムへのアクセスが必要 | 特定のディレクトリに限定し、ルートは決して許可しません |
| シェル実行が必要 | デフォルトでは無効にし、必要な場合は許可リストを使用します |
| ネットワークアクセスが必要 | 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:最小限の capabilities 設定
// 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 | 高 | iFrame がオリジンチェックを回避する | 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 | 高 | 過度に許容的な glob パターン | 明示的なパスの許可リストを使用する |
CVE の詳細と緩和策のコードについては、
references/security-examples.mdを参照してください
5.2 OWASP Top 10 2025 との対応
| OWASP カテゴリ | リスク | 主な緩和策 |
|---|---|---|
| A01 アクセス制御の不備 | 重大 | Capability システム、IPC 検証 |
| A02 暗号化の失敗 | 高 | 安全なアップデーター署名、TLS |
| A03 インジェクション | 高 | IPC 入力の検証、CSP |
| A04 安全でない設計 | 高 | 最小限の Capability |
| 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 シークレット管理
// 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:過度に許容的な Capability
// 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:シェル実行の有効化
// 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を有効化 - Capability の権限を最小限に設定
- ファイルシステムのスコープを明示的なパスに設定
- シェル実行を無効化するか、許可リストで制限
- フロントエンドの envPrefix に TAURI_ が含まれていない
- 自動アップデーターで署名検証を使用
- すべての IPC コマンドで入力を検証
- 機密性の高い操作でオリジンを検証
-
cargo auditが成功する
ランタイムチェックリスト
- 本番環境でデバッグモードを無効化
- 本番環境で DevTools を無効化
- リモートデバッグを無効化
- アップデートの確認が機能する
14. まとめ
目標は、以下の特性を備えた Tauri アプリケーションを作成することです。
- デフォルトで安全:最小限の Capability、制限的な CSP
- 多層防御:複数のセキュリティ層
- 検証済み:すべての IPC 入力を検証
- 透明性:署名付きアップデート、明確な権限
セキュリティに関する注意:
- 厳格な許可リストなしにシェル実行を有効にしないでください
- ファイルシステムへのアクセスは、必ず特定のディレクトリに限定してください
- XSS とデータの外部流出をブロックするように CSP を設定してください
- 機密性の高い操作ではオリジンを検証してください
- アップデートに署名し、署名を検証してください
- セキュリティパッチを適用するため、Tauri と Rust を最新の状態に保ってください
攻撃シナリオと脅威モデリングについては、
references/threat-model.mdを参照してください