Skip to main content

Security Layers

The TinyElf Agent engine implements a three-layer security pipeline to ensure tool calls undergo sufficient security verification before execution. The three layers execute in sequence, and rejection by any layer terminates the process.

Three-Layer Security Pipeline

graph TB
TC["Tool call request<br/>(toolName, args)"] --> L1

subgraph Layer1["Layer 1: ExecutionFirewall"]
L1["checkToolCall(name, args)"] --> L1a{"Path in deny list?"}
L1a -->|Yes| Block1["Block<br/>[firewall] Blocked"]
L1a -->|No| L1b{"Bash command contains deny path?"}
L1b -->|Yes| Block1
L1b -->|No| Pass1["Pass"]
end

Pass1 --> L2

subgraph Layer2["Layer 2: GuardianAgent"]
L2["review(name, args)"] --> L2a{"Mode = off?"}
L2a -->|Yes| Pass2a["Skip"]
L2a -->|No| L2b{"In review scope?"}
L2b -->|No| Pass2b["Skip"]
L2b -->|Yes| L2c["LLM risk assessment<br/>(15s timeout)"]
L2c --> L2d{"Risk level decision"}
L2d -->|"guard: high/critical"| Block2["Block<br/>[guardian] Blocked"]
L2d -->|"strict: medium+"| Block2
L2d -->|Allow| Pass2["Pass"]
end

Pass2 --> L3
Pass2a --> L3
Pass2b --> L3

subgraph Layer3["Layer 3: PermissionCallback"]
L3["isSensitiveTool(name)"] --> L3a{"Requires confirmation?"}
L3a -->|No| Pass3a["Auto-pass"]
L3a -->|Yes| L3b["Send confirmation request<br/>(5min timeout)"]
L3b --> L3c{"User reply"}
L3c -->|Allow| Pass3["Pass"]
L3c -->|AllowSession| Pass3s["Pass + cache"]
L3c -->|Deny/Timeout| Block3["Deny"]
end

Pass3 --> Exec["Execute tool"]
Pass3a --> Exec
Pass3s --> Exec

Layer 1: ExecutionFirewall

ExecutionFirewall is a deterministic security gateway that blocks access to sensitive system resources based on path pattern rules.

Interface

class ExecutionFirewall {
constructor(workspacePath: string);
checkPath(filePath: string, operation?: 'read' | 'write'): FirewallCheckResult;
checkCommand(command: string): FirewallCheckResult;
checkToolCall(toolName: string, params: Record<string, unknown>): FirewallCheckResult;
setWorkspacePath(newPath: string): void;
}

interface FirewallCheckResult {
allowed: boolean;
deniedBy?: string;
reason?: string;
}

Denied Path Rules

Both Read and Write Denied

Path PatternReason
C:\Windows\Windows system directory
C:\Program Files\Program installation directory
C:\ProgramData\Program data directory
C:\Recovery\Recovery partition
/etc/System configuration directory
/usr/System directory
/sbin/System binaries
/boot/Boot directory
/proc/Process pseudo-filesystem
/sys/System pseudo-filesystem
/dev/Device files
.ssh/SSH keys directory
.gnupg/GPG keys directory
.aws/AWS credentials
.azure/Azure credentials
.gcloud/GCloud credentials
.kube/configKubernetes configuration
.docker/config.jsonDocker configuration
id_rsa, id_ed25519, id_ecdsaSSH private key files
.env, .env.*Environment variable files
credentials.jsonCredentials file
service_account*.jsonService account keys
Firefox/Chrome/Edge user dataBrowser configuration files
System32\config\SAM/SYSTEM/...Windows registry

Write Denied Only (Readable)

Path PatternReason
.gitconfigGit configuration
.npmrcnpm configuration
.bashrcBash configuration
.zshrcZsh configuration
.profileShell configuration
.bash_profileBash configuration

Tool Path Parameter Mapping

ToolExtracted path parameterOperation type
Readpath, file_pathread
Writepath, file_pathwrite
Editpath, file_pathwrite
ListDirpath, dir_pathread

Command Path Extraction

checkCommand() extracts paths from shell commands and checks each one:

  • Windows absolute paths: C:\path\to\file
  • POSIX absolute paths: /path/to/file

Layer 2: GuardianAgent

GuardianAgent uses LLM to perform security risk assessment of tool calls.

Interface

class GuardianAgent {
constructor(
config: GuardianAgentConfig,
llmAdapter: TinyElfLLMAdapter,
auditLogger?: AuditLogger,
sessionId?: string,
workspacePath?: string,
);
review(toolName: string, args: Record<string, unknown>): Promise<GuardianReviewResult>;
clearCache(): void;
}

interface GuardianReviewResult {
allowed: boolean;
riskLevel: RiskLevel; // 'none' | 'low' | 'medium' | 'high' | 'critical'
reason: string;
cached: boolean;
}

Work Modes

ModeReview scopeBlock conditionTimeout/error behavior
offNoneNo blockingN/A
monitorSensitive toolsNo blocking (logging only)N/A
guardSensitive toolshigh, criticalFail open (allow)
strictAll toolsmedium, high, criticalFail closed (deny)

Sensitive Tools List

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

monitor and guard modes review only these tools. strict mode reviews all tools.

Risk Levels

LevelMeaningExample
noneCompletely safeReading files within workspace
lowLow riskWriting project files
mediumMedium riskInstalling packages, modifying state
highHigh riskOperations outside workspace, network requests, credential access
criticalCritical riskRecursive deletion, privilege escalation, data theft

