Defense-in-Depth Security
Aartiq uses a defense-in-depth model with six independent security layers: visual sandbox, syntactic firewall, human-in-the-loop authorization, directory allowlist, OS-level sandboxing, and capability-scoped execution. Source implementations: src/lib/Security.ts, src/lib/SecurityValidator.js, src/core/command-validator.js, src/core/directory-allowlist.js, src/core/sandbox-executor.js
Philosophy
We did not set out to build a fortress. We set out to build a browser that can act on your behalf without becoming a liability — so that when the model is wrong, the damage stops at a boundary it cannot cross. Every layer below is a deliberate “no”: no raw page HTML in the model's context, no unverified command, no unsandboxed fallback, no silent yes. Security here is not a toggle you flip; it is the shape of the thing. The claims on this page are stated plainly, with their limits, because a security claim you cannot disprove is not a claim — it is a wish.
6
Security Layers
5
Enforcement Layers Beyond The Firewall
600K
PBKDF2 Key-Derivation Iterations
The regex blocklist in SecurityValidator.js is documented as a fast first-pass reject layer only — not the primary defense. Primary enforcement continues through the remaining layers: the risk-tiered permission store (checkShellPermission), the capability controller's ticket-based approval (capability-controller.js), and the fail-closed OS sandbox (sandbox-executor.js). The six-layer model cited below reflects this defense-in-depth design.
Architecture
The Six Layers
Visual Sandbox
The AI perceives web pages through screenshots + OCR and a sanitized secure-DOM extractor rather than raw, unprocessed HTML. This significantly reduces DOM-based manipulation attacks, but it is a mitigation, not an absolute guarantee.
How It Works
- Primary input is the rendered page: Electron webContents.capturePage() screenshots (src/main/handlers/browser-handlers.js) and Tesseract.js OCR (src/lib/tesseract-service.js). The AI never runs inside the page's JavaScript realm.
- SecureDOMReader (src/components/ai/SecureDOMReader.ts) provides a text fallback path. It blocks script/style/iframe/object/embed/form/input/button tags and nav/footer/header/modal/overlay/ads classes before text extraction.
- PII redaction: emails, phone numbers, card numbers, bearer tokens, session IDs, and password/api-key assignments are replaced with [REDACTED] placeholders before content reaches the model.
- SecureDOMParser (src/lib/Security.ts) runs the extracted content against shell-primitive, encoding, and injection pattern groups, decodes base64/hex payloads, and rewrites dangerous matches to [BLOCKED: LAYER].
- AI Fortress masks API keys and secrets before content reaches the LLM (src/lib/Security.ts, src/components/AIChatSidebar.tsx).
- The AI context is explicitly built as read-only: the model cannot modify the DOM; interaction is limited to approved click/fill commands (FIND_AND_CLICK / CLICK_ELEMENT).
- Source files: src/lib/Security.ts, src/lib/html-sanitizer.js, src/components/ai/SecureDOMReader.ts
Benefits
- Significantly reduces prompt-injection via DOM manipulation — hidden, scripted, or style-obfuscated content is stripped before it reaches the model
- Page JavaScript cannot directly invoke the AI's execution layer (Electron context isolation + no DOM-write access); scripts are stripped from AI-visible content
- Hidden elements and blocked tags/classes never appear in AI-visible content
- Malicious scripts, event handlers (on*=), javascript:, data:, vbscript:, iframe/embed/object are removed from the AI's reading path
Syntactic Firewall
Every command is analyzed for dangerous patterns before execution.
How It Works
- Commands are scanned for destructive shell primitives and blocked commands (rm, sudo, su, passwd, chgrp, dd if=, mkfs, fork-bomb, command substitution)
- Encoded payloads and obfuscation (hex, base64, HTML entities) are decoded via extractBase64Strings and re-checked against injection patterns
- Jailbreak patterns ('ignore all previous instructions', etc.) are blocked before content reaches the model
- Network-triggering commands (curl, wget) are flagged, and the OS sandbox denies network by default
- This layer is explicitly documented as a fast first-pass reject — not sufficient on its own (SecurityValidator.js header)
- Source files: src/lib/SecurityValidator.js, src/lib/Security.ts, src/core/command-validator.js
Benefits
- Stops known attack patterns at the gate
- Prevents accidental destructive commands
- Provides logging for security audits
- Custom rules can be added by administrators
Blocked Patterns
rm -rf /Recursive delete of rootsudoBlocked command (privilege escalation)dd if=Direct disk write:(){ :|:& };:Fork bomb$( ... )Command substitution\x.. hex / chmod 777Encoded payload / permissive modecurl / wgetNetwork download (flagged; sandbox denies net)Monitored Patterns
rm File deletion (requires approval)chmod / chownPermission change (requires approval)kill / shutdown / mountProcess/system change (requires approval)Human-in-the-Loop
Critical actions require explicit human approval before execution.
How It Works
- AI generates a command; the parser assigns a risk field (default medium).
- checkShellPermission() classifies low/medium/high/critical and checks the PermissionStore allowlist; with no store it denies (fail-closed) — src/core/command-validator.js:77-133.
- Low risk: auto-runs only if autoApproveLowRisk is on (default off); otherwise a lightweight approval.
- High risk (shell/power): the desktop generates a QR encoding aartiq://approve?id=<token>&pin=<6-digit> and waits for the paired mobile to return the PIN — src/main/handlers/sync-handlers.js:40-48, src/main/handlers/utils.js:377-387.
- Alternatively, high risk uses an OS-native dialog: macOS 'Approve with Touch ID', Windows PowerShell, Linux bash — src/main/handlers/native-approval-manager.js:22-98. Biometric is gated by requireBiometricPerSession / requireBiometricEveryTime.
- The renderer only enables Approve when both mobileApproved and pinVerified are true (or the biometric dialog succeeds) — src/components/ai/ClickPermissionModal.tsx:247-302.
- Critical risk is denied at the gate; anything that does proceed goes through capability-controller single-use tickets — src/core/capability-controller.js:29-100, src/core/approval-ticket-manager.js:139-278, src/lib/approval-gate.js:53-147.
- Command only executes after explicit approval; timeouts and missing renderers resolve to deny — src/core/shell-permission-bridge.js:42-71.
- Source files: src/core/command-validator.js, src/lib/permission-store.js, src/main/handlers/sync-handlers.js, src/main/handlers/utils.js, src/main/handlers/native-approval-manager.js, src/components/ai/ClickPermissionModal.tsx, src/core/capability-controller.js
Benefits
- No automated execution of destructive commands
- QR approval ensures physical presence
- Mobile app confirms identity
- User approval required for execution
Approval Tiers
Low Risk
Auto / Shift+TabRead-only actions, navigation, volume changes. Auto-run only if autoApproveLowRisk is enabled (default false); otherwise a quick approval.
• Taking screenshots
• Navigating to URLs
• Adjusting volume
Medium Risk
Approval dialogActions that modify browser state or open apps. Shown as Allow Once / Always Allow / Deny (shell-permission-bridge.js); persist via the permission store.
• Filling forms
• Clicking buttons
• Opening applications
High Risk
QR + PIN or Touch IDShell commands and system changes. Either a QR+PIN approval from the paired mobile app, or an OS-native biometric prompt (Touch ID / Windows Hello). The QR carries a single-use token + 6-digit PIN.
• Shell command execution
• External app automation
• File modifications
Critical Risk
Never auto-approvedAlways denied at checkShellPermission (command-validator.js:87). Routed through the capability controller's ticket-based flow; tickets are single-use, input-hash-bound, 5-minute TTL.
• Destructive / irreversible operations
• Privilege escalation
Directory Allowlist
AI file access is restricted to explicitly approved directories with fine-grained read/write permissions.
How It Works
- Each directory in the allowlist specifies an access level (Read Only or Read & Write) and recursive flag (src/lib/permission-store.js)
- Path canonicalization resolves symlinks via fs.realpathSync before checking against the allowlist — the resolved path is checked, never the user-supplied string (src/core/directory-allowlist.js)
- Just-in-time permission prompts request approval before accessing new directories
- Batched multi-directory approval allows granting access to multiple paths at once
- File management operations (move, copy, open, print) are routed around the shell sandbox
- Read/write separation: a read grant must never allow deleting/overwriting — enforced in isPathAllowed() for both read and write operations
- Source files: src/core/directory-allowlist.js, src/lib/permission-store.js, src/main/handlers/permission-handlers.js
Benefits
- Scopes AI file access to an explicit allowlist — any path outside it is denied with a structured reason
- Note: the legacy default allowlist includes the user's home, Desktop, Documents, and Downloads (read-write). Remove or downgrade these in Settings for a stricter posture; the newer directory-allowlist.js default ships with only the app data directory + temp
- Symlink traversal attacks are blocked via realpath resolution
- Read-only entries never receive write access — enforced in the sandbox profile (macOS/Linux) and by isPathAllowed() on all platforms
- Audit trail of all directory access grants with timestamps (comet-audit.jsonl)
OS-Level Sandboxing
Shell commands execute inside platform-specific OS sandboxes that enforce process, filesystem, and network boundaries. Execution is FAIL-CLOSED: if the sandbox cannot be built and verified, the command is never run — there is no automatic fallback to unsandboxed execution.
How It Works
- macOS: Seatbelt (sandbox-exec) with a closed-by-default profile — (deny file-read*) and (deny file-write*) then re-allow only system paths + allowlisted directories, (deny network*), and (deny process-exec*) with allowlisted exec paths
- The Seatbelt profile is written to a temp file and validated with a pre-flight `sandbox-exec -f <profile> /usr/bin/true` run; if the profile fails to compile, the command is rejected (SANDBOX_POLICY_INVALID)
- Linux: bubblewrap (bwrap) with unshared pid/net/ipc/uts namespaces, read-only system mounts (/usr, /bin, /sbin, /lib, /lib64, /etc), private /tmp, and --unshare-net
- bubblewrap gets an extra capability pre-flight: `--version` succeeds even when user namespaces are disabled, so we run a real `--unshare-pid/net/ipc/uts /bin/true` probe and fail closed if the namespaces we require cannot be created (common in locked-down containers and some CI runners)
- Windows: Job Object containment (src/core/win-job-runner.ps1) — the target is created SUSPENDED, assigned to a Job Object, verified via IsProcessInJob, then resumed; limits (KILL_ON_JOB_CLOSE, active-process cap, job memory, die-on-unhandled-exception) are applied and verified before the target runs a single instruction
- Windows explicitly does NOT provide OS-level filesystem or network isolation in this release — the directory allowlist is enforced at the application layer (isPathAllowed), and requesting a per-process network policy fails closed (SANDBOX_UNAVAILABLE)
- All platforms: environment is sanitized — only allowlisted variables (PATH, HOME, USER, LANG, LC_ALL, TMPDIR, SHELL, TERM, etc.) pass through; API keys and tokens never reach the sandboxed process (buildSafeEnv)
- Every result carries an explicit `isolation` object ({ filesystem, network, process }) so callers cannot mistake process containment for filesystem/network isolation: macOS/Linux report all true, Windows reports {false, false, true}, and any setup failure reports all false. No single boolean 'sandboxed' is trusted on its own
- Network inside the sandbox is denied by default on macOS (deny network*) and Linux (--unshare-net). Per-domain network allowlisting is NOT supported on any platform — requesting it fails closed. Windows cannot enforce per-process network policy in this release
- Source files: src/core/sandbox-executor.js, src/core/win-job-runner.ps1, src/core/directory-allowlist.js
Benefits
- Defense in depth: even if the regex blocklist is bypassed, the OS sandbox still confines what the command can read, write, execute, and reach on the network
- On macOS/Linux the sandbox physically prevents writes outside the workspace + allowlisted write directories; on Windows this is enforced at the application layer by isPathAllowed()
- Credential leakage via ambient environment variables is prevented by the env allowlist
- Network exfiltration is blocked by default-deny networking inside the sandbox (macOS/Linux), not by firewall rules
What this does NOT guarantee
- On Windows, the Job Object confines processes only. It does NOT isolate the filesystem or network at the OS layer. The directory allowlist and network denial are enforced by application code (isPathAllowed), not by the kernel — a bug in that code, or a path you explicitly allowlist, sits outside the Job Object's reach.
- A sandbox confines what a command can do. It does not make a malicious command safe, and it does not decide what the AI asks for. Human approval is a social control, not a cryptographic one; a coerced or careless approval still executes.
- Seatbelt and bubblewrap constrain the process, not the data it is handed. If you allowlist a directory that contains secrets, the sandboxed command can read them. Allowlists are trust boundaries you draw — only as good as where you draw them.
- These guarantees apply to code executed through executeSandboxed(). The Electron main process, the renderer, native modules, and helper apps are NOT inside the sandbox. Sandboxing reduces blast radius; it is not a substitute for least-privilege OS accounts, patched dependencies, or simply not running untrusted code.
- Fail-closed means we refuse to run rather than run uncontained. It does not mean every malicious input is harmless — a command that is allowed by policy and approved by a human runs, inside the sandbox, with whatever access the policy grants.
Capability-Scoped Execution
Actions must be explicitly registered with a named handler and approval tier. Unregistered actions are rejected.
How It Works
- Each allowed action is registered with the CapabilityController
- Approval tiers: never (auto-approved), first-time-per-session, always (explicit confirm)
- Ticket-based authorization ensures single-use approval for high-risk actions
- Unregistered actions don't exist as callable surfaces — prompt injection cannot invoke them
- Source files: src/core/capability-controller.js, src/core/command-validator.js
Benefits
- Removes dangerous primitives from the attack surface entirely
- Prompt injection cannot invoke an action through the capability interface unless that action is registered and authorized
- Ticket system prevents replay attacks on approved actions
- Granular control over what the AI can and cannot do
Threat Model
Threat Scenarios
See how each security layer protects against common attack vectors.
Prompt Injection via Hidden Text
A malicious webpage hides prompt injection instructions in invisible text
Defense
Visual Sandbox prevents the AI from seeing hidden DOM elements. OCR only captures visible, rendered text.
Malicious JavaScript Redirect
A webpage uses JavaScript to redirect the AI to a phishing site
Defense
The AI only sees screenshots of the actual rendered page. JavaScript execution is blocked from the AI's perspective.
Social Engineering via Commands
An attacker tricks the AI into running 'rm -rf /'
Defense
The Syntactic Firewall blocks execution of dangerous shell patterns regardless of how the command is phrased.
Context Injection via Context Switching
A webpage contains instructions that attempt to override AI behavior
Defense
All user-provided content is filtered for injection patterns before reaching the AI context.
Unauthorized Shell Execution
AI executes a destructive shell command
Defense
Human-in-the-Loop requires explicit approval for all shell commands. High-risk commands require QR approval.
Remote Code Execution
AI is tricked into downloading and running malicious code
Defense
Shell commands requiring downloads are blocked by default. User approval ensures no unauthorized code execution.
Symlink Traversal Attack
Attacker creates a symlink in an allowed directory pointing to /etc/passwd or other sensitive files
Defense
Path canonicalization resolves all symlinks via fs.realpath() before checking against the directory allowlist. The resolved path is checked, not the user-supplied path.
Credential Leakage via Environment Variables
AI executes a command that inherits the parent process's environment with API keys and tokens
Defense
OS-level sandboxing strips all ambient environment variables. Only explicitly allowlisted variables (PATH, HOME, USER, LANG, LC_ALL, TMPDIR, SHELL, TERM, etc.) are passed to child processes; on Windows only non-credential system variables (SystemRoot, TEMP, USERPROFILE, etc.) pass through.
Network Exfiltration via Shell
AI is tricked into executing curl to upload sensitive data to an attacker's server
Defense
The sandbox denies network by default: macOS Seatbelt emits (deny network*) and Linux bubblewrap runs with --unshare-net. curl/wget downloads are additionally flagged by the command validator, and all shell execution requires human approval. Per-domain allowlisting is not supported; Windows cannot enforce per-process network policy in this release.
Unauthorized API Invocation
Prompt injection attempts to invoke an unregistered shell command or system action
Defense
Capability-Scoped Execution rejects unregistered actions entirely. If there's no registered run_shell_command action, prompt injection cannot invoke one through the capability interface unless that action is registered and authorized.
Permissions
Permission Levels
Screen Reading
Required for AI to see page content
Shell Execution
Required for terminal commands
App Launching
Required for opening applications
File System Access
Required for PDF generation and downloads
Network Access
Required for web browsing and API calls
Clipboard Access
Required for copy/paste functionality
Directory Allowlist
Controls which directories AI can access
OS-Level Sandboxing
Enforces filesystem and network boundaries
Risk Assessment
Risk Levels
Every command is classified into one of four risk tiers before it reaches the permission gate. Higher tiers require stronger, more explicit approval.
Low Risk
Auto-approvedRead-only actions and navigation
Reading tabsNavigating to URLsPerforming searchesApproval
Auto-approved based on user preferences
Medium Risk
Per-action approvalActions that modify state or affect the system
Shell commandsFile writesClipboard accessApproval
Per-action approval dialog
High Risk
Biometric confirmationDestructive or irreversible operations
rm -rfdd if=Deleting filesApproval
Biometric confirmation (Touch ID / Windows Hello), falling back to OS password prompt; QR/PIN mobile approval for remote-origin commands
Critical Risk
Always explicitRemote or privileged operations — never auto-approved
Remote shell commandsPrivilege escalation (sudo)System-level changesApproval
Always requires explicit approval; never auto-approved. Routed through the capability controller's ticket-based flow.
High-Risk Actions
QR Code Approval
Mobile App Approval
High-risk actions require physical confirmation via the Aartiq mobile app.
Action Triggered
AI attempts high-risk command
QR Displayed
Desktop shows unique QR code
Scan & Verify
Mobile app scans QR
PIN Confirmation
Enter 6-digit verification code
Command Executed
Action proceeds after approval
Security Guarantees
- QR codes are single-use only
- Each QR code is cryptographically unique
- PIN codes are generated per-session
- Mobile must be paired via secure handshake
- Failed attempts are logged with timestamps
- All approvals are logged with timestamps
Remote Access
Remote Device Security
Commands originating from a paired mobile device receive the same validation as local commands — plus additional scrutiny because the origin is remote.
Elevated Risk for Remote Origin
WiFi Sync commands from paired mobile devices pass through the exact same validation and permission checks as local commands, with one difference: the remote origin elevates the risk tier by one level.
- low → medium
- medium → high
- high → critical (never auto-approved)
- Critical-risk commands are never auto-approved, regardless of origin
High-Risk Remote Actions
Power actions and shell commands from a remote device require QR/PIN approval before execution, matching the on-device high-risk flow.
- Shutdown, restart, sleep, and lock require QR/PIN approval
- Remote shell commands are validated by SecurityValidator, routed through the capability controller, and executed via execFile (no shell interpretation)
- The MCP server binds to 127.0.0.1 only — no external network exposure
- Pairing tokens expire after 10 minutes
Encryption
E2E Encryption
AES-256-GCM
All sensitive data at rest is encrypted using AES-256-GCM with authenticated encryption and PBKDF2 key derivation.
AES-256-GCMPBKDF2-SHA256600,00012 bytesImplementation
Use Cases
Source: src/lib/crypto-utils.ts
Vault Migration
Legacy vault entries are automatically detected and re-encrypted to the modern E2EE2 format — the older formats used weaker key derivation.
LCL:LegacyPlaintext base64 — no encryption, no salt
E2EE:LegacyPBKDF2 100K iterations, no salt
E2EE2:CurrentPBKDF2 600K iterations, per-entry random salt + IV
- Atomic vault writes — backup before migration, rollback on failure
- Proactive migration re-encrypts LCL: and E2EE: entries to E2EE2: on demand
- Migration requires biometric / native verification before re-encryption begins
API Key Storage
API Key Protection
Key Redaction
API keys are automatically masked in logs and console output
Bearer|token|api[_-]?key|secret→ [REDACTED]Secure Storage
Keys stored in encrypted electron-store with OS keychain integration
Environment Isolation
Keys are never exposed to renderer process without explicit access
Auto-Masking
AI prompts are scrubbed for API keys before processing
sk-... (OpenAI)AIza... (Google)anthropic-... (Anthropic)gsk_... (Groq)Source Files
src/lib/firebaseConfigStorage.ts, src/lib/shared-keychain.js
Token Generation
Method
crypto.getRandomValues()Entropy
256-bit CSPRNGUses
Session tokens, pairing codes, QR verification
Capability Model
Capability-Scoped
Instead of trying to detect dangerous requests via regex, the system constrains what actions the AI can invoke at all — each with its own approval policy.
Register
Each allowed action is explicitly registered with a named handler and an approval tier. If an action isn't registered, it doesn't exist as a callable surface. The controller is wired into both the main process (main.js) and the command executor (command-executor.js).
Execute
Execution is gated by the controller. Unregistered actions are rejected outright. Registered actions are allowed or queued for approval based on their tier.
neverApproved automatically (read-only)first-time-per-sessionApproved once per sessionalwaysRequires explicit confirmation each timeWhy this matters
Regex-based threat detection can be bypassed — obfuscation, synonyms, and encoding all defeat pattern matching. A capability-scoped model doesn't try to detect danger in text; it removes the dangerous primitive from the attack surface entirely. If there's no registered run_shell_command action, prompt injection cannot invoke one through the capability interface unless that action is registered and authorized.
Verification
Security Test Coverage
Every layer above is backed by automated regression tests. The full suite runs in GitHub Actions CI (`.github/workflows/jest.yml`) on every push and pull request, and protects the security invariants from silent regressions.
525
Total Jest tests (514 passing, 11 platform-skipped, 0 failing)
84
OS-sandbox tests (fail-closed + adversarial)
21
Approval-ticket regression tests
6
Security layers under test
sandbox-security.test.js
macOS Seatbelt fail-closed + real OS-enforcement (read/write denied outside the allowlist, /tmp allowed, network bind denied, symlink-escape denied, child processes contained), plus Linux bubblewrap and Windows Job Object contract tests and command-tokenizer/env-sanitization checks.
windows-job-sandbox.test.js
JS contract (isolation flags, fail-closed network/allowlist policy) runs everywhere; the runtime matrix — suspended start, verified job assignment, grandchild containment, secret isolation, and KILL_ON_JOB_CLOSE — runs on Windows CI (windows-latest).
linux-bwrap-sandbox.test.js
JS contract (unshare flags, --bind vs --ro-bind, fail-closed when bwrap is missing or present-but-incapable of creating namespaces) runs everywhere; the runtime matrix (no read/write outside allowlist, private /tmp, network denied, symlink-escape denied) runs on Linux hosts where bwrap is installed.
approval-ticket-security.test.js
A dedicated regression suite for the ticket-based approval + capability-controller system. It locks in the fixes for the audit findings and fails if any invariant regresses.
Source: aartiq-browser/tests/approval-ticket-security.test.js