发布

  • feat: KVM/bwrap isolation backends for cowork mode (#269)

    frostbyte_neo 发布于 2026-03-01 02:13:09 +00:00

    • feat: add KVM/bwrap isolation backends for cowork mode

    Refactor cowork-vm-service.js from monolithic VMManager into a pluggable
    backend architecture with three isolation levels:

    • HostBackend: direct execution on host (existing Phase 1 behavior)
    • BwrapBackend: bubblewrap namespace sandbox (PID/mount isolation)
    • KvmBackend: full QEMU/KVM VM with vsock, virtiofs, QMP monitor

    Backend auto-detected at startup (KVM > bwrap > host) or overridden
    via COWORK_VM_BACKEND env var. Shared helpers extracted for env
    filtering, arg cleanup, command resolution, and work dir handling.

    Also:

    • Add Cowork Mode section to --doctor diagnostics with per-tool
      checks and distro-specific install hints
    • Update Patch 4 to extract real win32 file entries for linux
      bundle manifest (enables app download infrastructure)
    • Update handover documentation for Phase 2/3 architecture

    Co-Authored-By: Claude claude@anthropic.com

    • fix: correct KvmBackend vsock port, direction, and kernel cmdline

    The guest sdk-daemon connects TO the host (CID=2), not the other way
    around. Confirmed via disassembly of the guest binary: the vsock port
    is 51234 (0xC822), matching the Hyper-V GUID in the Windows service.

    • Change VSOCK_GUEST_PORT from 2222 to 51234
    • Reverse socat bridge: VSOCK-LISTEN (host listens) instead of
      VSOCK-CONNECT (host connecting to guest)
    • Add bridge server to accept persistent guest connection and route
      events/responses via callback map
    • Fix kernel cmdline: root=LABEL=cloudimg-rootfs (matches fstab)

    Co-Authored-By: Claude claude@anthropic.com

    • fix: patch VM download to use disk-backed temp dir on Linux

    Linux systems often mount /tmp as a small tmpfs (3-4GB). The VM
    rootfs download decompresses to ~9GB, causing ENOSPC. Patch the
    app's mkdtemp("wvm-") call to use the bundle directory (on real
    disk) instead of os.tmpdir() on Linux.

    Uses regex-based dynamic variable extraction to remain
    version-agnostic across minified code changes.

    Co-Authored-By: Claude claude@anthropic.com

    • fix: handle stale cowork socket (ECONNREFUSED) on Linux

    Stale sockets from previous sessions give ECONNREFUSED instead of
    ENOENT, bypassing the retry loop and auto-launch entirely. Fix:

    • Expand ENOENT check in retry loop to include ECONNREFUSED on Linux
    • Add cleanup_stale_cowork_socket() to launcher scripts (all formats)
      that removes dead sockets before Electron starts
    • Increase tmpdir patch search window from 1000 to 2000 chars

    Co-Authored-By: Claude claude@anthropic.com

    • fix: preserve DNS resolution inside bwrap sandbox

    On systems using systemd-resolved, /etc/resolv.conf is a symlink to
    /run/systemd/resolve/stub-resolv.conf. The bwrap --tmpfs /run option
    wiped this out, breaking DNS resolution inside the sandbox and
    preventing the spawned Claude process from reaching the API.

    Bind-mount the resolved /run/systemd/resolve/ directory back into
    the sandbox as read-only to restore DNS.

    Co-Authored-By: Claude claude@anthropic.com

    • fix: harden cowork isolation and build patches
    • Remove broken _setupEventForwarding (events already flow through
      _handleGuestData); the second bridge connection was silently ignored
    • Mount $HOME read-only in bwrap sandbox; only workDir and explicit
      mounts are writable (prevents writes to ~/.ssh, ~/.gnupg, etc.)
    • Scope Patch 4 win32 extraction to actual win32:{} block via brace
      counting to avoid crossing into darwin/linux sections
    • Set _qmpAvailable flag on QMP timeout instead of silently continuing
    • Wrap CID allocation at 65535 to prevent unbounded growth
    • Use execFileSync instead of execSync('which ...') in detectBackend
    • Coerce response ID to String for Map lookup in _handleGuestData
    • Use non-greedy [\s\S]*? in Patch 6 regex for nested brace robustness
    • Update patch count threshold from 4 to 5 after adding Patch 8
    • Add age-based fallback for stale socket cleanup when socat is missing
    • Use indexOf-based splice in Patch 8 instead of string.replace()
    • Extract shared resolveSdkBinary helper to deduplicate SDK resolution
    • Remove dead retryFuncRe/retryFuncMatch variables from Patch 6

    Co-Authored-By: Claude claude@anthropic.com

    • fix: address security and correctness issues from code review
    • Replace execSync string interpolation with execFileSync for qemu-img
      calls to eliminate shell injection risk
    • Add path validation to readFile in both LocalBackend and KvmBackend
      to restrict reads to within the user's home directory
    • Fix QMP _sendQmpCommand timer leak by clearing timeout on success
    • Fix _pendingCallbacks.delete() to use String(msg.id) matching the
      String(msg.id) used in the .get() lookup
    • Extract FORWARDED_EVENTS constant, cleanup helper, extractBlock
      helper, and consolidate doctor tool checks (from simplifier pass)

    Co-Authored-By: Claude claude@anthropic.com

    • docs: update README cowork notice with isolation backends and doctor info

    Co-Authored-By: Claude claude@anthropic.com


    Co-authored-by: Claude claude@anthropic.com

    下载附件