Key Rules (always marked as high/critical)

  • File deletion outside workspace
  • Recursive delete commands (rm -rf, del /s /q, Remove-Item -Recurse)
  • System directory operations (/etc, C:\Windows)
  • User personal directory operations (Desktop, Documents)
  • Privilege escalation (sudo, runas)
  • Piped execution (curl | sh)
  • Security bypass (--no-verify, --force)
  • Credential access

Caching Strategy

  • Approval results cached (key by first 16 bits of sha256(toolName:args))
  • Only allow results cached (deny results re-evaluated each time)
  • Cache cleared at session end

Constants

ConstantValueDescription
GUARDIAN_TIMEOUT_MS15,000LLM assessment timeout

Layer 3: PermissionCallback

TinyElfPermissionManager

class TinyElfPermissionManager {
resolvePermission(requestId: string, result: PermissionResult): void;
cleanupSession(dbSessionId: string): void;
requestPermission(dbSessionId, sender, toolName, toolInput): Promise<PermissionResult>;
buildPermissionCallback(dbSessionId, sender, permissionMode, channelCtx, channelPermissionGate):
((toolName, toolInput) => Promise<PermissionResult>) | undefined;
}

interface PermissionResult {
behavior: 'allow' | 'deny' | 'allowSession';
message?: string;
}

Permission Mode Behavior

ModebuildPermissionCallback returns
bypassPermissionsundefined (no callback injected, all tools auto-pass)
defaultDesktop callback / Channel callback
acceptEditsSame as default, but Write/Edit return false in isSensitiveTool

Session-Level Whitelist

When user selects allowSession:

sessionAllowedTools: Map<string, Set<string>> // dbSessionId → Set<toolName>

Subsequent calls with same tool name auto-pass. Whitelist cleared in cleanupSession().

Timeout

  • Desktop mode: 5-minute timeout → auto deny
  • Channel mode: 5-minute timeout → auto deny

ChannelPermissionGate

Permission confirmation in Channel scenarios is implemented via message routing.

Confirmation Flow

sequenceDiagram
participant A as TinyElfAgentLoop
participant G as ChannelPermissionGate
participant C as Channel (Discord/Telegram)
participant U as Channel User

A->>G: requestConfirmation(channelId, chatId, senderId, toolName, desc)
G->>G: Check always-allow list
alt Already in always-allow
G-->>A: true
else Not in list
G->>C: sendMessage("Confirmation needed: Bash: npm test")
C->>U: Display confirmation message
U->>C: Reply "y" / "n" / "always"
C->>G: processReply(channelId, chatId, senderId, "y")
G-->>A: true / false
end

Fingerprint Scope

The scope of always reply is determined by buildToolFingerprint():

fingerprint = `${toolName}:${description.slice(0, 120)}`

For example, Bash:npm test and Bash:rm -rf / are different fingerprints, requiring separate authorization.

Constants

ConstantValueDescription
MAX_PENDING5Max pending confirmations per Channel
TIMEOUT_MS300,000Confirmation timeout (5 minutes)

Reply Parsing

Supported confirmation replies (case-insensitive, auto-strips Discord mentions etc.):

ReplyBehavior
y, yes, approve, ok, 确认, はいAllow
n, no, deny, cancel, 拒绝, いいえDeny
always, always allow, 始终允许, 常に許可Allow + add to always list

AuditLogger

All security events are logged to the audit_log database table.

Event Types

type AuditEventType =
| 'firewall_block'
| 'permission_denied'
| 'permission_granted'
| 'permission_timeout'
| 'rate_limited'
| 'role_changed'
| 'user_created'
| 'user_deleted'
| 'injection_detected'
| 'command_blocked'
| 'guardian_review'
| 'prompt_review';

type AuditSeverity = 'info' | 'warn' | 'critical';

Write Strategy

  • Fire-and-forget: async write, does not block main flow
  • Write failures silently handled (console.warn), does not affect tool execution

Key Files

FilePathDescription
ExecutionFirewallplatform/security/ExecutionFirewall.tsPath firewall
GuardianAgentplatform/security/GuardianAgent.tsAI security assessment
AuditLoggerplatform/security/AuditLogger.tsAudit logging
ChannelPermissionGateplatform/security/ChannelPermissionGate.tsChannel permission gate
InputSanitizerplatform/security/InputSanitizer.tsInput sanitization
PromptGuardianplatform/security/PromptGuardian.tsPrompt injection detection
RateLimiterplatform/security/RateLimiter.tsRate limiting
UserPermissionServiceplatform/security/UserPermissionService.tsUser role permissions
TinyElfPermissionsagent-core/engine/tinyelf/TinyElfPermissions.tsPermission manager

All paths relative to packages/desktop/app/main/services/.

Extension Points

  • Custom denied paths: add rules to SYSTEM_DENIED_PATHS array
  • Custom Guardian system prompt: modify SYSTEM_PROMPT constant
  • Custom audit events: add new types to AuditEventType
  • Custom Channel replies: extend regex matching in processReply
ModulePathRelationship
TinyElfAgentLoopagent-core/engine/tinyelf/TinyElfAgentLoop.tsSecurity pipeline caller
TinyElfSessionRunneragent-core/engine/tinyelf/TinyElfSessionRunner.tsFirewall/Guardian instantiation
ShellToolagent-core/engine/tinyelf/tools/ShellTool.tsBuilt-in command blacklist
TinyElfLLMAdapteragent-core/engine/tinyelf/TinyElfLLMAdapter.tsLLM used by Guardian