メインコンテンツまでスキップ

安全模型

Elftia 实现了多层纵深防御的安全架构,覆盖从 Electron 进程隔离到 AI 工具调用审查的完整链路。

安全管线全景

外部消息(Channel 渠道)从输入到执行的完整安全管线:

flowchart TB
Input[Channel 消息输入] --> Sanitize[InputSanitizer<br/>清洗危险字符]
Sanitize --> Rate{RateLimiter<br/>速率检查}
Rate -->|超限| Reject1[拒绝: 429]
Rate -->|通过| Permission{UserPermissionService<br/>用户权限}
Permission -->|blocked| Reject2[拒绝: 403]
Permission -->|guest/moderator/admin| PG{PromptGuardian<br/>提示注入检测}
PG -->|block 模式 + 注入| Reject3[拒绝 + 偏转消息]
PG -->|monitor 模式| Log1[记录日志]
PG -->|通过| Magi[MagiService<br/>Agent 处理]

Magi --> ToolCall[工具调用请求]
ToolCall --> FW{ExecutionFirewall<br/>路径 deny list}
FW -->|拒绝| Block1[拒绝: 路径被阻止]
FW -->|通过| GA{GuardianAgent<br/>AI 安全审查}
GA -->|critical/high| Gate{ChannelPermissionGate<br/>人工确认}
GA -->|low/none| Exec[执行工具]
GA -->|monitor| LogExec[记录 + 执行]
Gate -->|批准| Exec
Gate -->|拒绝| Block2[拒绝: 用户否决]
Exec --> Audit[AuditLogger<br/>审计记录]
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 认证 Token

每次 IPC 调用都携带认证 token,主进程通过 secureHandle 验证:

sequenceDiagram
participant R as Renderer
participant P as Preload
participant M as Main (secureHandle)

Note over R,M: 应用启动
M->>P: ipcRenderer.sendSync('auth:bootstrap') 返回 token
P->>P: 保存 token 到内存

Note over R,M: IPC 调用
R->>P: window.api.someMethod(params)
P->>M: ipcRenderer.invoke(channel, {token, ...params})
M->>M: validateToken(token)
alt Token 无效
M-->>P: throw Error('AUTH_FAILED')
else Token 有效
M->>M: 执行 handler
M-->>P: result
end
  • Token 生成: 主进程启动时生成随机 token
  • 同步引导: Preload 通过 sendSync 在页面加载前获取 token
  • 每次验证: 所有 secureHandle 包装的 IPC 调用都验证 token

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/criticalfail-open
strict所有工具阻止 medium+fail-closed

风险等级

type RiskLevel = 'none' | 'low' | 'medium' | 'high' | 'critical';
等级含义示例
none完全安全读取工作区内文件
low轻微风险写入项目文件
medium中等风险Shell 命令修改状态、安装包
high高风险访问敏感路径、工作区外操作
critical极度危险rm -rf、数据外泄、提权

关键规则

以下操作始终被评为 highcritical

  • 工作区外的文件删除
  • 递归删除命令 (rm -rf, del /s /q, Remove-Item -Recurse)
  • 访问系统目录 (/etc, C:\Windows, C:\Users)
  • 权限提升 (sudo, runas)
  • 管道到 Shell (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

基于 LLM 的提示注入检测器,用于检测 Channel 消息中的恶意提示注入:

运行模式

模式行为
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; // 默认 20
windowMs: number; // 默认 60000 (1 分钟)
};
global: {
maxRequests: number; // 默认 100
windowMs: number; // 默认 60000
};
cleanupIntervalMs: number; // 过期条目清理周期
}
class RateLimiter {
check(userId: string): { allowed: boolean; retryAfter?: number };
updateConfig(config: Partial<RateLimitConfig>): void;
}

UserPermissionService

Channel 用户权限管理,支持自动注册和角色分配:

角色体系

角色权限说明
admin全部管理员
moderator发消息 + 触发 Agent协管员
guest发消息 (受速率限制)访客 (默认角色)
blocked被封禁用户
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 平台
participant User as 远端用户

Agent->>Gate: requestPermission(toolCall)
Gate->>Gate: 生成确认指纹 (SHA-256)
Gate->>Channel: 发送确认消息 + 指纹
Channel->>User: Agent 想执行 XX 操作,回复 YES 确认

alt 用户确认
User->>Channel: YES
Channel->>Gate: 匹配指纹
Gate-->>Agent: approved: true
else 超时 (60s)
Gate-->>Agent: approved: false, reason: timeout
else 用户拒绝
User->>Channel: NO
Gate-->>Agent: approved: false, reason: denied
end

AuditLogger

安全审计日志,记录所有安全相关事件到 SQLite audit_log 表:

事件类型

事件类型说明严重级别
tool_executed工具调用执行info
tool_blocked工具调用被阻止warning
tool_guardian_reviewGuardian 审查结果info/warning
permission_requested权限确认请求info
permission_granted权限被批准info
permission_denied权限被拒绝warning
rate_limited速率限制触发warning
injection_detected提示注入检测critical
user_blocked用户被封禁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.tsAI 工具审查
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.tsAES-256-GCM 加密
packages/desktop/app/main/services/infra/crypto/CryptoService.tsPBKDF2 密钥派生
packages/desktop/app/main/ipc/safe-handle.tsIPC 安全句柄
packages/desktop/app/preload/index.tscontextBridge 接口暴露
packages/desktop/app/shared/contracts/security-types.ts安全相关共享类型
packages/desktop/app/main/workers/db/auditLog.ts审计日志 DB 操作
packages/desktop/app/main/workers/db/channelUsers.tsChannel 用户 DB 操作