Skip to content

Sandbox fixes: launch status, shared denies, CA trust, copies - #5

Merged
jdoss merged 9 commits into
host-statefrom
sandbox-followups
Oct 9, 2026
Merged

jdoss merged 9 commits into
host-statefrom
sandbox-followups

Conversation

@jdoss

@jdoss jdoss commented Oct 9, 2026

Copy link
Copy Markdown
Contributor

Stacked on #4: merge #3 and #4 first. GitHub retargets this pull request as their branches are deleted.

Smaller sandbox fixes, one commit each.

What it does

  • Launch status: claude-profile sandbox <name> and list say when CLAUDE_PROFILE_SANDBOX overrides the profile's own setting, and sandbox --on/--off says so when the override contradicts the change. Both read a dangling marker link as the launch does (sandboxed), and --on replaces a linked marker instead of creating the file it points at.
  • Shared settings: sandbox launches drop the host-oriented denies (Bash(sudo, Read(~/.ssh, …) from shared-settings.json as they already do from the profile's settings.json, so a shared sudo deny no longer blocks the VM's scoped sudo dnf and sudo podman. Host launches keep them.
  • Copies instead of relabels: the VM gets copies of ~/.gitconfig and ~/.ssh/known_hosts from the profile's state dir, refreshed each launch, instead of :z relabelling the user's own files.
  • CA trust: when the host has custom CA anchors, the launcher mounts the host's extracted bundles (/etc/pki/ca-trust/extracted) read-only, and the entrypoint no longer runs update-ca-trust extract, which took about 7 s per launch. Boot to exit went from 13.7 s to 4.6 s on a host with six anchors, every anchor still trusted.
  • Reflinked claude binary: the cached copy of the host's claude uses FICLONE where the filesystem supports it (btrfs, XFS) and a plain copy elsewhere, so a claude update no longer writes another 244 MiB.
  • Proxy variables: --http-proxy=false keeps the host's *_proxy variables, and any credentials in them, out of the VM; CLAUDE_PROFILE_SANDBOX_FORWARD_ENV forwards them when a proxy is needed.
  • Containerfile lines fit in 100 characters (the base image digest moves to an ARG), and the README lists the credentials each launch, and each opt-in setting, gives the VM.

The sandbox image needs a rebuild: the entrypoint and Containerfile changed.

Test plan

  • prek run --all-files passes (390 tests)
  • Each change's tests failed before the change; mutation checks for the status display and the shared-deny stripping
  • Real krun VM with this branch's launcher and image: SSH through the bridge, a host https_proxy stays out, all six host anchors trusted, the gitconfig and known_hosts copies readable, git config read-only
  • The rebuilt image resolves the same base digest and writes a byte-identical storage.conf
  • On btrfs under Python 3.13 the clone shares every extent of the 244 MiB binary, where shutil.copy shared none
  • CI passes on this pull request

jdoss added 9 commits October 9, 2026 09:34
The override decides every launch, but `sandbox <name>` and `list` only
read the .sandbox marker, so with it set they could report a microVM
for a profile that launches on the host. Both now say when the
override is set and what it does, and `sandbox --on/--off` says so when
the override contradicts the new setting.

They also read the marker the way the launch does: a dangling marker
link counts as sandboxed. `sandbox --on` replaces a linked marker
instead of touching, and so creating, the file the link points at.
The sandbox's copy of the profile's settings.json drops deny rules such
as Bash(sudo *), which would otherwise block the VM's own scoped sudo
(deny wins even under --dangerously-skip-permissions). Shared settings
from shared-settings.json reach claude through --settings and kept
them, so a shared sudo deny still blocked sudo dnf and sudo podman in
the VM. Sandbox launches now drop the same denies from the shared
settings; host launches keep them.
Both were mounted straight from the host with :z, which relabels the
user's own files for containers (known_hosts loses its ssh_home_t
label) out from under the host's tools and restorecon. The VM now gets
copies in the profile's state dir, refreshed every launch, and :z
relabels those, as the launcher already does for the claude binary.
When the host has custom CA anchors, the entrypoint ran update-ca-trust
extract on every boot, which takes about 7 s in the VM. The host's own
update-ca-trust has already merged those anchors with its system roots
into /etc/pki/ca-trust/extracted, so the launcher now mounts that
directory read-only at the same path, alongside the anchors, and the
entrypoint no longer extracts. Boot to exit fell from 13.7 s to 4.6 s on
a host with six anchors, with every anchor still trusted.

Anchors without an extracted directory warn and mount neither.
Each host update of claude copied the 244 MiB native binary into the
data dir for the sandbox. On Python 3.13, shutil.copy writes every
block of it, and snapshots then keep each old version pinned. The copy
now asks the kernel to share the binary's blocks (FICLONE), which is
instant and takes no space on btrfs or XFS, and falls back to a plain
copy elsewhere or across filesystems.
podman run passes the host's http_proxy, https_proxy, no_proxy and the
like into the container by default, so a proxy URL's credentials
reached the VM although the launcher forwards nothing else from the
host environment. Sandbox launches now pass --http-proxy=false; a proxy
the VM needs can be forwarded with CLAUDE_PROFILE_SANDBOX_FORWARD_ENV.
The digest-pinned FROM line and the printf that writes storage.conf ran
past 100 characters. The digest moves into an ARG ahead of FROM, and
printf takes one argument per line as the sudoers step does. The build
resolves the same base digest and writes a byte-identical storage.conf.
The security section described what the VM can change but not which
credentials it can read or use. It now lists what every launch hands
the VM (the profile's Claude login, .env values, MCP tokens in
.claude.json and ancestor .mcp.json files, a copy of ~/.gitconfig),
what each opt-in setting adds, and what is not mounted.
@jdoss
jdoss merged commit ca129f2 into host-state Oct 9, 2026
1 check passed
@jdoss jdoss mentioned this pull request Oct 9, 2026
3 of 5 tasks
@jdoss
jdoss deleted the sandbox-followups branch October 9, 2026 16: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