This document contains reverse-engineered code from Claude Desktop's Electron app, documenting how the Cowork mode (Local Agent Mode) interfaces with the `@ant/claude-swift` native addon for VM-based isolation on macOS. **Sources:** - `build-reference/app-extracted/.vite/build/index.js` (main process) - `build-reference/app-extracted/.vite/build/mainView.js` (renderer preload) --- ## Table of Contents 1. [Overview](#overview) 2. [Module Loading](#module-loading) 3. [VM Lifecycle Management](#vm-lifecycle-management) 4. [Process Management](#process-management) 5. [Stdin/Stdout Communication](#stdinstdout-communication) 6. [Path Translation & Mounting](#path-translation--mounting) 7. [IPC Interface (Renderer → Main)](#ipc-interface-renderer--main) 8. [Settings Configuration Architecture](#settings-configuration-architecture) - [Two Storage Systems](#two-storage-systems) - [User Preferences Schema](#user-preferences-schema) - [YukonSilver Config (VM Settings)](#yukonsilver-config-vm-settings) - [Enterprise Configuration Override](#enterprise-configuration-override) - [Disabling Cowork - Methods Summary](#disabling-cowork---methods-summary) 9. [Security Architecture](#security-architecture) - [Hypervisor Isolation (VZVirtualMachine)](#1-hypervisor-isolation-vzvirtualmachine) - [Bubblewrap + Seccomp Sandboxing](#2-bubblewrap--seccomp-sandboxing-inside-vm) - [Path Traversal Detection & Blocked Extensions](#3-path-traversal-detection--blocked-extensions) - [OAuth MITM Proxy](#4-oauth-mitm-proxy-for-token-approval) - [Network Egress Allow-List](#5-network-egress-allow-list-alloweddomains) 10. [Symbol Reference](#symbol-reference) 11. [Heartbeat Monitoring](#heartbeat-monitoring) 12. [VM Bundle Management](#vm-bundle-management) 13. [Knowledge Base Integration](#knowledge-base-integration) 14. [File System Watching](#file-system-watching) 15. [ClaudeVM Web IPC Interface](#claudevm-web-ipc-interface) 16. [Idle Session Notifications](#idle-session-notifications) 17. [Telemetry Events Reference](#telemetry-events-reference) 18. [Internal Feature Codenames](#internal-feature-codenames) 19. [VM Image Analysis](#vm-image-analysis) - [Download URLs](#download-urls) - [Image Structure](#image-structure) - [Base System](#base-system) - [SDK Daemon](#sdk-daemon) - [Sandbox Runtime Package](#sandbox-runtime-package) - [Seccomp Filters](#seccomp-filters) - [Installed Tools](#installed-tools) - [Implications for Linux Port](#implications-for-linux-port) --- ## Overview Claude Desktop's Cowork mode runs Claude Code CLI inside an isolated Linux VM using Apple's Virtualization Framework (VZVirtualMachine). The architecture provides: - **Hard isolation**: Code runs in a sandboxed Linux VM, not on the host - **Explicit mounting**: Only user-selected folders are accessible - **Path translation**: VM paths like `/sessions//mnt/` map to host paths - **Stdio-based IPC**: Communication via stdin/stdout streams through the native addon ``` ┌─────────────────────────────────────────────────────────────────┐ │ Electron App │ │ ┌─────────────┐ ┌──────────────────┐ ┌──────────────┐ │ │ │ Renderer │────▶│ Main Process │────▶│@ant/claude- │ │ │ │ (Web UI) │ IPC │ (Node.js) │ │swift (Swift) │ │ │ └─────────────┘ └──────────────────┘ └──────┬───────┘ │ │ │ │ └───────────────────────────────────────────────────────┼──────────┘ │ ┌─────────────▼─────────────┐ │ VZVirtualMachine │ │ ┌───────────────────┐ │ │ │ Linux VM │ │ │ │ ┌─────────────┐ │ │ │ │ │ Claude Code │ │ │ │ │ │ CLI │ │ │ │ │ └─────────────┘ │ │ │ └───────────────────┘ │ └───────────────────────────┘ ``` --- ## Module Loading The `@ant/claude-swift` module is loaded lazily with caching. Three separate cache variables exist for different components. ### Primary Module Loader ```javascript // Minified: ff, Vw, p0e, li, eXe // Refactored names shown below let swiftModuleCache = null; let swiftModulePromise = null; /** * Loads the @ant/claude-swift module with singleton caching. * Returns the cached module or initiates loading. */ async function loadSwiftModule() { return ( swiftModuleCache || swiftModulePromise || (logger.info("[SwiftVM] Loading @ant/claude-swift module..."), (swiftModulePromise = (async () => { try { return ( (swiftModuleCache = (await import("@ant/claude-swift")).default), logger.info("[SwiftVM] Module loaded successfully"), swiftModuleCache ); } catch (error) { return (logger.error("[SwiftVM] Failed to load module: %o", error), null); } })()), swiftModulePromise) ); } /** * Gets the VM interface from the loaded module. * The module exports { vm, quickAccess, notifications, desktop, api, midnightOwl } */ async function getVMInterface() { const module = await loadSwiftModule(); return (module == null ? void 0 : module.vm) ?? null; } // Synchronous cached access (returns null if not yet loaded) getVMInterface.getCached = function () { return (swiftModuleCache == null ? void 0 : swiftModuleCache.vm) ?? null; }; /** * Gets the full swift module (for non-VM features like notifications, quickAccess) */ async function getSwiftModule() { return loadSwiftModule(); } ``` ### Platform-Specific Feature Loading ```javascript // Minified: ii, Unt (notifications), Ht, tit (quick entry) let notificationsModule = null; /** * Loads swift module for notifications (macOS only) */ async function loadNotificationsModule() { if (process.platform !== "darwin") return null; try { return ((notificationsModule = (await import("@ant/claude-swift")).default), notificationsModule); } catch (error) { return ( logger.warn("Failed to load claude-swift for notifications: %o", error), null ); } } let quickEntryModule = null; /** * Initializes Quick Entry (spotlight-like input) on macOS * Sets up keyboard shortcuts, API credentials, dictation */ async function initializeQuickEntry() { if ((await electron.app.whenReady(), !!isQuickEntrySupported())) try { quickEntryModule = (await import("@ant/claude-swift")).default; setupQuickEntryShortcut(); setupDictationShortcut(); // Listen for preference changes settingsEmitter.on("quickEntryShortcut", () => { setupQuickEntryShortcut(); }); // Set up event handlers quickEntryModule.on("logAnalyticsEvent", ({ eventName, metadata }) => { trackEvent(eventName, metadata); }); quickEntryModule.on("quickEntrySubmitted", handleQuickEntrySubmit); quickEntryModule.on("navigateToChat", async (chatId) => { const { navigateToExistingChat } = await import("./navigation"); navigateToExistingChat(chatId); }); await configureAPICredentials(); await configureDictationLanguage(); } catch (error) { logger.warn("Failed to load claude-swift %o", error); } } ``` --- ## VM Lifecycle Management ### Main Startup Sequence (4 Steps) ```javascript // Minified: AXe, Mx, kg, iae, TXe, $Xe // Refactored names shown below let vmStartupPromise = null; const VM_CONNECTION_TIMEOUT_MS = 120000; // iae const DEFAULT_VM_RAM_GB = 8; // TXe const GUEST_POLL_INTERVAL_MS = 100; // $Xe /** * Main VM startup orchestration - 4 step process: * 1. Download VM bundle + SDK * 2. Load Swift VM API * 3. Start the VM * 4. Wait for guest connection * * @param options - { memoryGB?: number } * @param progressCallback - Progress reporting callback */ async function startVMInternal(options, progressCallback) { const bundlePath = getVMBundlePath(); const startTime = Date.now(); const vmInstanceId = getVMInstanceId(); logger.info(`[VM:start] Beginning startup, bundlePath=${bundlePath}`); logger.info(`[VM:start] Bundle version: ${bundleVersion}`); logger.info(`[VM:start] VM instance ID: ${vmInstanceId}`); // Step 1/4: Download bundle + SDK logger.info("[VM:start] Step 1/4: Downloading bundle + SDK..."); const step1Start = Date.now(); const [isFreshDownload, sdkResult] = await Promise.all([ downloadVMBundle(progressCallback), sdkManager.prepareForVM() ]); if (!sdkResult.ready) { throw new Error(sdkResult.error ?? "SDK preparation failed"); } logger.info(`[VM:start] Step 1/4 complete: download=${isFreshDownload}, SDK ready, took ${Date.now() - step1Start}ms`); // Step 2/4: Load Swift VM API logger.info("[VM:start] Step 2/4: Loading Swift VM API..."); const step2Start = Date.now(); const vmInterface = await getVMInterface(); if (!vmInterface) { logger.error("[VM:start] Swift VM addon not available"); trackEvent("lam_vm_startup_failed", { vm_instance_id: vmInstanceId, bundle_version: bundleVersion, duration_ms: Date.now() - startTime, error_type: "swift_addon_unavailable", is_fresh_download: isFreshDownload, failed_step: "swift_api", }); throw new Error("Swift VM addon not available"); } logger.info(`[VM:start] Step 2/4 complete: Swift API loaded, took ${Date.now() - step2Start}ms`); // Initialize callbacks await initializeVMEventCallbacks(); setupHeartbeatMonitor(); // Register shutdown handler (only once) if (!shutdownHandlerRegistered) { shutdownHandlerRegistered = true; registerShutdownHandler({ name: "cowork-vm-shutdown", fn: async () => { const shutdownStart = Date.now(); const instanceId = getVMInstanceId(); logger.info(`[VM:shutdown] App quit, stopping VM (instance: ${instanceId})...`); stopHeartbeat(); try { await vmInterface.stopVM(); logger.info(`[VM:shutdown] Completed in ${Date.now() - shutdownStart}ms`); trackEvent("lam_vm_shutdown_completed", { vm_instance_id: instanceId ?? "unknown", bundle_version: bundleVersion, duration_ms: Date.now() - shutdownStart, trigger: "app_quit", }); } catch (error) { logger.error("[VM:shutdown] Failed: %o", error); throw error; } finally { clearVMInstanceId(); } }, }); } // Step 3/4: Start VM logger.info("[VM:start] Step 3/4: Starting VM..."); const step3Start = Date.now(); const ramGB = (options == null ? void 0 : options.memoryGB) ?? DEFAULT_VM_RAM_GB; await vmInterface.startVM(bundlePath, ramGB); logger.info(`[VM:start] Step 3/4 complete: VM start called, took ${Date.now() - step3Start}ms`); setVMState(VMState.Booting); // Step 4/4: Wait for guest connection logger.info("[VM:start] Step 4/4: Waiting for guest connection..."); const step4Start = Date.now(); let pollCount = 0; let lastLogTime = Date.now(); while (Date.now() - step2Start < VM_CONNECTION_TIMEOUT_MS) { pollCount++; if (await isGuestConnected()) { const totalDuration = Date.now() - startTime; logger.info( `[VM:start] Step 4/4 complete: connected after ${pollCount} polls, ${Date.now() - step4Start}ms since boot` ); // Install SDK in guest const sdkSubpath = sdkManager.getVMStorageSubpath(); const sdkVersion = sdkManager.getRequiredVersion(); logger.info(`[VM:start] Installing SDK: subpath=${sdkSubpath}, version=${sdkVersion}`); await vmInterface.installSdk(sdkSubpath, sdkVersion); logger.info(`[VM:start] SDK installed, total startup time: ${totalDuration}ms`); clearStartupError(); setVMState(VMState.Ready); trackEvent("lam_vm_startup_completed", { vm_instance_id: vmInstanceId, bundle_version: bundleVersion, duration_ms: totalDuration, is_fresh_download: isFreshDownload, }); // Setup heartbeat restart handler startHeartbeat({ onRestart: async () => { logger.info("[VM:heartbeat] Heartbeat failure detected, restarting VM..."); try { await vmInterface.stopVM(); clearVMInstanceId(); await startVM(options); logger.info("[VM:heartbeat] VM restart completed successfully"); } catch (err) { logger.error("[VM:heartbeat] VM restart failed:", err); } }, }); return; } // Log progress every 10 seconds if (Date.now() - lastLogTime >= 10000) { const elapsed = Date.now() - step4Start; logger.info(`[VM:start] Still waiting for guest connection... ${elapsed}ms elapsed, ${pollCount} polls`); lastLogTime = Date.now(); } await new Promise((resolve) => setTimeout(resolve, GUEST_POLL_INTERVAL_MS)); } // Timeout clearStartupError(); const totalDuration = Date.now() - startTime; const waitDuration = Date.now() - step2Start; logger.error( `[VM:start] Connection timeout after ${waitDuration}ms waiting for guest (${totalDuration}ms total), ${pollCount} polls` ); const errorMessage = "VM connection timeout"; setStartupError(errorMessage); throw new Error(errorMessage); } /** * Public wrapper ensuring single concurrent startup */ async function startVM(options, progressCallback) { if (vmStartupPromise) { logger.info("[startVM] VM startup already in progress, waiting..."); return vmStartupPromise; } if (await isGuestConnected()) { logger.info("[startVM] VM already connected"); return; } return ( (vmStartupPromise = startVMInternal(options, progressCallback) .catch((error) => { setVMState(VMState.Offline); throw error; }) .finally(() => { vmStartupPromise = null; })), vmStartupPromise ); } ``` ### Guest Connection Check ```javascript // Minified: h_ /** * Checks if the VM guest is connected and ready */ async function isGuestConnected() { const vmInterface = await getVMInterface(); return vmInterface ? vmInterface.isGuestConnected() : false; } ``` --- ## Process Management ### VMProcess Class ```javascript // Minified: tXe, dc, zw, sF // Refactored names shown below const activeProcesses = new Map(); // dc let activeProcessCount = 0; // zw const vmEventEmitter = new EventEmitter(); // sF /** * Represents a process running inside the VM. * Manages stdin/stdout streams and lifecycle. */ class VMProcess extends EventEmitter { constructor(processId, processName) { super(); this.id = processId; this.name = processName; this._killed = false; this._exitCode = null; this._wasKilled = false; this._spawnConfirmed = false; this._stdinBuffer = []; this._stdin = new PassThrough(); this._stdout = new PassThrough(); this._startTime = Date.now(); activeProcesses.set(processId, this); activeProcessCount++; logger.info(`[Process:${processId}] Created, name=${processName}, total active=${activeProcessCount}`); } /** * Called when VM confirms the process has spawned. * Flushes any buffered stdin data. */ async confirmSpawn() { logger.info( `[Process:${this.id}] Spawn confirmed, flushing ${this._stdinBuffer.length} buffered stdin chunks` ); await this.flushBufferedStdin(); this._spawnConfirmed = true; } /** * Flushes buffered stdin to the VM process */ async flushBufferedStdin() { const vmInterface = await getVMInterface(); if (vmInterface) { while (this._stdinBuffer.length > 0) { const chunk = this._stdinBuffer.shift(); try { await vmInterface.writeStdin(this.id, chunk); } catch (error) { logger.error(`[Process:${this.id}] failed to flush buffered stdin: %o`, error); } } } } get stdin() { return this._stdin; } get stdout() { return this._stdout; } get killed() { return this._killed; } get exitCode() { return this._exitCode; } /** * Push data to stdout stream (called by VM event callback) */ pushStdout(data) { this._stdout.push(data); } /** * Mark process as exited with code/signal */ setExited(code, signal) { const exitCode = this._wasKilled ? 0 : code; const exitSignal = this._wasKilled ? null : signal; this._exitCode = exitCode; this._killed = exitSignal !== null; this._stdout.push(null); // End stdout stream const duration = Date.now() - this._startTime; logger.info(`[Process:${this.id}] Exited, code=${exitCode}, signal=${exitSignal}, duration=${duration}ms`); trackEvent("lam_vm_process_exited", { vm_instance_id: getVMInstanceId() ?? "unknown", exit_code: exitCode, duration_ms: duration, was_killed: this._wasKilled || exitSignal !== null, }); this.emit("exit", exitCode, exitSignal); } /** * Mark process as errored */ setError(error, eventName) { logger.error(`[Process:${this.id}] Error: ${error.message}`); trackEvent(eventName ?? "lam_vm_runtime_error", { vm_instance_id: getVMInstanceId() ?? "unknown", error_message: error.message, }); this.emit("error", error); } /** * Kill the process with optional signal */ kill(signal) { logger.info(`[CoworkVMProcess:${this.id}] kill called with signal: %s`, signal); if (this._killed || this._exitCode !== null) { return true; } this._wasKilled = true; getVMInterface().then((vmInterface) => { if (vmInterface) { vmInterface.kill(this.id, signal).catch((error) => { logger.error(`[CoworkVMProcess:${this.id}] kill failed with error: %o`, error); }); this._killed = true; } }); return true; } /** * Set up forwarding from stdin stream to VM */ setupStdinForwarding() { this._stdin.on("data", (chunk) => { const data = chunk.toString(); const preview = data.length > 100 ? data.substring(0, 100) + "..." : data; logger.verbose(`[Process:${this.id}] stdin: ${data.length} bytes: ${preview.replace(/\n/g, "\\n")}`); // Buffer stdin until spawn is confirmed if (!this._spawnConfirmed) { logger.info(`[Process:${this.id}] Buffering stdin (spawn not yet confirmed): ${data.length} bytes`); this._stdinBuffer.push(data); return; } getVMInterface().then((vmInterface) => { if (vmInterface) { vmInterface.writeStdin(this.id, data).catch((error) => { logger.error(`[Process:${this.id}] failed to write stdin: %o`, error); this.emit("error", error); }); } }); }); } /** * Clean up process from active map */ cleanup() { activeProcesses.delete(this.id); activeProcessCount--; logger.info(`[Process:${this.id}] Cleaned up, remaining active=${activeProcessCount}`); this.removeAllListeners(); } } ``` --- ## Stdin/Stdout Communication ### Event Callbacks Setup ```javascript // Minified: rXe /** * Initializes VM event callbacks for stdout, stderr, exit, error, and network status. * These callbacks route VM events to the appropriate VMProcess instances. */ async function initializeVMEventCallbacks() { logger.info("[Callbacks] Initializing VM event callbacks..."); const vmInterface = await getVMInterface(); if (!vmInterface) { logger.error("[Callbacks] Swift VM addon not available for callbacks"); return; } // Stdout and stderr use the same handler - push to process stdout const outputHandler = (processId, data) => { const process = activeProcesses.get(processId); if (process) { process.pushStdout(data); } }; vmInterface.setEventCallbacks( outputHandler, // stdout callback outputHandler, // stderr callback (merged with stdout) // Exit callback (processId, code, signal) => { const process = activeProcesses.get(processId); if (process) { process.setExited(code, signal); } }, // Error callback (processId, message, eventName) => { const process = activeProcesses.get(processId); if (process) { process.setError(new Error(message)); } }, // Network status callback (status) => { logger.info(`[VM] Network status: ${status}`); vmEventEmitter.emit("networkStatus", status); } ); // Guest connection change handler const swiftModule = await getSwiftModule(); if (swiftModule) { swiftModule.on("guestConnectionChanged", (connected) => { logger.info(`[VM] Guest connection changed: ${connected}`); vmEventEmitter.emit("guestConnectionChanged", connected); if (!connected) { const processIds = Array.from(activeProcesses.keys()); if (isGracefulQuit()) { logger.info( `[VM] Guest disconnected during graceful quit, cleanly exiting ${processIds.length} active processes` ); for (const id of processIds) { const process = activeProcesses.get(id); if (process && process.exitCode === null && !process.killed) { process.setExited(0, null); } } return; } // Unexpected disconnect - terminate all processes logger.info( `[VM] Guest disconnected unexpectedly, terminating ${processIds.length} active processes with SIGKILL` ); for (const id of processIds) { const process = activeProcesses.get(id); if (process && process.exitCode === null && !process.killed) { process.setExited(null, "SIGKILL"); } } } }); } logger.info("[Callbacks] VM event callbacks initialized"); } ``` ### Spawn Function Factory ```javascript // Minified: gXe, yXe /** * Creates a spawn function configured for a specific session. * * @param config - Session configuration with mounts, process name, etc. * @returns A spawn function that creates VMProcess instances */ function createSpawnFunction(config) { const mountCount = Object.keys(config.additionalMounts).length; const mountNames = Object.keys(config.additionalMounts).join(", "); logger.info( `[Spawn:config] Creating spawn function for process=${config.processName}, isResume=${config.isResume}, mounts=${mountCount} (${mountNames}), allowedDomains=${(config.allowedDomains?.length) ?? 0}` ); return (spawnOptions) => { const processId = crypto.randomUUID(); logger.info( `[Spawn:create] id=${processId} name=${config.processName} cmd=${spawnOptions.command} args=${spawnOptions.args.join(" ")} cwd=${spawnOptions.cwd ?? "(none)"}` ); initializeVMEventCallbacks(); const process = new VMProcess(processId, config.processName); process.setupStdinForwarding(); // Filter sensitive env vars from logging const envVars = {}; let envCount = 0; for (const [key, value] of Object.entries(spawnOptions.env)) { if (value !== undefined) { envVars[key] = value; envCount++; } } logger.verbose(`[Spawn:create] id=${processId} env vars=${envCount} (sensitive values filtered)`); // Actually spawn in VM (async) spawnInVM(process, processId, spawnOptions.command, spawnOptions.args, spawnOptions.cwd, envVars, config) .catch((error) => { logger.error(`[Spawn:create] id=${processId} Failed to spawn: %o`, error); process.emit("error", error); }); return process; }; } /** * Spawns a process in the VM via the Swift addon. */ async function spawnInVM(process, processId, command, args, cwd, envVars, config) { const vmInterface = await getVMInterface(); if (!vmInterface) { logger.error(`[Spawn:vm] id=${processId} Swift VM addon not available`); process.setError( new Error("Swift VM addon not available"), "lam_vm_process_spawn_failed" ); return; } try { // Register OAuth token for MITM proxy if present const oauthToken = envVars.CLAUDE_CODE_OAUTH_TOKEN; if (oauthToken) { await vmInterface.addApprovedOauthToken(oauthToken); logger.info(`[Spawn:vm] id=${processId} OAuth token approved with MITM proxy`); } // Call the Swift addon's spawn method await vmInterface.spawn( processId, config.processName, command, args, cwd, Object.keys(envVars).length > 0 ? envVars : undefined, config.additionalMounts, config.isResume, config.allowedDomains, config.sharedCwdPath ); await process.confirmSpawn(); logger.info(`[Spawn:vm] id=${processId} Spawn succeeded`); trackEvent("lam_vm_process_spawned", { vm_instance_id: getVMInstanceId() ?? "unknown", is_resume: config.isResume, mount_count: Object.keys(config.additionalMounts).length, }); } catch (error) { const message = error instanceof Error ? error.message : String(error); logger.error(`[Spawn:vm] id=${processId} Spawn failed: ${message}`); process.setError( error instanceof Error ? error : new Error(message), "lam_vm_process_spawn_failed" ); } } ``` --- ## Path Translation & Mounting ### VM Path to Host Path Translation ```javascript // Minified: Og, DXe /** * Translates a VM path to the corresponding host path. * * VM paths have the format: /sessions//mnt// * * @param vmPath - The path inside the VM * @param context - Session context with mount information * @returns Host path or null if translation fails */ function translateVMPathToHost(vmPath, context) { const { vmProcessName, sessionStorageDir, userSelectedFolders, sharedCwdPath, } = context; const sessionPrefix = `/sessions/${vmProcessName}/`; // Must start with session prefix if (!vmPath.startsWith(sessionPrefix)) { return null; } const relativePath = vmPath.slice(sessionPrefix.length); // Check for path traversal attempts if (isPathTraversal(relativePath)) { return null; } // Handle mount paths: /sessions//mnt// if (relativePath.startsWith("mnt/")) { const afterMnt = relativePath.slice(4); const slashIndex = afterMnt.indexOf("/"); const mountName = slashIndex === -1 ? afterMnt : afterMnt.slice(0, slashIndex); const subpath = slashIndex === -1 ? "" : afterMnt.slice(slashIndex + 1); // Special mounts if (mountName === "outputs") { return sharedCwdPath ? path.join(os.homedir(), sharedCwdPath, "outputs", subpath) : sessionStorageDir ? path.join(sessionStorageDir, "outputs", subpath) : null; } if (mountName === "uploads") { return sessionStorageDir ? path.join(sessionStorageDir, "uploads", subpath) : null; } // User-selected folder mounts for (const folder of userSelectedFolders ?? []) { if (path.basename(folder) === mountName) { return path.join(folder, subpath); } } // Knowledge base mounts if (mountName === ".knowledge" && context.knowledgeBasePaths) { const kbSlash = subpath.indexOf("/"); const kbName = kbSlash === -1 ? subpath : subpath.slice(0, kbSlash); const kbSubpath = kbSlash === -1 ? "" : subpath.slice(kbSlash + 1); const kbPath = context.knowledgeBasePaths.get(kbName); if (kbPath) { return path.join(kbPath, kbSubpath); } } return null; } // Non-mount paths (e.g., shared working directory) return sharedCwdPath ? path.join(os.homedir(), sharedCwdPath, relativePath) : null; } ``` ### Mount Directory Tool ```javascript // Minified: (part of directory tool implementation) /** * Mounts a host directory into the VM with specified permissions. * Called when user grants folder access through the UI. */ async function mountDirectory(sessionContext, hostPath) { const vmInterface = await getVMInterface(); const { vmProcessName, sessionId } = sessionContext; // Resolve to real path const realPath = await fs.realpath(hostPath); // Get relative path from home directory const relativePath = path.relative(os.homedir(), realPath); // Mount name is the folder basename const mountName = path.basename(realPath); // Mount with read-write permissions await vmInterface.mountPath(sessionId, relativePath, mountName, "rw"); const vmPath = `/sessions/${vmProcessName}/mnt/${mountName}`; // Track the folder as user-selected sessionContext.addUserSelectedFolder(sessionId, realPath); logger.info(`[CoworkDirectoryTool] Mounted directory: ${realPath} -> ${vmPath}`); return { content: [{ type: "text", text: `Successfully mounted directory. Host path: ${realPath} VM path: ${vmPath} You can now access files in this directory at ${vmPath}`, }], }; } /** * Enables file deletion for a mounted directory. * Requires explicit user permission. */ async function enableFileDeletion(sessionContext, mountInfo) { const vmInterface = await getVMInterface(); // Remount with read-write-delete permissions await vmInterface.mountPath( sessionContext.sessionId, mountInfo.subpath, mountInfo.name, "rwd" // read, write, delete ); sessionContext.setFileDeleteApprovedForMount(mountInfo.name); logger.info(`[CoworkDirectoryTool] Enabled file deletion for mount: ${mountInfo.name}`); } ``` ### Mount Path Patterns The VM uses these standard mount paths: | Mount Path | Purpose | |------------|---------| | `/sessions//mnt/` | User-selected folders | | `/sessions//mnt/outputs` | Output files directory | | `/sessions//mnt/uploads` | Uploaded files directory | | `/sessions//mnt/.projects/` | Project contexts | | `/sessions//mnt/.plugins/` | Remote plugins | | `/sessions//mnt/.local-plugins/` | Local plugins | | `/sessions//mnt/.skills` | Skill definitions | | `/sessions//mnt/.knowledge/` | Knowledge bases | | `/sessions//mnt/.claude` | Claude configuration | --- ## IPC Interface (Renderer → Main) ### LocalAgentModeSessions Object This object is exposed to the renderer process via Electron's contextBridge. Each method invokes an IPC handler in the main process. ```javascript // Minified: It (assigned to LocalAgentModeSessions) // File: mainView.js (renderer preload) const LocalAgentModeSessions = { // ───────────────────────────────────────────── // Session Lifecycle // ───────────────────────────────────────────── /** * Start a new Cowork session * @param options - Session configuration */ start(options) { return ipcRenderer.invoke( "$eipc_message$_..._$_LocalAgentModeSessions_$_start", options ); }, /** * Stop a running session * @param sessionId - Session to stop */ stop(sessionId) { return ipcRenderer.invoke( "$eipc_message$_..._$_LocalAgentModeSessions_$_stop", sessionId ); }, /** * Archive a session * @param sessionId - Session to archive * @param options - Archive options */ archive(sessionId, options) { return ipcRenderer.invoke( "$eipc_message$_..._$_LocalAgentModeSessions_$_archive", sessionId, options ); }, // ───────────────────────────────────────────── // Session Communication // ───────────────────────────────────────────── /** * Send a message to a session * @param sessionId - Target session * @param message - Message content * @param images - Attached images * @param mcpConfig - MCP server configuration * @param connectors - First-party connectors */ sendMessage(sessionId, message, images, mcpConfig, connectors) { return ipcRenderer.invoke( "$eipc_message$_..._$_LocalAgentModeSessions_$_sendMessage", sessionId, message, images, mcpConfig, connectors ); }, /** * Respond to a tool permission request * @param sessionId - Session ID * @param requestId - Permission request ID * @param approved - Whether permission was granted */ respondToToolPermission(sessionId, requestId, approved) { return ipcRenderer.invoke( "$eipc_message$_..._$_LocalAgentModeSessions_$_respondToToolPermission", sessionId, requestId, approved ); }, // ───────────────────────────────────────────── // Session Queries // ───────────────────────────────────────────── /** * Get a specific session * @param sessionId - Session ID * @param options - Query options */ getSession(sessionId, options) { return ipcRenderer.invoke( "$eipc_message$_..._$_LocalAgentModeSessions_$_getSession", sessionId, options ); }, /** * Get all sessions */ getAll() { return ipcRenderer.invoke( "$eipc_message$_..._$_LocalAgentModeSessions_$_getAll" ); }, /** * Get session transcript * @param sessionId - Session ID */ getTranscript(sessionId) { return ipcRenderer.invoke( "$eipc_message$_..._$_LocalAgentModeSessions_$_getTranscript", sessionId ); }, // ───────────────────────────────────────────── // Folder Management // ───────────────────────────────────────────── /** * Get all trusted folders */ getTrustedFolders() { return ipcRenderer.invoke( "$eipc_message$_..._$_LocalAgentModeSessions_$_getTrustedFolders" ); }, /** * Add a folder to trusted list * @param folderPath - Path to trust */ addTrustedFolder(folderPath) { return ipcRenderer.invoke( "$eipc_message$_..._$_LocalAgentModeSessions_$_addTrustedFolder", folderPath ); }, /** * Remove a folder from trusted list * @param folderPath - Path to untrust */ removeTrustedFolder(folderPath) { return ipcRenderer.invoke( "$eipc_message$_..._$_LocalAgentModeSessions_$_removeTrustedFolder", folderPath ); }, /** * Check if a folder is trusted * @param folderPath - Path to check */ isFolderTrusted(folderPath) { return ipcRenderer.invoke( "$eipc_message$_..._$_LocalAgentModeSessions_$_isFolderTrusted", folderPath ); }, /** * Set folders for draft session * @param folders - Array of folder paths */ setDraftSessionFolders(folders) { return ipcRenderer.invoke( "$eipc_message$_..._$_LocalAgentModeSessions_$_setDraftSessionFolders", folders ); }, // ───────────────────────────────────────────── // MCP Operations // ───────────────────────────────────────────── /** * Configure MCP servers for a session * @param sessionId - Session ID * @param mcpServers - Server configuration */ setMcpServers(sessionId, mcpServers) { return ipcRenderer.invoke( "$eipc_message$_..._$_LocalAgentModeSessions_$_setMcpServers", sessionId, mcpServers ); }, /** * Call an MCP tool * @param sessionId - Session ID * @param serverName - MCP server name * @param toolName - Tool to call * @param arguments - Tool arguments */ mcpCallTool(sessionId, serverName, toolName, arguments) { return ipcRenderer.invoke( "$eipc_message$_..._$_LocalAgentModeSessions_$_mcpCallTool", sessionId, serverName, toolName, arguments ); }, /** * Read an MCP resource * @param sessionId - Session ID * @param serverName - MCP server name * @param resourceUri - Resource URI */ mcpReadResource(sessionId, serverName, resourceUri) { return ipcRenderer.invoke( "$eipc_message$_..._$_LocalAgentModeSessions_$_mcpReadResource", sessionId, serverName, resourceUri ); }, /** * List MCP resources * @param sessionId - Session ID * @param serverName - MCP server name */ mcpListResources(sessionId, serverName) { return ipcRenderer.invoke( "$eipc_message$_..._$_LocalAgentModeSessions_$_mcpListResources", sessionId, serverName ); }, // ───────────────────────────────────────────── // Event Listeners // ───────────────────────────────────────────── /** * Subscribe to session events * @param callback - Event handler * @returns Unsubscribe function */ onOnEvent(callback) { const handler = (event, data) => callback(data); ipcRenderer.on( "$eipc_message$_..._$_LocalAgentModeSessions_$_onEvent", handler ); return () => { ipcRenderer.removeListener( "$eipc_message$_..._$_LocalAgentModeSessions_$_onEvent", handler ); }; }, /** * Subscribe to tool permission requests * @param callback - Permission request handler * @returns Unsubscribe function */ onOnToolPermissionRequest(callback) { const handler = (event, data) => callback(data); ipcRenderer.on( "$eipc_message$_..._$_LocalAgentModeSessions_$_onToolPermissionRequest", handler ); return () => { ipcRenderer.removeListener( "$eipc_message$_..._$_LocalAgentModeSessions_$_onToolPermissionRequest", handler ); }; }, // ───────────────────────────────────────────── // Other Operations // ───────────────────────────────────────────── getSupportedCommands(sessionId) { /* ... */ }, setFirstPartyConnectors(sessionId, connectors) { /* ... */ }, setFocusedSession(sessionId) { /* ... */ }, respondDirectoryServers(sessionId, servers) { /* ... */ }, openOutputsDir(sessionId) { /* ... */ }, shareSession(sessionId) { /* ... */ }, updateSession(sessionId, updates) { /* ... */ }, }; // Registration function - exposes to renderer function registerLocalAgentModeSessions(contextBridgeApi) { if (isContextBridgeAvailable()) { contextBridgeApi["claude.web"] = contextBridgeApi["claude.web"] || {}; contextBridgeApi["claude.web"].LocalAgentModeSessions = LocalAgentModeSessions; } } ``` --- ## Settings Configuration Architecture This section documents how Claude Desktop stores and manages settings, including how to disable Cowork features. ### Two Storage Systems Claude Desktop uses two parallel storage systems: #### 1. claude_desktop_config.json (Main Config) **Location:** `~/.config/Claude/claude_desktop_config.json` (Linux) ```javascript // Minified: KDe (schema), Xp (path), GDe (read), U0 (write) // File: index.js lines 71515-71654 const configFileSchema = { claudeAiUrl: string, // Backend URL override globalShortcut: string, // Global keyboard shortcut mcpServers: object, // MCP server configurations features: object, // Feature flags isHardwareAccelerationDisabled: boolean, isUsingBuiltInNodeForMcp: boolean, isDxtAutoUpdatesEnabled: boolean, dxtMaxTotalSizeMB: number, preferences: preferencesSchema, // User preferences (see below) }; // Path function function getConfigFilePath() { // Minified: Xp return path.join(app.getPath("userData"), "claude_desktop_config.json"); } // Read with caching function readConfigFile() { // Minified: GDe, with caching via Un const configPath = getConfigFilePath(); if (!fs.existsSync(configPath)) return {}; return JSON.parse(fs.readFileSync(configPath, "utf-8")); } // Write function async function writeConfigFile(config) { // Minified: U0 const configPath = getConfigFilePath(); await fs.writeFile(configPath, JSON.stringify(config, null, 2)); } ``` #### 2. electron-store (Runtime Data) **Location:** `~/.config/Claude/config.json` ```javascript // Minified: na (instance), z5 (wrapper class) // File: index.js lines 60177, 71333 class ElectronStoreWrapper { // Minified: z5 constructor(options) { this.store = new ElectronStore(options); } get(key) { return this.store.get(key); } set(key, value) { this.store.set(key, value); } } const electronStore = new ElectronStoreWrapper({ configFileMode: 420 }); ``` **Stored data:** Locale, theme, window state, yukon-silver-config ### User Preferences Schema ```javascript // Minified: zue (schema), JDe (defaults) // File: index.js lines 20612-20638, 71674-71690 const preferencesSchema = { menuBarEnabled: boolean, // Show menu bar (default: true) legacyQuickEntryEnabled: boolean, // Quick entry feature (default: true) chromeExtensionEnabled: boolean, // Chrome extension (default: true) quickEntryShortcut: string, // "double-tap-option" | "off" | etc. quickEntryDictationShortcut: string, // Dictation shortcut plushRaccoonEnabled: boolean, // Dev-only feature (default: false) quietPenguinEnabled: boolean, // Dev-only feature (default: false) louderPenguinEnabled: boolean, // Dev-only feature (default: false) plushRaccoonOption1: string, // Keyboard accelerator plushRaccoonOption2: string, // Keyboard accelerator plushRaccoonOption3: string, // Keyboard accelerator sparkleHedgehogAppearance: string, // UI appearance (default: "default") sparkleHedgehogScale: number, // UI scale (default: 1) chillingSlothLocation: string, // Worktrees location (default: "default") secureVmFeaturesEnabled: boolean, // COWORK/VM FEATURES (default: true) localAgentModeTrustedFolders: string[], // Trusted folders list }; const defaultPreferences = { menuBarEnabled: true, legacyQuickEntryEnabled: true, chromeExtensionEnabled: true, quickEntryShortcut: "double-tap-option", quickEntryDictationShortcut: "off", plushRaccoonEnabled: false, quietPenguinEnabled: false, louderPenguinEnabled: false, plushRaccoonOption1: "off", plushRaccoonOption2: "off", plushRaccoonOption3: "off", sparkleHedgehogAppearance: "default", sparkleHedgehogScale: 1, chillingSlothLocation: "default", secureVmFeaturesEnabled: true, // <-- Set to false to disable Cowork localAgentModeTrustedFolders: [], }; ``` ### Reading & Writing Preferences ```javascript // Minified: gi (get), Ky (set), rB (apply defaults) // File: index.js lines 71806-71829 /** * Get a preference value with defaults applied */ function getPreference(key) { // Minified: gi const prefs = getCachedConfig().preferences ?? {}; return applyDefaultPreferences(prefs)[key]; } /** * Set a preference value and persist to disk */ async function setPreference(key, value) { // Minified: Ky // Run pre-change validator if exists const validator = preferenceValidators[key]; if (validator && (await validator(value)) === false) return; // Merge with existing preferences const newPrefs = { ...(getCachedConfig().preferences ?? {}), [key]: value }; // Persist to file await setConfigValue("preferences", newPrefs); // Emit change event settingsEventEmitter.emit(key, value); // Notify renderer via IPC if (mainWindow?.webContents) { AppPreferencesDispatcher .getDispatcher(mainWindow.webContents) ?.dispatchPreferencesChanged(applyDefaultPreferences(newPrefs)); } } ``` ### YukonSilver Config (VM Settings) Controls automatic VM bundle downloading and startup. ```javascript // Minified: YKe (schema), X4 (get), Uge (set), JKe (store) // File: index.js lines 129376-129385 const yukonSilverConfigSchema = { autoDownloadInBackground: boolean, // Pre-download VM bundle (default: false) autoStartOnUserIntent: boolean, // Auto-start VM for Cowork memoryGB: number, // RAM allocation for VM }; // Storage via electron-store with key "data:yukon-silver-config" const [getYukonSilverConfig, setYukonSilverConfig, yukonSilverStore] = createConfigStore({ name: "yukon-silver-config", schema: yukonSilverConfigSchema, allowStale: true, // Cache between navigations }); // Usage check (line 173092) const shouldAutoDownload = getYukonSilverConfig()?.autoDownloadInBackground ?? false; ``` ### Enterprise Configuration Override Enterprise policies override user preferences. ```javascript // Minified: XDe (keys), eLe (macOS), rLe (Windows) // File: index.js lines 71870-72001 /** * Settings controllable via enterprise policy */ const enterpriseSettingKeys = [ "isDesktopExtensionEnabled", "isDesktopExtensionDirectoryEnabled", "isDesktopExtensionSignatureRequired", "isLocalDevMcpEnabled", "isClaudeCodeForDesktopEnabled", "secureVmFeaturesEnabled", // <-- Can disable Cowork enterprise-wide "disableAutoUpdates", "autoUpdaterEnforcementHours", "isDxtEnabled", "isDxtDirectoryEnabled", "isDxtSignatureRequired", ]; /** * Read enterprise config on macOS * Location: ~/Library/Preferences/com.anthropic.Claude.plist */ async function readMacOSEnterpriseConfig() { // Minified: eLe // Uses CFPreferences API via native addon } /** * Read enterprise config on Windows * Location: HKLM\SOFTWARE\Policies\Claude */ async function readWindowsEnterpriseConfig() { // Minified: rLe // Uses Windows Registry API } ``` ### IPC Interface for Preferences ```javascript // Minified: qfe // File: index.js lines 44884-44935, 167168-167170 const AppPreferencesDispatcher = { // IPC channel: "claude.settings_$_AppPreferences" /** * Get all preferences with defaults applied */ getPreferences() { return applyDefaultPreferences(getCachedConfig().preferences ?? {}); }, /** * Set a single preference */ async setPreference(key, value) { await setPreference(key, value); }, /** * Event: Notify renderer of preference changes */ dispatchPreferencesChanged(preferences) { // Sent from main process to renderer }, }; ``` ### Disabling Cowork - Methods Summary #### Method 1: Config File (Recommended) Edit `~/.config/Claude/claude_desktop_config.json`: ```json { "preferences": { "secureVmFeaturesEnabled": false } } ``` #### Method 2: Enterprise Policy (macOS) Edit `~/Library/Preferences/com.anthropic.Claude.plist`: ```xml secureVmFeaturesEnabled ``` #### Method 3: Enterprise Policy (Windows) Set in registry at `HKLM\SOFTWARE\Policies\Claude`: ``` secureVmFeaturesEnabled = 0 (DWORD) ``` #### Method 4: Disable Background Download via DevTools ```javascript // In DevTools console: await window.claude.ClaudeVM.setYukonSilverConfig({ autoDownloadInBackground: false, autoStartOnUserIntent: true, memoryGB: 4 }); ``` ### Settings Check Flow ```javascript // File: index.js line 156721 // How secureVmFeaturesEnabled is checked: if (getPreference("secureVmFeaturesEnabled") === false) { // Cowork features are disabled return { status: "unsupported", reason: "user_disabled" }; } // Enterprise check (takes precedence): if ((await getEnterpriseConfig()).secureVmFeaturesEnabled === false) { return { status: "unsupported", reason: "enterprise_disabled" }; } ``` --- ## Security Architecture The Cowork security model implements defense-in-depth with multiple isolation layers. This section documents each layer with actual code from the source. ### 1. Hypervisor Isolation (VZVirtualMachine) The `@ant/claude-swift` native addon interfaces with Apple's Virtualization Framework to run Claude Code inside an isolated Linux VM. ```javascript // Minified: Mx, AXe // File: index.js lines 152262-152422 /** * VM startup orchestration - boots the Linux VM via Swift addon */ async function startVM(options) { // Load the Swift VM addon const vmInterface = await getVMInterface(); if (!vmInterface) { throw new Error("Swift VM addon not available"); } // Step 3/4: Start VM via VZVirtualMachine const ramGB = options?.memoryGB ?? 8; // DEFAULT_VM_RAM_GB = 8 await vmInterface.startVM(bundlePath, ramGB); // Step 4/4: Wait for guest agent to connect const VM_CONNECTION_TIMEOUT_MS = 120000; const GUEST_POLL_INTERVAL_MS = 100; while (Date.now() - startTime < VM_CONNECTION_TIMEOUT_MS) { if (await vmInterface.isGuestConnected()) { // Install SDK in guest await vmInterface.installSdk(sdkSubpath, sdkVersion); return; } await sleep(GUEST_POLL_INTERVAL_MS); } throw new Error("VM connection timeout"); } ``` The Swift addon exposes these VM interface methods: | Method | Purpose | |--------|---------| | `startVM(bundlePath, ramGB)` | Boot the Linux VM with specified memory | | `stopVM()` | Gracefully shut down the VM | | `isGuestConnected()` | Check if guest agent is ready | | `installSdk(subpath, version)` | Install Claude Code SDK in guest | | `spawn(...)` | Create a process inside the VM | | `writeStdin(processId, data)` | Send data to process stdin | | `mountPath(sessionId, path, name, mode)` | Mount host folder into VM | | `kill(processId, signal)` | Terminate a VM process | | `addApprovedOauthToken(token)` | Register OAuth token with MITM proxy | | `setEventCallbacks(...)` | Register stdout/stderr/exit handlers | --- ### 2. Bubblewrap + Seccomp Sandboxing (Inside VM) The Linux VM uses additional sandboxing internally. Error categorization reveals the tooling: ```javascript // Minified: (inline in error categorization) // File: index.js lines 154211-154234 /** * Categorizes VM errors for reporting and retry logic. * Reveals bubblewrap and seccomp usage inside the VM. */ function categorizeVMError(errorText) { const output = extractOutput(errorText); // Seccomp filter violations if (errorText.includes("Killed") && errorText.includes("apply-seccomp")) return { category: "seccomp_killed", rawOutput: output }; // Sandbox dependency issues (reveals tooling) if (errorText.includes("Sandbox dependencies are not available") || errorText.includes("ripgrep") || errorText.includes("bubblewrap") || errorText.includes("socat")) return { category: "sandbox_deps_missing", rawOutput: output }; // Mount failures if (errorText.includes("was not found") && errorText.includes("/sessions/")) return { category: "mount_not_found", rawOutput: output }; if (errorText.includes("failed to unmount") || errorText.includes("device or resource busy")) return { category: "mount_busy", rawOutput: output }; // VM disconnection if (errorText.includes("Disconnected from guest") || errorText.includes("disconnected unexpectedly")) return { category: "vm_disconnected", rawOutput: output }; // ... additional categories } ``` The sandbox stack inside the VM: - **Bubblewrap (bwrap)** - Linux namespace isolation - **Seccomp filters** - Syscall allowlisting - **Socat** - Socket relay for controlled network access - **Ripgrep** - Used by Claude Code for file searching --- ### 3. Path Traversal Detection & Blocked Extensions Every file access is validated before being allowed. ```javascript // Minified: qbe (blocked extensions), Wst (validateVMPathAccess) // File: index.js lines 173457-173496 /** * Blocked binary file extensions - prevents execution of dangerous files */ const blockedBinaryExtensions = [ ".exe", ".com", ".msi", ".bin", ".app", ".dmg", ".pkg", ".jar" ]; /** * Blocked executable script extensions (for openLocalFile operations) */ const blockedExecutableExtensions = [ ".sh", ".bash", ".zsh", ".command", ".bat", ".cmd", ".ps1", ".vb", ".jnlp", ".js", ".pl", ".py", ".rb", ".scpt", ".scptd", ".applescript", ".workflow" ]; /** * Validates VM path access - checks session ownership, traversal, and extensions. * * @param sessionId - Must start with "local_" * @param vmPath - Path in format /sessions//mnt// * @throws Error with codes: INVALID_SESSION, INVALID_PATH, PATH_TRAVERSAL, BLOCKED_EXTENSION */ function validateVMPathAccess(sessionId, vmPath) { // Must be a local session if (!sessionId.startsWith("local_")) throw new Error("Invalid session: " + sessionId, "INVALID_SESSION"); // Extract session name from path const sessionName = extractSessionName(vmPath); // Minified: Hst if (!sessionName) throw new Error("Invalid VM path format: " + vmPath, "INVALID_PATH"); // Verify session ownership const expectedName = getVMProcessName(sessionId); if (!expectedName || expectedName !== sessionName) throw new Error("Session mismatch for path: " + vmPath, "INVALID_SESSION"); // Normalize path and check for traversal const normalized = path.posix.normalize(vmPath); const expectedPrefix = `/sessions/${sessionName}/`; if (!normalized.startsWith(expectedPrefix)) { logger.warn(`validateVMPathAccess: path traversal detected: ${vmPath}`); throw new Error("Path traversal detected: " + vmPath, "PATH_TRAVERSAL"); } // Check file extension against blocklist const ext = path.extname(vmPath).toLowerCase(); if (blockedBinaryExtensions.includes(ext)) { logger.warn(`validateVMPathAccess: blocked binary file type ${ext}: ${vmPath}`); throw new Error("Blocked file type: " + ext, "BLOCKED_EXTENSION"); } return { vmProcessName: sessionName, normalizedPath: normalized }; } /** * Archive path safety check - prevents path traversal in archives */ function isPathSafe(filePath) { if (filePath.includes("..")) return false; const normalized = path.normalize(filePath); return !path.isAbsolute(normalized); } ``` The `extractSessionName` function (minified as `Hst`): ```javascript // Minified: Hst // File: index.js lines 173458-173464 function extractSessionName(vmPath) { if (!vmPath.startsWith("/sessions/")) return null; const afterSessions = vmPath.slice(10); // Remove "/sessions/" const slashIndex = afterSessions.indexOf("/"); const sessionName = slashIndex === -1 ? afterSessions : afterSessions.slice(0, slashIndex); // Reject traversal attempts if (!sessionName || sessionName === ".." || sessionName === ".") return null; return sessionName; } ``` --- ### 4. OAuth MITM Proxy for Token Approval The VM runs a man-in-the-middle proxy that intercepts API requests and validates OAuth tokens before allowing them through. ```javascript // File: index.js lines 151984-152007 /** * Spawns a process in the VM with OAuth token pre-approval. * Tokens are registered with the MITM proxy before the process starts. */ async function spawnInVM(process, processId, command, args, cwd, envVars, config) { const vmInterface = await getVMInterface(); if (!vmInterface) { process.setError(new Error("Swift VM addon not available"), "lam_vm_process_spawn_failed"); return; } try { // Register OAuth token with MITM proxy BEFORE spawning const oauthToken = envVars.CLAUDE_CODE_OAUTH_TOKEN; if (oauthToken) { await vmInterface.addApprovedOauthToken(oauthToken); logger.info(`[Spawn:vm] id=${processId} OAuth token approved with MITM proxy`); } // Now spawn the process - it can only use pre-approved tokens await vmInterface.spawn( processId, config.processName, command, args, cwd, Object.keys(envVars).length > 0 ? envVars : undefined, config.additionalMounts, config.isResume, config.allowedDomains, config.sharedCwdPath, ); await process.confirmSpawn(); // ... } catch (error) { // ... } } ``` The session environment setup passes the OAuth token: ```javascript // File: index.js lines 149675-149681 function createSessionEnv(config) { return { CLAUDE_CODE_ENTRYPOINT: "claude-desktop", ANTHROPIC_BASE_URL: config.apiHost, ANTHROPIC_API_KEY: "", // Empty - uses OAuth instead CLAUDE_CODE_OAUTH_TOKEN: config.oauthToken, // Token for MITM approval DISABLE_AUTOUPDATER: "1", CLAUDE_CODE_ENABLE_ASK_USER_QUESTION_TOOL: "true", }; } ``` --- ### 5. Network Egress Allow-List (allowedDomains) The VM restricts outbound network connections to a whitelist of approved domains. ```javascript // File: index.js lines 151941-152000 /** * Creates a spawn function with network restrictions. * allowedDomains is passed to the VM and enforced at the network layer. */ function createSpawnFunction(config) { const mountCount = Object.keys(config.additionalMounts).length; const mountNames = Object.keys(config.additionalMounts).join(", "); logger.info( `[Spawn:config] Creating spawn function for process=${config.processName}, ` + `isResume=${config.isResume}, mounts=${mountCount} (${mountNames}), ` + `allowedDomains=${config.allowedDomains?.length ?? 0}` ); return (spawnOptions) => { // ... spawn logic passes allowedDomains to VM await vmInterface.spawn( processId, config.processName, command, args, cwd, envVars, config.additionalMounts, config.isResume, config.allowedDomains, // Network egress whitelist config.sharedCwdPath, ); }; } ``` The `egressAllowedDomains` are validated in the IPC layer: ```javascript // File: index.js lines 5780-5786 // Session start validation includes egressAllowedDomains (typeof t.egressAllowedDomains < "u" && !( Array.isArray(t.egressAllowedDomains) && t.egressAllowedDomains.every((e) => typeof e == "string") )) ``` And passed through from the session configuration: ```javascript // File: index.js lines 155438-155443 // Session creation passes domains to spawn config { // ... other config isResume: !!sessionOptions.resume, allowedDomains: sessionInfo.egressAllowedDomains, sharedCwdPath: sharedCwdPath, } ``` Plugin/marketplace operations also enforce domain restrictions: ```javascript // File: index.js (CustomPlugins interface) // All plugin operations require egressAllowedDomains parameter await e.addMarketplace(name, url, egressAllowedDomains); await e.installPlugin(pluginId, egressAllowedDomains); await e.listMarketplaces(egressAllowedDomains); await e.listInstalledPlugins(egressAllowedDomains); await e.listAvailablePlugins(egressAllowedDomains); ``` --- ### Security Layer Summary | Layer | Technology | Protection Level | |-------|------------|------------------| | **1. Hypervisor** | VZVirtualMachine | Hardware-enforced isolation from host | | **2. Namespace isolation** | Bubblewrap | Process/mount/network namespaces inside VM | | **3. Syscall filtering** | Seccomp | Kernel-level syscall whitelist | | **4. Path validation** | Custom JS | Blocks traversal, dangerous extensions | | **5. OAuth proxy** | MITM in VM | Token approval before API access | | **6. Network egress** | allowedDomains | Whitelist-only outbound connections | --- ## Symbol Reference Quick reference for minified symbols → meaningful names: ### VM Module & Lifecycle | Minified | Meaning | Location | |----------|---------|----------| | `ff` | swiftModuleCache | index.js:151513 | | `Vw` | swiftModulePromise | index.js:151514 | | `p0e` | loadSwiftModule() | index.js:151515 | | `li` | getVMInterface() | index.js:151534 | | `eXe` | getSwiftModule() | index.js:151541 | | `ii` | notificationsModuleCache | index.js:163988 | | `Ht` | quickEntryModuleCache | index.js:166528 | | `Unt` | loadNotificationsModule() | index.js:163988 | | `tit` | initializeQuickEntry() | index.js:166528 | ### VM Process Management | Minified | Meaning | Location | |----------|---------|----------| | `tXe` | VMProcess class | index.js:151555 | | `dc` | activeProcesses (Map) | index.js:151551 | | `zw` | activeProcessCount | index.js:151552 | | `sF` | vmEventEmitter | index.js:151553 | | `rXe` | initializeVMEventCallbacks() | index.js:151690 | | `gXe` | createSpawnFunction() | index.js:151941 | | `yXe` | spawnInVM() | index.js:151974 | ### VM Startup & Shutdown | Minified | Meaning | Location | |----------|---------|----------| | `AXe` | startVMInternal() | index.js:152262 | | `Mx` | startVM() | index.js:152422 | | `kg` | vmStartupPromise | index.js:152423 | | `h_` | isGuestConnected() | index.js:152017 | | `iae` | VM_CONNECTION_TIMEOUT_MS (120000) | index.js | | `TXe` | DEFAULT_VM_RAM_GB (8) | index.js | | `$Xe` | GUEST_POLL_INTERVAL_MS (100) | index.js | | `mXe` | startHeartbeat() | index.js:152362 | | `vXe` | stopHeartbeat() | index.js:152305 | ### VM Bundle Management | Minified | Meaning | Location | |----------|---------|----------| | `CXe` | downloadVMBundle() | index.js:152118 | | `g0e` | getBundleStatus() | index.js:152090 | | `m_` | getVMBundlePath() | index.js:152031 | | `ci` | bundleVersion | index.js:152088 | | `gy` | bundleChecksum | index.js:152176 | | `wXe` | VM_BUNDLES_DIR ("vm_bundles") | index.js:152031 | | `SXe` | BUNDLE_FILENAME ("claudevm.bundle") | index.js:152032 | ### Path Translation & Security | Minified | Meaning | Location | |----------|---------|----------| | `Og` | translateVMPathToHost() | index.js:152669 | | `DXe` | (path translation helper) | index.js:152669 | | `C0e` | resolveMountToDirectory() | index.js:152760 | | `qbe` | blockedBinaryExtensions | index.js:173457 | | `Kst` | blockedExecutableExtensions | index.js:173498 | | `Wst` | validateVMPathAccess() | index.js:173465 | | `Hst` | extractSessionName() | index.js:173458 | | `b8` | isPathSafe() | index.js:127669 | ### Settings & Configuration | Minified | Meaning | Location | |----------|---------|----------| | `Xp` | getConfigFilePath() | index.js:71527 | | `GDe` | readConfigFile() | index.js:71530 | | `U0` | writeConfigFile() | index.js:71601 | | `KDe` | configFileSchema | index.js:71515 | | `zue` | preferencesSchema | index.js:20612 | | `JDe` | defaultPreferences | index.js:71674 | | `gi` | getPreference() | index.js:71806 | | `Ky` | setPreference() | index.js:71817 | | `zd` | setConfigValue() | index.js:71652 | | `Un` | getCachedConfig() | index.js:71530 | | `rB` | applyDefaultPreferences() | index.js:71806 | | `bf` | settingsEventEmitter | index.js:71817 | ### YukonSilver (VM Config) | Minified | Meaning | Location | |----------|---------|----------| | `YKe` | yukonSilverConfigSchema | index.js:129376 | | `X4` | getYukonSilverConfig() | index.js:129376 | | `Uge` | setYukonSilverConfig() | index.js:129376 | | `JKe` | yukonSilverConfigStore | index.js:129376 | | `VKe` | ConfigStore class | index.js:129296 | | `Ax` | createConfigStore() | index.js:129296 | | `wj` | checkYukonSilverSupport() | index.js:156704 | ### Enterprise Configuration | Minified | Meaning | Location | |----------|---------|----------| | `XDe` | enterpriseSettingKeys | index.js:71870 | | `eLe` | readMacOSEnterpriseConfig() | index.js:71925 | | `rLe` | readWindowsEnterpriseConfig() | index.js:71999 | ### IPC Dispatchers | Minified | Meaning | Location | |----------|---------|----------| | `qfe` | AppPreferencesDispatcher | index.js:44884 | | `It` | LocalAgentModeSessions | mainView.js:3778 | | `na` | electronStore instance | index.js:71333 | | `z5` | ElectronStoreWrapper class | index.js:60177 | ### Logging & Telemetry | Minified | Meaning | Location | |----------|---------|----------| | `Me` | logger (VM context) | index.js:151510 | | `H` | logger (general) | various | | `pr` | trackEvent() | various | | `Ci` | Error constructor with code | various | ### Menu & UI | Minified | Meaning | Location | |----------|---------|----------| | `krt` | buildAppFeaturesMenu() | index.js:159033 | | `P$e` | defaultFeatureFlags | index.js:20670 | | `I$e` | featureFlagLabels | index.js:20670 | | `Dst` | getCoworkVmMenuItem() | index.js:173117 | --- ## Heartbeat Monitoring The VM uses a bidirectional heartbeat protocol to detect failures and automatically restart. ```javascript // Minified: mXe (startHeartbeat), vXe (stopHeartbeat) // File: index.js lines 152362-152375 const NETWORK_TIMEOUT_MS = 30000; // tae = 3e4 /** * Heartbeat protocol types */ const HeartbeatMessageType = { heartbeat_request: "heartbeat_request", heartbeat_response: "heartbeat_response", }; /** * Starts heartbeat monitoring with automatic restart on failure. * Called after VM startup completes successfully. */ function startHeartbeat(config) { // config.onRestart is called when heartbeat fails return { onRestart: async () => { logger.info("[VM:heartbeat] Heartbeat failure detected, restarting VM..."); try { await vmInterface.stopVM(); clearVMInstanceId(); await startVM(options); logger.info("[VM:heartbeat] VM restart completed successfully"); } catch (error) { logger.error("[VM:heartbeat] VM restart failed:", error); } }, }; } /** * Checks if a heartbeat process is running */ function isHeartbeatAlive() { return isProcessRunning("__heartbeat_ping__"); } ``` Telemetry event on failure: ```javascript trackEvent("lam_vm_heartbeat_failure", { vm_instance_id: getVMInstanceId() ?? "unknown", // ... additional context }); ``` --- ## VM Bundle Management ### Bundle Structure ```javascript // Minified: wXe, SXe, m_ // File: index.js lines 152031-152032 const VM_BUNDLES_DIR = "vm_bundles"; // wXe const BUNDLE_FILENAME = "claudevm.bundle"; // SXe // Bundle contents: // - rootfs.img (compressed as .zst during download) // - Version tracked in .{filename}.origin files ``` ### Download with Checksum Verification ```javascript // Minified: CXe // File: index.js lines 152118-152200 /** * Downloads VM bundle with zstd decompression and SHA256 verification. * Attempts warm bundle promotion before full download. */ async function downloadVMBundle(progressCallback) { const bundleDir = getVMBundlePath(); await fs.mkdir(bundleDir, { recursive: true }); let isFreshDownload = false; let downloadSizeBytes = -1; // Step 1: Try warm bundle promotion (pre-downloaded in background) if (await promoteWarmBundle(bundleDir)) { logger.info("[downloadVM] Warm bundle promoted successfully"); return false; // Not a fresh download } isFreshDownload = true; const startTime = Date.now(); // Step 2: Download with zstd decompression const downloadUrl = `${baseUrl}/rootfs.img.zst`; const { sha256, bytesWritten } = await downloadWithTransform({ url: downloadUrl, outputPath: path.join(bundleDir, "rootfs.img"), computeHash: true, transform: zstd.createZstdDecompress(), onProgress: progressCallback, }); downloadSizeBytes = bytesWritten; // Step 3: Verify checksum if (sha256 !== expectedChecksum) { throw new Error("Checksum mismatch for VM bundle"); } // Step 4: Write version marker await fs.writeFile(versionFile, bundleVersion); trackEvent("lam_vm_bundle_download_completed", { bundle_version: bundleVersion, duration_ms: Date.now() - startTime, download_size_bytes: downloadSizeBytes, was_warm_promoted: false, }); return true; // Fresh download } ``` ### Warm Bundle System Background downloads allow instant VM starts: ```javascript // File: index.js lines 172931-173041 /** * Warm bundle events for background pre-downloading */ // lam_vm_warm_download_started - Background download initiated // lam_vm_warm_download_completed - Background download finished // lam_vm_warm_download_failed - Background download error // lam_vm_warm_promote_completed - Warm bundle moved to active // lam_vm_warm_promote_failed - Promotion failed, fallback to download ``` --- ## Knowledge Base Integration Multiple knowledge bases can be mounted into a single session. ```javascript // File: index.js lines 155507-155569 /** * Session methods for knowledge base management */ const sessionMethods = { /** * Mount a knowledge base into the session * @param kbId - Knowledge base identifier * @param mountName - Name for the mount point * @param mountPath - Full path in VM */ addMountedKnowledgeBase(kbId, mountName, mountPath) { const session = this.sessions.get(sessionId); if (!session) return; // Track mounted KBs session.mountedKnowledgeBases = session.mountedKnowledgeBases || []; if (!session.mountedKnowledgeBases.includes(kbId)) { session.mountedKnowledgeBases.push(kbId); } // Track mount paths session.knowledgeBaseMountPaths = session.knowledgeBaseMountPaths || new Map(); session.knowledgeBaseMountPaths.set(mountName, mountPath); this.saveSession(session); }, /** * Get path for a mounted knowledge base */ getKnowledgeBasePath(kbId) { // Returns path like /sessions//mnt/.knowledge// }, }; ``` Knowledge base content is injected into the system prompt: ```javascript // File: index.js lines 154311-154337 // KB mount path template const kbMountPath = `/mnt/.knowledge/${mountName}/`; // Telemetry trackEvent("local_kb_session_mounted", { session_id: sessionId, kb_id: kbId, file_count: stats.fileCount, total_size_bytes: stats.totalSize, }); ``` --- ## File System Watching The app monitors host directories for file changes: ```javascript // Minified: FileSystemWatcher class // File: index.js lines 152950-153049 class FileSystemWatcher extends EventEmitter { constructor() { this.watchers = new Map(); // sessionId -> FSWatcher this.knownFiles = new Map(); // sessionId -> Set } /** * Start watching a directory for file changes */ startWatching(sessionId, dirPath) { const knownFiles = new Set(); // Initial scan for (const file of fs.readdirSync(dirPath)) { if (!file.startsWith(".")) { // Skip hidden files knownFiles.add(file); } } this.knownFiles.set(sessionId, knownFiles); // Watch for changes const watcher = fs.watch(dirPath, { recursive: false }, (eventType, filename) => { if (!filename || filename.startsWith(".")) return; const fullPath = path.join(dirPath, filename); const exists = fs.existsSync(fullPath); const isFile = exists && fs.statSync(fullPath).isFile(); if (exists && isFile && !knownFiles.has(filename)) { // New file created knownFiles.add(filename); this.emit("fsEvent", { type: "fs_file_created", sessionId, hostPath: fullPath, fileName: filename, timestamp: Date.now(), }); } else if (!exists && knownFiles.has(filename)) { // File deleted knownFiles.delete(filename); this.emit("fsEvent", { type: "fs_file_deleted", sessionId, fileName: filename, timestamp: Date.now(), }); } }); this.watchers.set(sessionId, watcher); } stopWatching(sessionId) { const watcher = this.watchers.get(sessionId); if (watcher) { watcher.close(); this.watchers.delete(sessionId); this.knownFiles.delete(sessionId); } } } ``` --- ## ClaudeVM Web IPC Interface Additional IPC methods exposed via contextBridge (separate from LocalAgentModeSessions): ```javascript // File: index.js lines 8419-8561, mainView.js lines 468-530 const ClaudeVM = { // ───────────────────────────────────────────── // Bundle Management // ───────────────────────────────────────────── /** * Download VM bundle (manual trigger) */ download() { return ipcRenderer.invoke("$eipc_message$_..._$_ClaudeVM_$_download"); }, /** * Get current download status * @returns { status: "idle" | "downloading" | "ready" | "error", progress?: number } */ getDownloadStatus() { return ipcRenderer.invoke("$eipc_message$_..._$_ClaudeVM_$_getDownloadStatus"); }, /** * Delete bundle and trigger reinstall */ deleteAndReinstall() { return ipcRenderer.invoke("$eipc_message$_..._$_ClaudeVM_$_deleteAndReinstall"); }, // ───────────────────────────────────────────── // VM Control // ───────────────────────────────────────────── /** * Start VM with optional memory configuration * @param options - { memoryGB?: number } */ startVM(options) { return ipcRenderer.invoke("$eipc_message$_..._$_ClaudeVM_$_startVM", options); }, /** * Get current running status * @returns { running: boolean, instanceId?: string } */ getRunningStatus() { return ipcRenderer.invoke("$eipc_message$_..._$_ClaudeVM_$_getRunningStatus"); }, // ───────────────────────────────────────────── // YukonSilver Configuration (VM Settings) // ───────────────────────────────────────────── /** * Get current YukonSilver configuration * @returns { autoDownloadInBackground: boolean, autoStartOnUserIntent: boolean, memoryGB: number } */ getYukonSilverConfig() { return ipcRenderer.invoke("$eipc_message$_..._$_ClaudeVM_$_getYukonSilverConfig"); }, /** * Configure YukonSilver settings * @param config - { autoDownloadInBackground?: boolean, autoStartOnUserIntent?: boolean, memoryGB?: number } * * Example to disable background downloading: * await window.claude.ClaudeVM.setYukonSilverConfig({ * autoDownloadInBackground: false, * autoStartOnUserIntent: true, * memoryGB: 4 * }); */ setYukonSilverConfig(config) { return ipcRenderer.invoke("$eipc_message$_..._$_ClaudeVM_$_setYukonSilverConfig", config); }, // ───────────────────────────────────────────── // Event Subscriptions // ───────────────────────────────────────────── onDownloadProgress(callback) { // Fires with progress percentage during download const handler = (event, progress) => callback(progress); ipcRenderer.on("$eipc_message$_..._$_ClaudeVM_$_onDownloadProgress", handler); return () => ipcRenderer.removeListener("$eipc_message$_..._$_ClaudeVM_$_onDownloadProgress", handler); }, onDownloadStatusChanged(callback) { // Fires when download status transitions (idle -> downloading -> ready) const handler = (event, status) => callback(status); ipcRenderer.on("$eipc_message$_..._$_ClaudeVM_$_onDownloadStatusChanged", handler); return () => ipcRenderer.removeListener("$eipc_message$_..._$_ClaudeVM_$_onDownloadStatusChanged", handler); }, onRunningStatusChanged(callback) { // Fires when VM starts/stops const handler = (event, status) => callback(status); ipcRenderer.on("$eipc_message$_..._$_ClaudeVM_$_onRunningStatusChanged", handler); return () => ipcRenderer.removeListener("$eipc_message$_..._$_ClaudeVM_$_onRunningStatusChanged", handler); }, onStartupError(callback) { // Fires when VM startup fails with error message const handler = (event, error) => callback(error); ipcRenderer.on("$eipc_message$_..._$_ClaudeVM_$_onStartupError", handler); return () => ipcRenderer.removeListener("$eipc_message$_..._$_ClaudeVM_$_onStartupError", handler); }, }; ``` ### Developer Console Examples ```javascript // Check current VM config await window.claude.ClaudeVM.getYukonSilverConfig() // Returns: { autoDownloadInBackground: false, autoStartOnUserIntent: true, memoryGB: 8 } // Disable background VM bundle downloading (~2GB) await window.claude.ClaudeVM.setYukonSilverConfig({ autoDownloadInBackground: false, autoStartOnUserIntent: true, memoryGB: 4 }); // Check download status await window.claude.ClaudeVM.getDownloadStatus() // Returns: { status: "idle" | "downloading" | "ready" | "error", progress?: number } // Check if VM is running await window.claude.ClaudeVM.getRunningStatus() // Returns: { running: boolean, instanceId?: string } // Delete VM bundle and reinstall (via Developer menu) await window.claude.ClaudeVM.deleteAndReinstall() ``` --- ## Idle Session Notifications Native notifications when agent is waiting for user input: ```javascript // File: index.js lines 164190-164231 /** * Shows native notification for idle session * Uses Swift addon on macOS, falls back to Electron Notification */ async function showIdleNotificationForSession(sessionId, title, onClick) { const swiftModule = await getSwiftModule(); if (swiftModule?.notifications) { // Native macOS notification via Swift addon swiftModule.notifications.show({ title, sessionId, onClick, }); } else { // Fallback to Electron notification const notification = new Notification({ title, body: "Click to return to session", }); notification.on("click", onClick); notification.show(); } } function closeIdleNotificationForSession(sessionId) { // Dismiss notification when session resumes } ``` --- ## Telemetry Events Reference Complete list of `lam_*` analytics events: ### VM Lifecycle | Event | Description | |-------|-------------| | `lam_vm_startup_completed` | VM boot + guest connection successful | | `lam_vm_startup_failed` | VM failed to start (with error_type) | | `lam_vm_shutdown_completed` | Clean VM shutdown | | `lam_vm_shutdown_failed` | Shutdown error | | `lam_vm_heartbeat_failure` | Heartbeat check failed | ### Process Events | Event | Description | |-------|-------------| | `lam_vm_process_spawned` | Process started in VM | | `lam_vm_process_spawn_failed` | Process failed to start | | `lam_vm_process_exited` | Process terminated (with exit_code, duration_ms) | | `lam_vm_runtime_error` | Error during process execution | ### Bundle Events | Event | Description | |-------|-------------| | `lam_vm_bundle_download_completed` | Bundle download finished | | `lam_vm_bundle_download_failed` | Bundle download error | | `lam_vm_warm_download_started` | Background download initiated | | `lam_vm_warm_download_completed` | Background download finished | | `lam_vm_warm_download_failed` | Background download error | | `lam_vm_warm_promote_completed` | Warm bundle activated | | `lam_vm_warm_promote_failed` | Warm bundle promotion error | ### Session Events | Event | Description | |-------|-------------| | `lam_session_start_attempted` | Session creation initiated | | `lam_session_initialization_failed` | Session setup error | | `lam_session_stopped` | Session manually stopped | | `lam_session_archived` | Session archived | | `lam_session_timeout` | Session timed out | | `lam_session_app_quit` | Session ended due to app quit | | `lam_session_step_completed` | Session step finished | | `lam_session_turn_completed` | Conversation turn finished | | `lam_session_query_error` | Query execution error | ### Message Events | Event | Description | |-------|-------------| | `lam_message_cycle_outcome` | Message processing result | ### Other Events | Event | Description | |-------|-------------| | `lam_project_sync_failed` | Project context sync error | | `local_kb_session_mounted` | Knowledge base mounted | --- ## Internal Feature Codenames Anthropic uses animal-themed codenames for internal features. Each has a status check returning `supported`, `unsupported` (with reason), or `unavailable`. ### yukonSilver - VM Platform Gate ```javascript // Minified: wj // File: index.js lines 156704-156711 function checkYukonSilverSupport() { return process.platform !== "darwin" ? { status: "unsupported", reason: "Darwin only" } : process.arch !== "arm64" ? { status: "unsupported", reason: "arm64 only" } : getMacOSVersion().major < 14 ? { status: "unsupported", reason: "minimum macOS version not met" } : { status: "supported" }; } // Async version adds enterprise/user overrides async function checkYukonSilverSupportAsync() { const platformCheck = checkYukonSilverSupport(); if (platformCheck.status !== "supported") return platformCheck; if ((await getEnterpriseConfig()).secureVmFeaturesEnabled === false) return { status: "unsupported", reason: "enterprise_disabled" }; if (getSetting("secureVmFeaturesEnabled") === false) return { status: "unsupported", reason: "user_disabled" }; return { status: "supported" }; } ``` Config schema (`yukon-silver-config`): ```javascript { autoDownloadInBackground: boolean, // Pre-download VM bundle autoStartOnUserIntent: boolean, // Auto-start VM for Cowork memoryGB: number, // RAM allocation } ``` ### Other Codenames | Codename | Purpose | Evidence | |----------|---------|----------| | `yukonSilverGems` | Dependent on yukonSilver | Just checks if VM supported (NOT Google Gemini) | | `chillingSloth` | Git worktrees | `chillingSlothLocation` → `~/.claude-worktrees` | | `chillingSlothEnterprise` | Enterprise worktrees | Async check with enterprise config | | `chillingSlothFeat` | Worktrees feature flag | Platform check (darwin only) | | `chillingSlothLocal` | Local worktrees | Blocks Windows ARM64 | | `sparkleHedgehog` | UI prototype | Has `appearance` and `scale` settings | | `plushRaccoon` | Keyboard shortcuts | 3 configurable accelerators, dev-only | | `quietPenguin` | Unknown | macOS-only, dev-only | | `louderPenguin` | Unknown | macOS-only, dev-only | | `midnightOwl` | Unknown prototype | In Swift addon: `midnightOwl.setEnabled()` | | `desktopTopBar` | Top bar UI | Hardcoded: `feature_flag_disabled` | | `dxt` | Browser extensions | "Allows loading browser extensions in the app" | | `nativeQuickEntry` | Quick entry popup | macOS 13+ required | | `quickEntryDictation` | Voice dictation | macOS-only | ### Development vs Production ```javascript // Minified: IM // File: index.js line 156637 function devOnlyFeature(checkFn) { return app.isPackaged ? { status: "unavailable" } // Production: disabled : checkFn(); // Development: run check } // Usage: plushRaccoon: devOnlyFeature(() => someCheck), quietPenguin: devOnlyFeature(checkMacOS), louderPenguin: devOnlyFeature(checkMacOS), ``` --- ## VM Image Analysis This section documents the contents of Anthropic's Linux VM rootfs image, obtained from their CDN. ### Download URLs Anthropic hosts VM images for both architectures: ``` ARM64: https://downloads.claude.ai/vms/linux/arm64/{commit_hash}/rootfs.img.zst x64: https://downloads.claude.ai/vms/linux/x64/{commit_hash}/rootfs.img.zst ``` Example (January 2026): ``` https://downloads.claude.ai/vms/linux/x64/300d0efc7ac6b45a8384f2a36ff5dbe0d1f89baf/rootfs.img.zst ``` ### Image Structure ``` Compressed size: 2.3 GB (zstd) Decompressed size: 10 GB Partition table: GPT Device Start End Sectors Size Type rootfs.img1 227328 20971486 20744159 9.9G Linux filesystem rootfs.img14 2048 10239 8192 4M BIOS boot rootfs.img15 10240 227327 217088 106M EFI System ``` ### Base System ``` PRETTY_NAME="Ubuntu 22.04.5 LTS" VERSION="22.04.5 LTS (Jammy Jellyfish)" VERSION_CODENAME=jammy ``` File system layout: ``` /home/ubuntu/ - Default user home /workspace/ - Empty mount point for sessions /usr/local/bin/ - SDK daemon and tools /usr/local/lib/node_modules_global/ - Global npm packages ``` ### SDK Daemon The SDK daemon (`/usr/local/bin/sdk-daemon`) is a Go binary that bridges the host and VM via vsock. **Systemd service** (`/etc/systemd/system/sdk-daemon.service`): ```ini [Unit] Description=Claude SDK Daemon - vsock RPC bridge for process management After=network.target local-fs.target systemd-udev-settle.service Wants=network.target [Service] Type=simple ExecStart=/usr/local/bin/sdk-daemon Restart=always RestartSec=3 User=root Group=root Environment=HOME=/root [Install] WantedBy=multi-user.target ``` **Dependencies** (from binary strings): ``` github.com/mdlayher/vsock v1.2.1 - Virtio socket communication github.com/elazarl/goproxy v1.7.2 - HTTP MITM proxy (for OAuth) github.com/mdlayher/socket v0.4.1 - Low-level socket handling ``` **RPC Methods** (extracted from binary): ``` Spawn - Create a process in the VM Kill - Terminate a process KillAll - Terminate all processes Mount - Mount a host directory Stdin - Write to process stdin WriteStdin - Alias for Stdin ReadFile - Read file from VM InstallToTrustStore - Install certificates ``` **RPC Data Types**: ```go type SpawnParams struct { ... } type SpawnResult struct { ... } type MountConfig struct { ... } type StdinParams struct { ... } type KillParams struct { ... } type ReadFileParams struct { ... } type ReadFileResult struct { ... } ``` ### Sandbox Runtime Package Location: `/usr/local/lib/node_modules_global/lib/node_modules/@anthropic-ai/sandbox-runtime/` **Package info** (`package.json`): ```json { "name": "@anthropic-ai/sandbox-runtime", "version": "0.0.28", "description": "Anthropic Sandbox Runtime (ASRT) - A general-purpose tool for wrapping security boundaries around arbitrary processes", "bin": { "srt": "dist/cli.js" }, "engines": { "node": ">=18.0.0" } } ``` **Available on npm**: https://www.npmjs.com/package/@anthropic-ai/sandbox-runtime **Key dependencies**: ```json { "@pondwader/socks5-server": "^1.0.10", "commander": "^12.1.0", "lodash-es": "^4.17.21", "shell-quote": "^1.8.3", "zod": "^3.24.1" } ``` **Sandbox mechanisms**: - Linux: bubblewrap (namespace isolation) - macOS: sandbox-exec - Network: SOCKS5 proxy for domain filtering - Syscalls: seccomp BPF filters ### Seccomp Filters Pre-compiled filters in `vendor/seccomp/`: ``` vendor/seccomp/arm64/apply-seccomp (542 KB) vendor/seccomp/arm64/unix-block.bpf (88 bytes) vendor/seccomp/x64/apply-seccomp (828 KB) vendor/seccomp/x64/unix-block.bpf (104 bytes) ``` **Filter source** (`vendor/seccomp-src/seccomp-unix-block.c`): ```c /* * Seccomp BPF filter generator to block Unix domain socket creation * * This program generates a seccomp-bpf filter that blocks the socket() syscall * when called with AF_UNIX as the domain argument. This prevents creation of * Unix domain sockets while allowing all other socket types (AF_INET, AF_INET6, etc.) * and all other syscalls. */ #include #include int main(int argc, char *argv[]) { scmp_filter_ctx ctx; /* Create seccomp context with default action ALLOW */ ctx = seccomp_init(SCMP_ACT_ALLOW); /* Add rule to block socket(AF_UNIX, ...) */ /* Returns EPERM when AF_UNIX socket creation is attempted */ seccomp_rule_add(ctx, SCMP_ACT_ERRNO(EPERM), SCMP_SYS(socket), 1, SCMP_A0(SCMP_CMP_EQ, AF_UNIX)); /* Export the filter to a file for use with bubblewrap --seccomp */ seccomp_export_bpf(ctx, fd); seccomp_release(ctx); return 0; } ``` **Security note from source**: > SECURITY LIMITATION - 32-bit x86 (ia32): > This filter does NOT block socketcall() syscall, which is a security issue > on 32-bit x86 systems. On ia32, the socket() syscall doesn't exist - instead, > all socket operations are multiplexed through socketcall(). ### Installed Tools **Sandbox dependencies** (`/usr/bin/`): | Binary | Purpose | |--------|---------| | `bwrap` | Bubblewrap - namespace isolation | | `socat` | Socket relay for controlled network access | | `rg` | Ripgrep - fast file search | **Runtime** (`/usr/bin/`, `/usr/local/bin/`): | Binary | Version | Purpose | |--------|---------|---------| | `node` | 18.x | JavaScript runtime | | `npm` | 9.x | Package manager | | `python3` | 3.10 | Python runtime | | `git` | 2.x | Version control | **Document processing** (`/usr/local/lib/python3.10/dist-packages/`): | Package | Purpose | |---------|---------| | `camelot` | PDF table extraction | | `cv2` (OpenCV) | Image processing | | `lxml` | XML/HTML parsing | | `docx` | Word document handling | | `magika` | File type detection (Google) | | `bs4` | HTML parsing (BeautifulSoup) | | `markdown` | Markdown processing | | `img2pdf` | Image to PDF conversion | | `pdfminer` | PDF text extraction | **Other utilities** (`/usr/local/bin/`): ``` camelot, chardetect, coloredlogs, csv2ods, distro, dotenv, dumppdf.py, f2py, fonttools, humanfriendly, imageio_download_bin, img2pdf, isympy, magika, mailodf, marked, markdown-toc ``` ### Cloud-Init Instance The image was built with cloud-init, instance ID `claude-1`: ``` /var/lib/cloud/instances/claude-1/ ``` ### Key Paths Summary | Path | Purpose | |------|---------| | `/usr/local/bin/sdk-daemon` | vsock RPC bridge (Go binary, 7MB) | | `/usr/local/lib/node_modules_global/bin/srt` | Sandbox runtime CLI | | `/usr/local/lib/node_modules_global/lib/node_modules/@anthropic-ai/sandbox-runtime/` | Sandbox runtime package | | `/workspace/` | Session mount point (empty) | | `/home/ubuntu/` | Default user home | | `/etc/systemd/system/sdk-daemon.service` | SDK daemon systemd unit | ### Implications for Linux Port 1. **x64 images exist** - A VM-based Linux implementation is viable without building custom rootfs 2. **vsock is the protocol** - QEMU/KVM and Firecracker both support virtio-vsock 3. **Sandbox runtime is open source** - Can use `@anthropic-ai/sandbox-runtime` directly 4. **Claude Code is not pre-installed** - Installed dynamically via `installSdk()` RPC call 5. **SDK daemon protocol** - Would need to implement compatible RPC server for full VM approach