セキュリティモデル
Elftia は多層の縦深防御(Defense-in-Depth)セキュリティアーキテクチャを実装しており、Electron のプロセス分離から AI ツール呼び出し審査まで、完全なチェーンをカバーします。
セキュリティパイプライン全体像
外部メッセージ(Channel)が入力から実行に至るまでの完全なセキュリティパイプライン:
flowchart TB
Input[Channel message input] --> Sanitize[InputSanitizer<br/>strip dangerous characters]
Sanitize --> Rate{RateLimiter<br/>rate check}
Rate -->|over limit| Reject1[Reject: 429]
Rate -->|pass| Permission{UserPermissionService<br/>user permissions}
Permission -->|blocked| Reject2[Reject: 403]
Permission -->|guest/moderator/admin| PG{PromptGuardian<br/>prompt injection detection}
PG -->|block mode + injection| Reject3[Reject + deflect message]
PG -->|monitor mode| Log1[Log event]
PG -->|pass| Magi[MagiService<br/>Agent processing]
Magi --> ToolCall[Tool call request]
ToolCall --> FW{ExecutionFirewall<br/>path deny list}
FW -->|deny| Block1[Deny: path blocked]
FW -->|pass| GA{GuardianAgent<br/>AI safety review}
GA -->|critical/high| Gate{ChannelPermissionGate<br/>human confirmation}
GA -->|low/none| Exec[Execute tool]
GA -->|monitor| LogExec[Log + execute]
Gate -->|approved| Exec
Gate -->|denied| Block2[Deny: user vetoed]
Exec --> Audit[AuditLogger<br/>audit record]
LogExec --> Exec
Log1 --> Magi
Electron セキュリティの基盤
Context Isolation
Electron の Context Isolation により、レンダラープロセスは Node.js API に直接アクセスできません:
{/* BrowserWindow 作成時のセキュリティ設定 */}
const mainWindow = new BrowserWindow({
webPreferences: {
contextIsolation: true,
nodeIntegration: false,
sandbox: true,
preload: preloadPath,
},
});
contextBridge
Preload スクリプトは contextBridge.exposeInMainWorld() を通じて、許可リストに登録された API のみを公開します:
{/* preload/index.ts -- IPC インターフェースを安全に公開 */}
contextBridge.exposeInMainWorld('api', {
completion: {
chatInSession: (params) => ipcRenderer.invoke('completion:chat', params),
},
});
IPC 認証トークン
すべての IPC 呼び出しには認証トークンが含まれており、メインプロセスが secureHandle を通じて検証します:
sequenceDiagram
participant R as Renderer
participant P as Preload
participant M as Main (secureHandle)
Note over R,M: App startup
M->>P: ipcRenderer.sendSync('auth:bootstrap') returns token
P->>P: Save token to memory
Note over R,M: IPC call
R->>P: window.api.someMethod(params)
P->>M: ipcRenderer.invoke(channel, {token, ...params})
M->>M: validateToken(token)
alt Token invalid
M-->>P: throw Error('AUTH_FAILED')
else Token valid
M->>M: execute handler
M-->>P: result
end
- トークン生成: メインプロセスが起動時にランダムトークンを生成
- 同期ブートストラップ: Preload がページ読み込み前に
sendSyncでトークンを取得 - 呼び出しごとの検証:
secureHandleでラップされたすべての IPC 呼び出しがトークンを検証
ExecutionFirewall
システムディレクトリや認証情報ファイルへのアクセスをブロックする確定的なパスアクセス制御。LLM のオーバーヘッドはゼロです。
拒否ルール
{/* パスルール構造 */}
interface DeniedPathRule {
pattern: RegExp;
reason: string;
denyOps?: ('read' | 'write')[];
}
| カテゴリ | ルール例 | ブロック対象操作 |
|---|---|---|
| システムディレクトリ | C:\Windows\, /etc/, /usr/, /proc/ | 読み取り + 書き込み |
| 認証情報ファイル | .ssh/, .aws/, .gnupg/, .env | 読み取り + 書き込み |
| ブラウザデータ | Chrome\User Data\, .mozilla/ | 読み取り + 書き込み |
| レジストリ | System32\config\SAM|SYSTEM|SOFTWARE | 読み取り + 書き込み |
| 設定ファイル | .gitconfig, .npmrc, .bashrc | 書き込みのみ |
ツールパス抽出
ツール呼び出しパラメータからファイルパスを自動的に抽出してチェックします:
{/* ツールからパスパラメータへのマッピング */}
const FILE_TOOL_PATH_PARAMS: Record<string, string[]> = {
Read: ['path', 'file_path'],
Write: ['path', 'file_path'],
Edit: ['path', 'file_path'],
ListDir: ['path', 'dir_path'],
};
{/* ファイアウォールチェックインターフェース */}
class ExecutionFirewall {
constructor(workspacePath: string);
checkPath(filePath: string, operation: 'read' | 'write'): FirewallCheckResult;
checkToolCall(toolName: string, toolInput: Record<string, unknown>): FirewallCheckResult;
}
interface FirewallCheckResult {
allowed: boolean;
deniedBy?: string;
reason?: string;
}
GuardianAgent
LLM ベースのツール呼び出し安全審査機能で、ExecutionFirewall の後、人間による確認の前に配置されます。
動作モード
| モード | 審査対象 | ブロックポリシー | エラー/タイムアウト時の動作 |
|---|---|---|---|
off | なし | なし | 実行しない |
monitor | 機密ツール | ログのみ、ブロックしない | fail-open |
guard | 機密ツール | high/critical をブロック | fail-open |
strict | すべてのツール | medium 以上をブロック | fail-closed |
リスクレベル
type RiskLevel = 'none' | 'low' | 'medium' | 'high' | 'critical';
| レベル | 意味 | 例 |
|---|---|---|
none | 完全に安全 | ワークスペース内のファイルを読み取る |
low | 軽微なリスク | プロジェクトファイルへの書き込み |
medium | 中程度のリスク | 状態を変更する Shell コマンド、パッケージのインストール |
high | 高リスク | 機密パスへのアクセス、ワークスペース外での操作 |
critical | 極めて危険 | rm -rf、データ流出、権限昇格 |
重要ルール
以下の操作は常に high または critical と評価されます:
- ワークスペース外のファイル削除
- 再帰的削除コマンド(
rm -rf、del /s /q、Remove-Item -Recurse) - システムディレクトリへのアクセス(
/etc、C:\Windows、C:\Users) - 権限昇格(
sudo、runas) - シェルへのパイプ(
curl | sh、wget | bash) - 認証情報/キーへのアクセス
キャッシュ
SHA-256 ハッシュを使用して審査済みのツール呼び出しをキャッシュし、重複する LLM 呼び出しを回避します:
{/* GuardianAgent キャッシュキー生成 */}
function cacheKey(toolName: string, toolInput: unknown): string {
return createHash('sha256')
.update(JSON.stringify({ tool: toolName, input: toolInput }))
.digest('hex');
}
機密ツールリスト
const SENSITIVE_TOOLS = new Set(['Bash', 'Write', 'Edit', 'Agent']);
非機密ツール(例:Read、ListDir)は monitor/guard モードでは審査をスキップし、strict モードでのみ審査されます。
PromptGuardian
Channel メッセージ内の悪意あるプロンプトインジェクションを検出するための LLM ベースのプロンプトインジェクション検出機能:
動作モード
| モード | 動作 |
|---|---|
off | 無効 |
monitor | 検出してログに記録、ブロックしない |
block | インジェクション検出時にブロックし、偏向メッセージを返す |
検出メカニズム
{/* PromptGuardian コアインターフェース */}
class PromptGuardian {
async review(content: string, source: MessageSource): Promise<{
allowed: boolean;
isInjection: boolean;
confidence: number;
reason: string;
cached: boolean;
}>;
}
- 審査済みコンテンツの SHA-256 キャッシュ
- タイムアウト/エラー時は fail-open
- Channel 由来のメッセージにのみ有効
InputSanitizer
メッセージから危険な文字を除去する正規表現ベースのサニタイザー:
{/* 簡略版 InputSanitizer */}
class InputSanitizer {
sanitize(input: string): string;
updateConfig(config: SanitizationConfig): void;
}
除去される文字:
- ゼロ幅文字(ZWS、ZWNJ、ZWJ、ZWSP)
- Unicode 双方向制御文字
- 不可視フォーマット文字
- 制御文字(改行とタブは保持)
RateLimiter
ユーザーごとおよびグローバルな制限をサポートするスライディングウィンドウ型レートリミッター:
{/* レートリミッター設定 */}
interface RateLimitConfig {
perUser: {
maxRequests: number; // default 20
windowMs: number; // default 60000 (1 minute)
};
global: {
maxRequests: number; // default 100
windowMs: number; // default 60000
};
cleanupIntervalMs: number; // expired entry cleanup interval
}
class RateLimiter {
check(userId: string): { allowed: boolean; retryAfter?: number };
updateConfig(config: Partial<RateLimitConfig>): void;
}
UserPermissionService
Channel ユーザーの権限管理。自動登録とロール割り当てをサポートします:
ロールシステム
| ロール | 権限 | 説明 |
|---|---|---|
admin | すべて | 管理者 |
moderator | メッセージ送信 + Agent のトリガー | モデレーター |
guest | メッセージ送信(レート制限あり) | ゲスト(デフォルトロール) |
blocked | なし | BANされたユーザー |
class UserPermissionService {
checkPermission(channelId: string, platformUserId: string): Promise<{
allowed: boolean;
role: UserRole;
user: ChannelUser;
}>;
autoRegister(channelId: string, platformUserId: string, displayName: string): Promise<ChannelUser>;
}
ChannelPermissionGate
Channel から発生する機密ツール呼び出しにはユーザーによる確認が必要です:
sequenceDiagram
participant Agent as TinyElf Agent
participant Gate as PermissionGate
participant Channel as Channel Platform
participant User as Remote User
Agent->>Gate: requestPermission(toolCall)
Gate->>Gate: Generate confirmation fingerprint (SHA-256)
Gate->>Channel: Send confirmation message + fingerprint
Channel->>User: Agent wants to perform operation XX — reply YES to confirm
alt User confirms
User->>Channel: YES
Channel->>Gate: Match fingerprint
Gate-->>Agent: approved: true
else Timeout (60s)
Gate-->>Agent: approved: false, reason: timeout
else User denies
User->>Channel: NO
Gate-->>Agent: approved: false, reason: denied
end
AuditLogger
SQLite の audit_log テーブルにセキュリティ関連のすべてのイベントを記録するセキュリティ監査ログ:
イベントタイプ
| イベントタイプ | 説明 | 重大度 |
|---|---|---|
tool_executed | ツール呼び出しが実行された | info |
tool_blocked | ツール呼び出しがブロックされた | warning |
tool_guardian_review | Guardian 審査の結果 | info/warning |
permission_requested | 権限確認がリクエストされた | info |
permission_granted | 権限が承認された | info |
permission_denied | 権限が拒否された | warning |
rate_limited | レート制限が発動した | warning |
injection_detected | プロンプトインジェクションが検出された | critical |
user_blocked | ユーザーが BAN された | warning |
{/* 監査ログエントリ */}
interface AuditLogEntry {
id: string;
timestamp: number;
eventType: AuditEventType;
severity: 'info' | 'warning' | 'critical';
channelId?: string;
userId?: string;
toolName?: string;
details: Record<string, unknown>;
}
SecurityService / CryptoService
ローカルに保存された機密データを保護するアプリケーションレベルの暗号化サービス:
{/* 暗号化スキーム */}
class SecurityService {
encrypt(plaintext: string): string; // AES-256-GCM, IV + authTag + ciphertext
decrypt(ciphertext: string): string;
}
class CryptoService {
deriveKey(password: string, salt: Buffer): Buffer;
// PBKDF2, 100K iterations, SHA-512, 32 bytes (256 bits)
}
すべての API キーは SQLite に保存される前に SecurityService.encrypt() で暗号化されます。
関連ファイル
| ファイル | 説明 |
|---|---|
packages/desktop/app/main/services/platform/security/ExecutionFirewall.ts | パスファイアウォール |
packages/desktop/app/main/services/platform/security/GuardianAgent.ts | AI ツール審査 |
packages/desktop/app/main/services/platform/security/PromptGuardian.ts | プロンプトインジェクション検出 |
packages/desktop/app/main/services/platform/security/RateLimiter.ts | レートリミッター |
packages/desktop/app/main/services/platform/security/InputSanitizer.ts | 入力サニタイザー |
packages/desktop/app/main/services/platform/security/UserPermissionService.ts | ユーザー権限 |
packages/desktop/app/main/services/platform/security/ChannelPermissionGate.ts | 権限確認ゲート |
packages/desktop/app/main/services/platform/security/AuditLogger.ts | 監査ログ |
packages/desktop/app/main/services/platform/security/SecurityService.ts | AES-256-GCM 暗号化 |
packages/desktop/app/main/services/infra/crypto/CryptoService.ts | PBKDF2 鍵導出 |
packages/desktop/app/main/ipc/safe-handle.ts | IPC セキュアハンドル |
packages/desktop/app/preload/index.ts | contextBridge インターフェース公開 |
packages/desktop/app/shared/contracts/security-types.ts | セキュリティ関連の共有型 |
packages/desktop/app/main/workers/db/auditLog.ts | 監査ログ DB 操作 |
packages/desktop/app/main/workers/db/channelUsers.ts | Channel ユーザー DB 操作 |