Skip to content

feat(sandbox): run the guest server as an unprivileged user - #214

Merged
quickbeard merged 1 commit into
mainfrom
feat/sandbox-unprivileged-guest
Aug 13, 2026
Merged

quickbeard merged 1 commit into
mainfrom
feat/sandbox-unprivileged-guest

Conversation

@quickbeard

Copy link
Copy Markdown
Owner

Phase 4c, item 2 — closes the "guest server runs as root" gap (the privilege half of the CoWork/SharedRoot lesson: escapes happen at the mount seam, so the process touching those mounts should not be root).

What changed

  • vz (image/guest/codev-init): PID 1, the mounts, and the vsock bridges stay root; the server execs under setpriv --reuid=codev --regid=codev --init-groups (uid 1000), after a chown of the guest home so the root-copied /etc/skel files stay writable.
  • WSL (wsl/provider.ts): same drop in the host-generated boot script, plus /etc/wsl.conf written at import (automount options = "metadata,uid=1000,gid=1000", then a terminate so the first start reads it) — Windows has no uid to match, so the guest's own user owns the automounted drives.
  • bubblewrap: already unprivileged — --unshare-user with no --uid keeps the invoking host user, there is no root in that sandbox to drop.

The design changed after measurement

The plan was to carry the host uid/gid into boot.json and remap the guest user, because virtio-fs was assumed to check guest access against host file ownership. Measured on macOS 15, that is not how it behaves: Virtualization.framework's virtio-fs performs operations host-side as the process owning the VM and reports every shared file as root inside the guest, so guest ownership never gates workspace access. The first attempt also exposed why the remap would have been actively harmful — macOS users are in gid 20, which collides with Debian's dialout, and the collision guard silently fell back to root.

So the remap and all its CODEV_HOST_UID/GID plumbing are gone: the guest simply drops to the image's existing codev user. Less code, no collision cases, no host-specific state in the image.

Verification (rebuilt image, real VM)

e2e-vz on a freshly built image: health: OK / path api: OK / broker: OK, then via a guest pty:

uid=1000(codev) gid=1000(codev) groups=1000(codev)
edit-host-file: OK      create: OK      home: OK
skills: minimax-docx minimax-pdf minimax-xlsx pptx-generator

…and the files it created land on the host owned by the host user (minh:staff). Sandbox suite 92 pass / 2 skip; new WSL test asserts the wsl.conf automount line, the setpriv exec, and that the bind mounts still happen before the drop.

Rollout

  • vz needs a new published image — codev-init is baked, so this is inert until the next image respin. Worth batching with any other image-level work, since each respin costs every existing sandbox a ~750MB re-download (via Reset, per feat(sandbox): make reset the image-upgrade path #211).
  • WSL applies immediately — the boot script is host-generated and setpriv already exists in the shipped rootfs. Not yet exercised on real Windows: the drop and the wsl.conf automount are unit-tested only, so they want a pass on the Windows box before the fleet leans on them.

🤖 Generated with Claude Code

codev-init and the WSL boot script now drop to the image's codev user
(uid 1000) for the server itself; PID 1, the mounts, and the vsock/socat
bridges stay root. WSL additionally gets /etc/wsl.conf at import so the
Windows drives automount as that user.

No host-uid remapping: measured on macOS 15 that
Virtualization.framework's virtio-fs performs host operations as the
process owning the VM and reports shared files as root in the guest, so
guest ownership never gates workspace access. Verified on a rebuilt
image — the server reports uid=1000(codev) and can still append to host
files, create directories, write its home, and read the baked skills,
with results landing on the host owned by the host user.

vz takes effect with the next published image (codev-init is baked);
the WSL half is host-generated and applies to the current image.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@quickbeard
quickbeard merged commit e537320 into main Aug 13, 2026
0 of 5 checks passed
@quickbeard
quickbeard deleted the feat/sandbox-unprivileged-guest branch August 13, 2026 05:12
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant