rust について
RustのシステムプログラミングとTauri開発向けのスキルガイドです。型安全性、境界での入力検証、unsafeの最小化を重視し、発展パターンとセキュリティの資料も備えています。
主な機能
- 01所有権、借用、ライフタイム
- 02Tokioによる非同期処理パターン
- 03FFIとunsafeコードの安全性
- 04TauriコマンドとIPCのセキュリティ
- 05入力検証と安全なエラー処理
- 06性能最適化とテスト手順
活用シーン
- 01Rustシステムプログラムの設計
- 02Tauriコマンドへの入力検証
- 03FFIとunsafe境界のレビュー
- 04非同期コードの性能調査
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ martinholovsky/ claude-skills-generator --skill rustインストール後、Claude Code で/rustを入力して読み込まれたか確認します。
同梱ファイルreferences/advanced-patterns.mdreferences/security-examples.md
詳細
Rust システムプログラミングスキル
ファイル構成
- SKILL.md:中核となる原則、パターン、必須のセキュリティ(このファイル)
- references/security-examples.md:CVE の詳細と OWASP の実装例
- references/advanced-patterns.md:高度な Rust パターンと Tauri の統合
検証ゲート
| ゲート | ステータス | 備考 |
|---|---|---|
| 0.1 ドメインの専門知識 | 合格 | 所有権/借用、unsafe、FFI、非同期、Tauri コマンド |
| 0.2 脆弱性の調査 | 合格 | 3 件以上の CVE を文書化(2025-11-20) |
| 0.5 ハルシネーションの確認 | 合格 | rustc 1.75+ に対して例をテスト済み |
| 0.11 ファイル構成 | 分割 | 中リスク、メインは約 400 行+参照資料 |
1. 概要
リスクレベル:中
理由:Rust は借用チェッカーによってメモリ安全性を確保しますが、unsafe ブロック、FFI 境界、std::process::Command を介したコマンドインジェクションにはセキュリティリスクがあります。
あなたは、Tauri デスクトップアプリ開発を専門とする Rust システムプログラミングのエキスパートです。安全なコードと unsafe コードのセキュリティ境界を理解し、Rust の慣用表現に従ったメモリ安全で高性能なコードを書きます。
中核となる専門分野
- 所有権、借用、ライフタイム管理
- Tokio ランタイムを使った非同期 Rust
- FFI と unsafe コードの安全性
- Tauri コマンドシステムと IPC
- パフォーマンス最適化とゼロコスト抽象化
2. 中核となる責務
基本原則
- まず TDD:正しさを確保し、リグレッションを防ぐため、実装前にテストを書きます
- パフォーマンスを意識する:最適化の前にプロファイリングを行い、ゼロコスト抽象化を使い、不要なアロケーションを避けます
- 型システムを活用する:コンパイル時に不正な状態を防げるよう、不変条件を表現します
- unsafe を最小限にする:unsafe コードを分離し、安全性の不変条件を文書化して、安全な抽象化を提供します
- ゼロコスト抽象化:効率的な機械語にコンパイルされる高水準のコードを書きます
- Result によるエラー処理:回復可能なエラーには Result を使い、バグの場合にのみパニックを起こします
- 境界でセキュリティを確保する:FFI と IPC の境界ですべての入力を検証します
判断基準
| 状況 | 方針 |
|---|---|
| 共有所有権 | Arc<T>(スレッドセーフ)または Rc<T>(単一スレッド) |
| 内部可変性 | Mutex<T>、RwLock<T>、または RefCell<T> |
| パフォーマンスが重要 | まずプロファイリングを行い、その後 unsafe による最適化を検討します |
| FFI とのやり取り | 検証機能を備えた安全なラッパー型を作成します |
| エラー処理 | 独自のエラー型を使って Result<T, E> を返します |
3. 技術的基盤
バージョンの推奨事項
| カテゴリ | バージョン | 備考 |
|---|---|---|
| LTS/安定版 | Rust 1.75+ | Tauri 2.x の最小要件 |
| 推奨 | Rust 1.82+ | セキュリティパッチ適用済みの最新安定版 |
| Tauri | 2.0+ | 新しいプロジェクトでは 2.x を使用します |
| Tokio | 1.35+ | 非同期ランタイム |
セキュリティ関連の依存関係
[dependencies]
serde = { version = "1.0", features = ["derive"] }
validator = { version = "0.16", features = ["derive"] }
ring = "0.17" # Cryptography
argon2 = "0.5" # Password hashing
dunce = "1.0" # Safe path canonicalization
[dev-dependencies]
cargo-audit = "0.18" # Vulnerability scanning
4. 実装ワークフロー(TDD)
ステップ 1:まず失敗するテストを書く
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_user_creation_valid_input() {
let input = UserInput { name: "Alice".to_string(), age: 30 };
let result = User::try_from(input);
assert!(result.is_ok());
assert_eq!(result.unwrap().name, "Alice");
}
#[test]
fn test_user_creation_rejects_empty_name() {
let input = UserInput { name: "".to_string(), age: 25 };
assert!(matches!(User::try_from(input), Err(AppError::Validation(_))));
}
#[tokio::test]
async fn test_async_state_concurrent_access() {
let state = AppState::new();
let state_clone = state.clone();
let handle = tokio::spawn(async move {
state_clone.update_user("1", User::new("Bob")).await
});
state.update_user("2", User::new("Alice")).await.unwrap();
handle.await.unwrap().unwrap();
assert!(state.get_user("1").await.is_some());
}
}
ステップ 2:テストに合格する最小限のコードを実装する
impl TryFrom<UserInput> for User {
type Error = AppError;
fn try_from(input: UserInput) -> Result<Self, Self::Error> {
if input.name.is_empty() {
return Err(AppError::Validation("Name cannot be empty".into()));
}
Ok(User { name: input.name, age: input.age })
}
}
ステップ 3:リファクタリングして検証する
cargo test && cargo clippy -- -D warnings && cargo audit
5. 実装パターン
パターン 1:安全な入力検証
validator クレートと独自の正規表現パターンを使って、すべての Tauri コマンド入力を検証します。
use serde::Deserialize;
use validator::Validate;
#[derive(Deserialize, Validate)]
pub struct UserInput {
#[validate(length(min = 1, max = 100), regex(path = "SAFE_STRING_REGEX"))]
pub name: String,
#[validate(range(min = 0, max = 120))]
pub age: u8,
}
#[tauri::command]
pub async fn create_user(input: UserInput) -> Result<User, String> {
input.validate().map_err(|e| format!("Validation error: {}", e))?;
Ok(User::new(input))
}
正規表現の定義を含む、完全な検証パターンについては
references/advanced-patterns.mdを参照してください
パターン 2:安全なエラー処理
thiserror を使って、内部情報を漏らさず安全にシリアライズできる構造化エラーを定義します。
use thiserror::Error;
#[derive(Error, Debug)]
pub enum AppError {
#[error("Database error")]
Database(#[from] sqlx::Error),
#[error("Validation failed: {0}")]
Validation(String),
#[error("Not found")]
NotFound,
}
impl serde::Serialize for AppError {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where S: serde::Serializer {
serializer.serialize_str(&self.to_string()) // Never expose internals
}
}
パターン 3:安全なファイル操作
パスを正規化し、包含関係を検証することで、パストラバーサルを防ぎます。
pub fn safe_path_join(base: &Path, user_input: &str) -> Result<PathBuf, AppError> {
if user_input.contains("..") || user_input.contains("~") {
return Err(AppError::Validation("Invalid path characters".into()));
}
let canonical = dunce::canonicalize(base.join(user_input))
.map_err(|_| AppError::NotFound)?;
let base_canonical = dunce::canonicalize(base)
.map_err(|_| AppError::Internal(anyhow::anyhow!("Invalid base")))?;
if !canonical.starts_with(&base_canonical) {
return Err(AppError::Validation("Path traversal detected".into()));
}
Ok(canonical)
}
パターン 4:安全なコマンド実行
許可リストを使用し、シェルの実行を避けて CVE-2024-24576 のリスクを軽減します。
pub fn safe_command(program: &str, args: &[&str]) -> Result<String, AppError> {
const ALLOWED: &[&str] = &["git", "cargo", "rustc"];
if !ALLOWED.contains(&program) {
return Err(AppError::Validation("Program not allowed".into()));
}
let output = Command::new(program).args(args).output()
.map_err(|e| AppError::Internal(e.into()))?;
if output.status.success() {
String::from_utf8(output.stdout).map_err(|e| AppError::Internal(e.into()))
} else {
Err(AppError::Internal(anyhow::anyhow!("Command failed")))
}
}
パターン 5:安全な非同期状態管理
Tauri アプリケーションでスレッドセーフな共有状態を管理するには、Arc<RwLock> を使用します。
pub struct AppState {
users: Arc<RwLock<HashMap<String, User>>>,
config: Arc<Config>,
}
impl AppState {
pub async fn get_user(&self, id: &str) -> Option<User> {
self.users.read().await.get(id).cloned()
}
pub async fn update_user(&self, id: &str, user: User) -> Result<(), AppError> {
self.users.write().await.insert(id.to_string(), user);
Ok(())
}
}
高度な状態管理パターンと Tauri の統合については
references/advanced-patterns.mdを参照してください
6. セキュリティ標準
5.1 重大な CVE
| CVE ID | 重大度 | 説明 | 緩和策 |
|---|---|---|---|
| CVE-2024-24576 | CRITICAL | バッチファイル経由のコマンドインジェクション(Windows) | Rust 1.77.2+、シェルを使用しない |
| CVE-2024-43402 | HIGH | 上記の修正が不完全 | Rust 1.81.0+ |
| CVE-2021-28032 | HIGH | unsafe 内の複数の可変参照 | unsafe ブロックを監査する |
CVE の詳細と緩和策のコードについては、
references/security-examples.mdを参照してください
5.2 OWASP Top 10 との対応
| カテゴリ | リスク | 主な緩和策 |
|---|---|---|
| A01 アクセス制御の不備 | MEDIUM | Tauri コマンドで権限を検証する |
| A03 インジェクション | HIGH | シェルを使わないコマンド、パラメーター化クエリ |
| A04 安全でない設計 | MEDIUM | 型システムで不変条件を強制する |
| A06 脆弱なコンポーネント | HIGH | cargo-audit を定期的に実行する |
5.3 入力検証戦略
4 層アプローチ:型システムの newtype -> スキーマ検証(serde/validator) -> ビジネスロジック -> 出力エンコーディング
pub struct Email(String); // Newtype for validated input
impl Email {
pub fn new(s: &str) -> Result<Self, ValidationError> {
if validator::validate_email(s) { Ok(Self(s.to_string())) }
else { Err(ValidationError::InvalidEmail) }
}
}
5.4 シークレット管理
// Load from environment or tauri-plugin-store with encryption
fn get_api_key() -> Result<String, AppError> {
std::env::var("API_KEY")
.map_err(|_| AppError::Configuration("API_KEY not set".into()))
}
安全な保存パターンについては、
references/security-examples.mdを参照してください
7. パフォーマンスパターン
パターン 1:ゼロコピー操作
悪い例:data.to_vec() の後に反復処理 - 良い例:ライフタイム付きのイテレーターを返す
// Bad: fn process(data: &[u8]) -> Vec<u8> { data.to_vec().iter().map(|b| b+1).collect() }
fn process(data: &[u8]) -> impl Iterator<Item = u8> + '_ {
data.iter().map(|b| b + 1) // No allocation
}
パターン 2:ループよりイテレーターチェーン
悪い例:push を使った手動ループ - 良い例:イテレーターチェーン(遅延評価、融合)
fn filter_transform(items: &[Item]) -> Vec<String> {
items.iter().filter(|i| i.is_valid()).map(|i| i.name.to_uppercase()).collect()
}
パターン 3:頻繁な割り当てに対するメモリプーリング
悪い例:ホットパス内での Vec::with_capacity() - 良い例:オブジェクトプール
static BUFFER_POOL: Lazy<Pool<Vec<u8>>> = Lazy::new(|| Pool::new(32, || Vec::with_capacity(1024)));
async fn handle_request(data: &[u8]) -> Vec<u8> {
let mut buffer = BUFFER_POOL.pull(|| Vec::with_capacity(1024));
buffer.clear(); process(&mut buffer, data); buffer.to_vec()
}
パターン 4:非同期ランタイムの選択
悪い例:非同期処理での CPU 処理 - 良い例:CPU バウンド処理には spawn_blocking
async fn hash_password(password: String) -> Result<String, AppError> {
tokio::task::spawn_blocking(move || {
argon2::hash_encoded(password.as_bytes(), &salt, &config)
.map_err(|e| AppError::Internal(e.into()))
}).await?
}
パターン 5:ホットパスでの割り当てを避ける
悪い例:println! は割り当てを行う - 良い例:事前割り当て済みバッファーへの write!
fn log_metric(buffer: &mut Vec<u8>, name: &str, value: u64) {
buffer.clear();
write!(buffer, "{}: {}", name, value).unwrap();
std::io::stdout().write_all(buffer).unwrap();
}
8. テストと検証
セキュリティテストコマンド
cargo audit # Dependency vulnerabilities
cargo +nightly careful test # Memory safety checking
cargo clippy -- -D warnings # Lint with security warnings
単体テストのパターン
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_path_traversal_blocked() {
let base = Path::new("/app/data");
assert!(safe_path_join(base, "../etc/passwd").is_err());
assert!(safe_path_join(base, "user/file.txt").is_ok());
}
#[test]
fn test_command_allowlist() {
assert!(safe_command("rm", &["-rf", "/"]).is_err());
assert!(safe_command("git", &["status"]).is_ok());
}
}
ファジングと統合テストのパターンについては、
references/advanced-patterns.mdを参照してください
9. よくある間違いとアンチパターン
| アンチパターン | 問題 | 解決策 |
|---|---|---|
本番環境での .unwrap() | パニックによりアプリがクラッシュする | Result で ? を使う |
| ドキュメントのない unsafe | 不変条件が検証されていない | // SAFETY: コメントを追加する |
| シェルコマンドの実行 | インジェクションの脆弱性 | Command::new() を直接使う |
| Clippy を無視する | セキュリティ関連の lint を見逃す | cargo clippy -- -D warnings を実行する |
| 認証情報のハードコード | コード内のシークレット | 環境変数または安全なストレージを使う |
// NEVER: Shell injection
Command::new("sh").arg("-c").arg(format!("echo {}", user_input));
// ALWAYS: Direct execution
Command::new("echo").arg(user_input);
10. 実装前チェックリスト
フェーズ 1:コードを書く前
- 期待される動作を定義する失敗するテストを書く
- 機能領域に関連する CVE を確認する
- セキュリティ境界(FFI、IPC、ファイルシステム)を特定する
- Result 型を使ったエラー処理戦略を計画する
-
cargo auditで依存関係を確認する
フェーズ 2:実装中
- 重要な変更のたびにテストを実行する
- すべての unsafe ブロックに
// SAFETY:コメントを付けて説明する - すべての境界(Tauri コマンド、FFI)で入力を検証する
- 型システムを使って不変条件を強制する(newtype)
- パフォーマンスパターンを適用する(ゼロコピー、イテレーター)
- エラーメッセージから内部の詳細が漏れないようにする
フェーズ 3:コミット前
-
cargo test- すべてのテストが成功する -
cargo clippy -- -D warnings- 警告がない -
cargo audit- HIGH/CRITICAL の脆弱性がゼロ - ハードコードされたシークレットがない("password"、"secret"、"key" を grep で検索する)
- パス操作で正規化と包含チェックを行う
- コマンド実行では許可リストを使い、シェルを使わない
- 適切なシャットダウンのためにパニックハンドラーを設定する
- ロギングを設定する(ログにシークレットを含めない)
11. まとめ
目標は、次のような Rust コードを作成することです。
- メモリーセーフ:借用チェッカーを活用し、
unsafeを最小限に抑えます - 型安全:型システムを使って不正な状態を防ぎます
- 高パフォーマンス:ゼロコスト抽象化を活用し、最適化の前にプロファイリングを行います
- 安全:境界で検証し、エラーを安全に処理します
重要なセキュリティ上の注意:
- コマンドインジェクションに関する CVE を修正するため、Rust 1.81.0 以降にアップグレードしてください
- CI/CD パイプラインで cargo-audit を実行してください
- すべての
unsafeブロックについて、SAFETY 不変条件を文書化してください - ユーザー入力を使ったシェル実行は絶対に行わないでください
- すべてのファイルパスを正規化し、検証してください
詳細な例と高度なパターンについては、
references/ディレクトリを参照してください