understanding-tauri-ipc について
Tauri のフロントエンドと Rust バックエンド間の IPC とセキュリティパターンを解説する開発スキルです。イベントとコマンドによる通信を扱い、標準の brownfield とサンドボックス型の isolation を比較します。
主な機能
- 01イベントとコマンド通信の解説
- 02brownfield と isolation の比較
- 03IPC メッセージの検証と暗号化
- 04サンドボックス分離の設定
- 05IPC セキュリティ対策の適用
活用シーン
- 01既存の Web アプリをデスクトップに移行
- 02Tauri アプリの IPC パターンを選定
- 03フロントエンドからの機密操作を保護
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ dchuk/ claude-code-tauri-skills --skill understanding-tauri-ipcインストール後、Claude Code で/understanding-tauri-ipcを入力して読み込まれたか確認します。
詳細
Tauri のプロセス間通信(IPC)
このスキルでは、Tauri の IPC システムと、フロントエンドとバックエンドのプロセス間で安全に通信するためのブラウンフィールドパターンおよびアイソレーションパターンについて説明します。
概要
Tauri は非同期メッセージパッシングを使用してプロセス間通信を実装しています。これにより、分離されたプロセス間でシリアライズされたリクエストとレスポンスを安全にやり取りできます。
メッセージパッシングを使用する理由
- 共有メモリや関数への直接アクセスよりも安全です
- 受信側は悪意のあるリクエストを拒否または破棄できます
- Tauri Core は実行前にすべてのリクエストを検証します
- 許可されていない関数の呼び出しを防ぎます
IPC プリミティブ
Tauri は 2 つの IPC プリミティブを提供しています。
イベント
- 方向:双方向(フロントエンド <-> Tauri Core)
- タイプ:一方向のメッセージング(応答なし)
- 最適な用途:ライフサイクルイベント、状態の変化、通知
Rust(フロントエンドに送信):
use tauri::{AppHandle, Emitter};
fn emit_event(app: &AppHandle) {
app.emit("backend-event", "payload data").unwrap();
}
フロントエンド(リッスン):
import { listen } from '@tauri-apps/api/event';
const unlisten = await listen('backend-event', (event) => {
console.log('Received:', event.payload);
});
// Call unlisten() when done
フロントエンド(バックエンドに送信):
import { emit } from '@tauri-apps/api/event';
await emit('frontend-event', { data: 'value' });
コマンド
- 方向:フロントエンド -> Rust バックエンド
- プロトコル:JSON-RPC ベースの抽象化
- API:ブラウザーの
fetch()API に類似 - 要件:引数と戻り値のデータは JSON でシリアライズ可能である必要があります
Rust コマンドの定義:
#[tauri::command]
fn greet(name: &str) -> String {
format!("Hello, {}!", name)
}
fn main() {
tauri::Builder::default()
.invoke_handler(tauri::generate_handler![greet])
.run(tauri::generate_context!())
.expect("error while running tauri application");
}
フロントエンドからの呼び出し:
import { invoke } from '@tauri-apps/api/core';
const greeting = await invoke('greet', { name: 'World' });
console.log(greeting); // "Hello, World!"
Result を返す非同期コマンド:
#[tauri::command]
async fn read_file(path: String) -> Result<String, String> {
std::fs::read_to_string(&path)
.map_err(|e| e.to_string())
}
IPC パターン
Tauri は 2 つの IPC セキュリティパターンを提供しています。ブラウンフィールド(デフォルト)とアイソレーションです。
ブラウンフィールドパターン
概要
ブラウンフィールドパターンは、Tauri のデフォルトの IPC アプローチです。既存の Web フロントエンドプロジェクトへの変更を最小限に抑え、互換性を優先します。
使用する場面
- 既存の Web アプリケーションをデスクトップに移行する場合
- プロトタイプの迅速な作成や開発を行う場合
- 信頼できるフロントエンドコードを使用するアプリケーション
- IPC の対象範囲が限られたシンプルなアプリケーション
使用する理由
- 設定は不要です
- 既存の Web コードへの変更を最小限に抑えられます
- Tauri API に直接アクセスできます
- 最速で開発を進められます
設定
ブラウンフィールドはデフォルトです。明示的な設定は任意です。
{
"app": {
"security": {
"pattern": {
"use": "brownfield"
}
}
}
}
**注:**ブラウンフィールドには追加の設定オプションはありません。
コード例
Rust バックエンド:
#[tauri::command]
fn process_data(input: String) -> Result<String, String> {
// Direct processing without isolation layer
Ok(format!("Processed: {}", input))
}
フロントエンド:
import { invoke } from '@tauri-apps/api/core';
// Direct invocation - no isolation layer
const result = await invoke('process_data', { input: 'test' });
セキュリティに関する考慮事項
- フロントエンドコードは、公開されているすべてのコマンドに直接アクセスできます
- フロントエンドとバックエンドの間に追加の検証レイヤーはありません
- フロントエンドの依存関係に対するサプライチェーン攻撃により、コマンドが呼び出される可能性があります
- Rust のコマンドレベルの検証に依存します
アイソレーションパターン
概要
アイソレーションパターンでは、JavaScript を使用して、Tauri Core に到達する前にフロントエンドからの Tauri API メッセージをすべて傍受して変更します。セキュアな JavaScript アプリケーション(アイソレーションアプリケーション)は、サンドボックス化された iframe で実行され、すべての IPC 通信を検証して暗号化します。
使用する場面
- フロントエンドの依存関係が多いアプリケーション
- 高いセキュリティ要件がある場合
- 機密データや機密性の高い処理を扱う場合
- 一般公開向けのアプリケーション
- サプライチェーン攻撃が懸念される場合
使用する理由
開発時の脅威に対する保護:
- すべての IPC 呼び出しを実行前に検証します
- 悪意のある、または望ましくないフロントエンドからの呼び出しを検出します
- サプライチェーン攻撃のリスクを軽減します
- すべての通信に対するチェックポイントを提供します
Tauri は、可能な場合は常にアイソレーションを使用することを推奨しています。
仕組み
- Tauri の IPC ハンドラーがフロントエンドからメッセージを受信します
- メッセージがアイソレーションアプリケーション(サンドボックス化された iframe)にルーティングされます
- アイソレーションフックがメッセージを検証し、必要に応じて変更します
- 実行時に生成されたキーを使用して、メッセージが AES-GCM で暗号化されます
- 暗号化されたメッセージが IPC ハンドラーに返されます
- 暗号化されたメッセージが Tauri Core に渡され、復号と実行が行われます
主なセキュリティ機能:
- アプリケーションを起動するたびに新しい暗号化キーが生成されます
- サンドボックス化された iframe により、アイソレーションコードの改ざんを防ぎます
- イベントベースの API を含む、すべての IPC 呼び出しが検証されます
設定
tauri.conf.json:
{
"app": {
"security": {
"pattern": {
"use": "isolation",
"options": {
"dir": "../dist-isolation"
}
}
}
}
}
コード例
ディレクトリ構成:
project/
src/ # Main frontend
src-tauri/ # Rust backend
dist-isolation/
index.html
index.js
dist-isolation/index.html:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>Isolation Secure Script</title>
</head>
<body>
<script src="index.js"></script>
</body>
</html>
dist-isolation/index.js:
window.__TAURI_ISOLATION_HOOK__ = (payload) => {
// Log all IPC calls for debugging
console.log('IPC call intercepted:', payload);
// Return payload unchanged (passthrough)
return payload;
};
検証例(index.js):
window.__TAURI_ISOLATION_HOOK__ = (payload) => {
// Validate command calls
if (payload.cmd === 'invoke') {
const { __tauriModule, message } = payload;
// Block unauthorized file system access
if (message.cmd === 'readFile') {
const path = message.path;
if (!path.startsWith('/allowed/directory/')) {
console.error('Blocked unauthorized file access:', path);
return null; // Block the request
}
}
// Validate specific commands
if (message.cmd === 'deleteItem') {
if (!confirm('Are you sure you want to delete this item?')) {
return null; // User cancelled
}
}
}
return payload;
};
包括的な検証例:
const ALLOWED_COMMANDS = ['greet', 'read_config', 'save_settings'];
const BLOCKED_PATHS = ['/etc/', '/usr/', '/System/'];
window.__TAURI_ISOLATION_HOOK__ = (payload) => {
// Validate invoke commands
if (payload.cmd === 'invoke') {
const commandName = payload.message?.cmd;
// Whitelist approach
if (!ALLOWED_COMMANDS.includes(commandName)) {
console.warn('Blocked unknown command:', commandName);
return null;
}
// Validate path arguments
const args = payload.message?.args || {};
if (args.path) {
for (const blocked of BLOCKED_PATHS) {
if (args.path.startsWith(blocked)) {
console.error('Blocked access to protected path:', args.path);
return null;
}
}
}
}
// Validate event emissions
if (payload.cmd === 'emit') {
const eventName = payload.event;
// Add event validation as needed
}
return payload;
};
パフォーマンスに関する考慮事項
- AES-GCM 暗号化によるオーバーヘッドは、ほとんどのアプリケーションで最小限です
- HTTPS で使用される TLS 暗号化と同等です
- 鍵の生成にはシステムのエントロピーが必要です(最新のシステムではシームレスに処理されます)
- パフォーマンスが重要なアプリケーションでは、わずかな影響が見られる場合があります
制限事項
- Windows では、サンドボックス化された iframe 内で ES Modules を読み込めません
- ビルド時にスクリプトをインライン化する必要があります
- 外部ファイルは参照するのではなく、埋め込む必要があります
- 分離アプリケーションにはバンドラーを使用しないでください
ベストプラクティス
- シンプルに保つ:分離アプリケーションの依存関係を最小限に抑えます
- バンドラーを使わない:ES Modules と複雑なビルドプロセスは避けます
- 入力を検証する:IPC 呼び出しが想定されたパラメーターと一致することを確認します
- コマンドをホワイトリスト化する:既知の安全なコマンドのみを許可します
- 不審なアクティビティを記録する:潜在的な攻撃を監視します
- イベントにも適用する:Rust コードを実行するイベントを検証します
パターンの比較
| 項目 | ブラウンフィールド | 分離 |
|---|---|---|
| デフォルト | はい | いいえ |
| 設定 | 不要 | 分離アプリケーションが必要 |
| セキュリティ | 基本 | 強化 |
| 検証 | コマンドレベルのみ | すべての IPC 呼び出し |
| 暗号化 | なし | AES-GCM |
| パフォーマンス | 最速 | わずかなオーバーヘッド |
| 複雑さ | シンプル | 中程度 |
| 最適な用途 | 信頼できるコード、プロトタイプ | 本番環境、機密性の高いアプリ |
セキュリティのベストプラクティス
両方のパターンに共通
-
Rust コマンドですべての入力を検証する
#[tauri::command] fn process_file(path: String) -> Result<String, String> { // Always validate paths if path.contains("..") || path.starts_with("/etc") { return Err("Invalid path".into()); } // Process file... Ok("Done".into()) } -
型付き引数を使用する
#[derive(serde::Deserialize)] struct CreateUserArgs { name: String, email: String, } #[tauri::command] fn create_user(args: CreateUserArgs) -> Result<(), String> { // Type-safe argument handling Ok(()) } -
公開するコマンドを制限する:必要な機能のみを公開します
-
ケイパビリティベースの権限を使用する:
capabilities/で権限を設定します
分離パターンの場合
- 分離コードを最小限に保つ:攻撃対象領域を減らします
- 外部依存関係を避ける:分離アプリケーションでは npm パッケージを使用しません
- 厳格に検証する:ブラックリストよりホワイトリストを使用します
- 徹底的にテストする:検証でエッジケースを検出できることを確認します
パターンの選択
ブラウンフィールドを使用するケース:
- 内部ツールを構築する場合
- 迅速にプロトタイプを作成する場合
- フロントエンドコードを完全に信頼できる場合
- セキュリティ要件が最小限の場合
分離を使用するケース:
- 公開アプリケーションを構築する場合
- 機密性の高いユーザーデータを扱う場合
- 多数のサードパーティ製フロントエンドパッケージを使用する場合
- セキュリティを優先する場合
- コンプライアンス要件がある場合
迷った場合は、本番アプリケーションでは分離を優先してください。