보안 모델
Elftia는 Electron 프로세스 격리부터 AI 도구 호출 검토까지 전체 체인을 아우르는 다중 계층 심층 방어(defense-in-depth) 보안 아키텍처를 구현합니다.
전체 보안 파이프라인
외부 메시지(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에 직접 접근하지 못하도록 보장합니다:
{/* Security configuration when creating BrowserWindow */}
const mainWindow = new BrowserWindow({
webPreferences: {
contextIsolation: true,
nodeIntegration: false,
sandbox: true,
preload: preloadPath,
},
});
contextBridge
Preload 스크립트는 contextBridge.exposeInMainWorld()를 통해 허용 목록에 있는 API만 노출합니다:
{/* preload/index.ts -- safely expose IPC interface */}
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 오버헤드 없음.
차단 규칙
{/* Path rule structure */}
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 | 쓰기만 |
도구 경로 추출
도구 호출 파라미터에서 파일 경로를 자동으로 추출하여 검사합니다:
{/* Tool-to-path-parameter mapping */}
const FILE_TOOL_PATH_PARAMS: Record<string, string[]> = {
Read: ['path', 'file_path'],
Write: ['path', 'file_path'],
Edit: ['path', 'file_path'],
ListDir: ['path', 'dir_path'],
};
{/* Firewall check interface */}
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) - Shell로의 파이핑 (
curl | sh,wget | bash) - 자격 증명/키 접근
캐싱
SHA-256 해시를 사용하여 이미 검토된 도구 호출을 캐싱함으로써 중복 LLM 호출을 방지합니다:
{/* GuardianAgent cache key generation */}
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 core interface */}
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
메시지에서 위험한 문자를 제거하는 정규식 기반 소독기:
{/* Simplified InputSanitizer */}
class InputSanitizer {
sanitize(input: string): string;
updateConfig(config: SanitizationConfig): void;
}
제거되는 문자:
- 영폭 문자 (ZWS, ZWNJ, ZWJ, ZWSP)
- Unicode 양방향 제어 문자
- 보이지 않는 서식 문자
- 제어 문자 (개행 및 탭은 보존)
RateLimiter
사용자별 및 전역 제한을 지원하는 슬라이딩 윈도우 속도 제한기:
{/* Rate limiter configuration */}
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 | 없음 | 차단된 사용자 |
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 | 사용자 차단됨 | warning |
{/* Audit log entry */}
interface AuditLogEntry {
id: string;
timestamp: number;
eventType: AuditEventType;
severity: 'info' | 'warning' | 'critical';
channelId?: string;
userId?: string;
toolName?: string;
details: Record<string, unknown>;
}
SecurityService / CryptoService
로컬에 저장된 민감한 데이터를 보호하는 애플리케이션 수준 암호화 서비스:
{/* Encryption scheme */}
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 작업 |