Skip to main content

Security Model

Elftia implements a multi-layered defense-in-depth security architecture, covering the complete chain from Electron process isolation to AI tool call review.

Full Security Pipeline

The complete security pipeline for an external message (Channel) from input to execution:

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 Security Foundations

Context Isolation

Electron's Context Isolation ensures the renderer process cannot directly access Node.js APIs:

{/* Security configuration when creating BrowserWindow */}
const mainWindow = new BrowserWindow({
webPreferences: {
contextIsolation: true,
nodeIntegration: false,
sandbox: true,
preload: preloadPath,
},
});

contextBridge

The preload script exposes only allowlisted APIs via contextBridge.exposeInMainWorld():

{/* preload/index.ts -- safely expose IPC interface */}
contextBridge.exposeInMainWorld('api', {
completion: {
chatInSession: (params) => ipcRenderer.invoke('completion:chat', params),
},
});

IPC Authentication Token

Every IPC call carries an authentication token; the main process validates it via 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
  • Token generation: Main process generates a random token at startup
  • Synchronous bootstrap: Preload retrieves the token via sendSync before the page loads
  • Per-call validation: All IPC calls wrapped with secureHandle validate the token

ExecutionFirewall

Deterministic path access control that blocks access to system directories and credential files. Zero LLM overhead.

Deny Rules

{/* Path rule structure */}
interface DeniedPathRule {
pattern: RegExp;
reason: string;
denyOps?: ('read' | 'write')[];
}
CategoryExample RulesBlocked Operations
System directoriesC:\Windows\, /etc/, /usr/, /proc/read + write
Credential files.ssh/, .aws/, .gnupg/, .envread + write
Browser dataChrome\User Data\, .mozilla/read + write
RegistrySystem32\config\SAM|SYSTEM|SOFTWAREread + write
Config files.gitconfig, .npmrc, .bashrcwrite only

Tool Path Extraction

Automatically extracts file paths from tool call parameters for checking:

{/* 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

An LLM-based tool call safety reviewer, positioned after the ExecutionFirewall and before human confirmation.

Operating Modes

ModeReview ScopeBlocking PolicyError/Timeout Behavior
offNoneNoneDoes not run
monitorSensitive toolsLog only, do not blockfail-open
guardSensitive toolsBlock high/criticalfail-open
strictAll toolsBlock medium+fail-closed

Risk Levels

type RiskLevel = 'none' | 'low' | 'medium' | 'high' | 'critical';
LevelMeaningExample
noneCompletely safeReading a file within the workspace
lowMinor riskWriting to a project file
mediumModerate riskShell commands that modify state, installing packages
highHigh riskAccessing sensitive paths, operating outside the workspace
criticalExtremely dangerousrm -rf, data exfiltration, privilege escalation

Critical Rules

The following operations are always rated high or critical:

  • File deletion outside the workspace
  • Recursive delete commands (rm -rf, del /s /q, Remove-Item -Recurse)
  • Access to system directories (/etc, C:\Windows, C:\Users)
  • Privilege escalation (sudo, runas)
  • Piping to shell (curl | sh, wget | bash)
  • Credential/key access

Caching

Uses SHA-256 hashes to cache reviewed tool calls, avoiding redundant LLM calls:

{/* GuardianAgent cache key generation */}
function cacheKey(toolName: string, toolInput: unknown): string {
return createHash('sha256')
.update(JSON.stringify({ tool: toolName, input: toolInput }))
.digest('hex');
}

Sensitive Tool List

const SENSITIVE_TOOLS = new Set(['Bash', 'Write', 'Edit', 'Agent']);

Non-sensitive tools (e.g., Read, ListDir) skip review in monitor/guard mode; they are reviewed only in strict mode.

PromptGuardian

An LLM-based prompt injection detector used to detect malicious prompt injection in Channel messages:

Operating Modes

ModeBehavior
offDisabled
monitorDetect and log, do not block
blockBlock and return a deflection message when injection is detected

Detection Mechanism

{/* PromptGuardian core interface */}
class PromptGuardian {
async review(content: string, source: MessageSource): Promise<{
allowed: boolean;
isInjection: boolean;
confidence: number;
reason: string;
cached: boolean;
}>;
}
  • SHA-256 cache for already-reviewed content
  • fail-open on timeout/error
  • Enabled only for Channel-sourced messages

InputSanitizer

A regex-based sanitizer that removes dangerous characters from messages:

{/* Simplified InputSanitizer */}
class InputSanitizer {
sanitize(input: string): string;
updateConfig(config: SanitizationConfig): void;
}

Characters removed:

  • Zero-width characters (ZWS, ZWNJ, ZWJ, ZWSP)
  • Unicode bidirectional control characters
  • Invisible formatting characters
  • Control characters (newlines and tabs are preserved)

RateLimiter

A sliding-window rate limiter supporting per-user and global limits:

{/* 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 user permission management, supporting auto-registration and role assignment:

Role System

RolePermissionsDescription
adminAllAdministrator
moderatorSend messages + trigger AgentsModerator
guestSend messages (rate-limited)Guest (default role)
blockedNoneBanned user
class UserPermissionService {
checkPermission(channelId: string, platformUserId: string): Promise<{
allowed: boolean;
role: UserRole;
user: ChannelUser;
}>;

autoRegister(channelId: string, platformUserId: string, displayName: string): Promise<ChannelUser>;
}

ChannelPermissionGate

Sensitive tool calls originating from a Channel require user confirmation:

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

Security audit log that records all security-related events to the SQLite audit_log table:

Event Types

Event TypeDescriptionSeverity
tool_executedTool call executedinfo
tool_blockedTool call blockedwarning
tool_guardian_reviewGuardian review resultinfo/warning
permission_requestedPermission confirmation requestedinfo
permission_grantedPermission approvedinfo
permission_deniedPermission deniedwarning
rate_limitedRate limit triggeredwarning
injection_detectedPrompt injection detectedcritical
user_blockedUser bannedwarning
{/* 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

Application-level encryption services that protect sensitive data stored locally:

{/* 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)
}

All API keys are encrypted via SecurityService.encrypt() before being stored in SQLite.

FileDescription
packages/desktop/app/main/services/platform/security/ExecutionFirewall.tsPath firewall
packages/desktop/app/main/services/platform/security/GuardianAgent.tsAI tool review
packages/desktop/app/main/services/platform/security/PromptGuardian.tsPrompt injection detection
packages/desktop/app/main/services/platform/security/RateLimiter.tsRate limiter
packages/desktop/app/main/services/platform/security/InputSanitizer.tsInput sanitizer
packages/desktop/app/main/services/platform/security/UserPermissionService.tsUser permissions
packages/desktop/app/main/services/platform/security/ChannelPermissionGate.tsPermission confirmation gate
packages/desktop/app/main/services/platform/security/AuditLogger.tsAudit log
packages/desktop/app/main/services/platform/security/SecurityService.tsAES-256-GCM encryption
packages/desktop/app/main/services/infra/crypto/CryptoService.tsPBKDF2 key derivation
packages/desktop/app/main/ipc/safe-handle.tsIPC secure handle
packages/desktop/app/preload/index.tscontextBridge interface exposure
packages/desktop/app/shared/contracts/security-types.tsSecurity-related shared types
packages/desktop/app/main/workers/db/auditLog.tsAudit log DB operations
packages/desktop/app/main/workers/db/channelUsers.tsChannel user DB operations