Skip to content

feat: multi-user support in sandboxes - #27

Open
Tyler Payne (tylerpayne) wants to merge 2 commits into
mainfrom
feature/sandbox-multiuser
Open

feat: multi-user support in sandboxes#27
Tyler Payne (tylerpayne) wants to merge 2 commits into
mainfrom
feature/sandbox-multiuser

Conversation

@tylerpayne

Copy link
Copy Markdown
Collaborator

Summary

Adds OS-user management to the Quicksand API so multiple isolated users can share one VM.

  • Sandbox.create_user(name) returns a SandboxUser handle (uid, gid, home) and Sandbox.delete_user(name, remove_home=...) kills the user's processes and removes the account. Sandbox.execute(..., user=...) (or SandboxUser.execute) runs commands with that user's uid/gid/supplementary groups and HOME, defaulting cwd to the user's home.
  • The guest agent manages accounts by writing /etc/passwd, /etc/group, and /etc/shadow directly, so behaviour is identical on Alpine, Ubuntu, and any other guest with no dependency on adduser/useradd. Privileges drop via initgroups/setgid/setuid in a pre_exec hook. Account-file mutations are serialised behind a mutex, and usernames are validated to prevent injection into the colon/newline-delimited files.
  • Both transports (HTTP and virtio-serial) expose create_user/delete_user and the user field on execute/execute_stream.
  • Because the Dockerfiles compile the agent from a build-context copy that the Dockerfile text does not reference, the image cache key now folds in a hash of the agent source, with a .buildhash sidecar validating caller-supplied output paths. The ubuntu hatch hook always delegates to build_image so an agent-only change rebuilds the packaged image instead of shipping a stale qcow2.

Also includes one standalone build fix. The quicksand-qemu wheel hardcoded manylinux_2_17, letting pip install wheels whose bundled binaries need a newer glibc than the host and then crash on load. The manylinux tag is now derived from the glibc symbol versions the bundled ELF binaries actually reference (via auditwheel, a new Linux-only build dependency).

Fixes applied during review

  • Resolved leftover merge conflict markers in the guest agent (Cargo.toml, main.rs). The multiuser changes predated the virtio-serial writer-task refactor, so they were ported onto it: handlers emit frames through send_frame and commands still run on spawned tasks so a long command cannot stall the read loop.
  • The image cache sidecar check now only applies to caller-supplied output paths. The default custom-<hash>.qcow2 name already encodes the input hash, so requiring a sidecar there needlessly invalidated existing caches.
  • Updated test fakes to match the new execute signature and the cache-key test to include the agent source hash.

Testing

  • cargo check and cargo clippy clean on the guest agent (the two clippy warnings are pre-existing).
  • 305 unit tests pass, including 6 new tests covering user threading into execute params, SandboxUser handle routing, and error propagation.
  • ruff check, ruff format, and ty check all pass.

🤖 Generated with Claude Code

Tyler Payne (tylerpayne) and others added 2 commits July 8, 2026 11:42
Add OS-user management to the Quicksand API. Sandbox.create_user() and
Sandbox.delete_user() manage accounts inside the guest, and execute()
accepts a user= parameter (or use the returned SandboxUser handle) to
run commands with that user's uid/gid/groups and HOME. Multiple users
share one VM, giving per-user isolation without per-user VM overhead.

The guest agent implements the accounts natively by editing /etc/passwd,
/etc/group and /etc/shadow, so behaviour is identical across distros
with no dependency on adduser/useradd. Commands drop privileges via
initgroups/setgid/setuid in a pre_exec hook. Both the HTTP and
virtio-serial transports expose create_user/delete_user and the user
field on execute/execute_stream.

Since the Dockerfiles compile the agent from a build-context copy the
Dockerfile text does not reference, the image cache key now folds in a
hash of the agent source, with a sidecar file to validate caller-supplied
output paths. The ubuntu hatch hook now always delegates to build_image
so a cached-but-stale qcow2 is rebuilt instead of packaged.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The quicksand-qemu wheel hardcoded manylinux_2_17, but its bundled
binaries link against the build runner's glibc, which can be newer.
pip would then install a wheel that fails to load on older hosts
instead of rejecting it at install time. BinaryBundler now scans the
bundled ELF files for the glibc symbol versions they reference and asks
auditwheel which manylinux policy that implies, raising on any failure
rather than guessing. auditwheel becomes a Linux-only build dependency
of quicksand-qemu.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
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