Claude Desktop Cowork Mode - VM Architecture Analysis
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
- Overview
- Module Loading
- VM Lifecycle Management
- Process Management
- Stdin/Stdout Communication
- Path Translation & Mounting
- IPC Interface (Renderer → Main)
- Settings Configuration Architecture
- Security Architecture
- Symbol Reference
- Heartbeat Monitoring
- VM Bundle Management
- Knowledge Base Integration
- File System Watching
- ClaudeVM Web IPC Interface
- Idle Session Notifications
- Telemetry Events Reference
- Internal Feature Codenames
- VM Image Analysis
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/<name>/mnt/<folder>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
// 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
// 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)
// 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
// 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
// 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
// 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
// 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
// Minified: Og, DXe
/**
* Translates a VM path to the corresponding host path.
*
* VM paths have the format: /sessions/<processName>/mnt/<mountName>/<subpath>
*
* @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/<name>/mnt/<mount>/<subpath>
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
// 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/<name>/mnt/<folder> |
User-selected folders |
/sessions/<name>/mnt/outputs |
Output files directory |
/sessions/<name>/mnt/uploads |
Uploaded files directory |
/sessions/<name>/mnt/.projects/<uuid> |
Project contexts |
/sessions/<name>/mnt/.plugins/<id> |
Remote plugins |
/sessions/<name>/mnt/.local-plugins/<path> |
Local plugins |
/sessions/<name>/mnt/.skills |
Skill definitions |
/sessions/<name>/mnt/.knowledge/<name> |
Knowledge bases |
/sessions/<name>/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.
// 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)
// 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
// 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
// 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
// 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.
// 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.
// 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
// 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:
{
"preferences": {
"secureVmFeaturesEnabled": false
}
}
Method 2: Enterprise Policy (macOS)
Edit ~/Library/Preferences/com.anthropic.Claude.plist:
<key>secureVmFeaturesEnabled</key>
<false/>
Method 3: Enterprise Policy (Windows)
Set in registry at HKLM\SOFTWARE\Policies\Claude:
secureVmFeaturesEnabled = 0 (DWORD)
Method 4: Disable Background Download via DevTools
// In DevTools console:
await window.claude.ClaudeVM.setYukonSilverConfig({
autoDownloadInBackground: false,
autoStartOnUserIntent: true,
memoryGB: 4
});
Settings Check Flow
// 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.
// 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:
// 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.
// 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/<name>/mnt/<mount>/<subpath>
* @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):
// 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.
// 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:
// 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.
// 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:
// 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:
// 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:
// 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.
// 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:
trackEvent("lam_vm_heartbeat_failure", {
vm_instance_id: getVMInstanceId() ?? "unknown",
// ... additional context
});
VM Bundle Management
Bundle Structure
// 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
// 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:
// 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.
// 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/<name>/mnt/.knowledge/<kbName>/
},
};
Knowledge base content is injected into the system prompt:
// 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:
// 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<filename>
}
/**
* 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):
// 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
// 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:
// 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
// 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):
{
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
// 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):
[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:
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):
{
"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:
{
"@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):
/*
* 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 <seccomp.h>
#include <sys/socket.h>
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
- x64 images exist - A VM-based Linux implementation is viable without building custom rootfs
- vsock is the protocol - QEMU/KVM and Firecracker both support virtio-vsock
- Sandbox runtime is open source - Can use
@anthropic-ai/sandbox-runtimedirectly - Claude Code is not pre-installed - Installed dynamically via
installSdk()RPC call - SDK daemon protocol - Would need to implement compatible RPC server for full VM approach