Document the rootless worker posture and commit its seccomp profile - #2043
Merged
Merged
Conversation
ppXD
force-pushed
the
feat/serve-a-broker-lease-over-a-unix-socket
branch
from
September 27, 2026 18:56
41e1daf to
3af4728
Compare
ppXD
changed the base branch from
feat/serve-a-broker-lease-over-a-unix-socket
to
main
September 27, 2026 18:56
ppXD
force-pushed
the
docs/document-the-rootless-worker-posture
branch
from
September 27, 2026 18:56
e2c0994 to
e5866c5
Compare
The worker image runs as uid 1654 with no capabilities, but nothing in the repo said how to let that user confine. Docker's default seccomp profile reserves clone/unshare with namespace flags, setns, mount and umount2 for CAP_SYS_ADMIN and denies pivot_root outright, so bubblewrap fails its probe and every run is recorded unconfined. The only remedies people reached for were --privileged or seccomp=unconfined. codespace-worker.json is moby v20.10.20's default resolved for a container with no capabilities, plus one unconditional allow of those seven calls. It is written in the OCI runtime-spec form, with no includes, excludes or archMap. containerd decodes a Localhost profile straight into the OCI LinuxSeccomp struct and silently drops those keys, so a moby-format file would have turned every capability-gated rule into an unconditional allow on the Kubernetes path the Dockerfile recommends first: 39 calls, among them the new mount API, open_by_handle_at, bpf and process_vm_readv/writev. Resolving the conditions up front makes Docker, containerd and CRI-O apply one filter. derive-codespace-worker.sh regenerates the file from the pinned moby source, and the unit test pins its SHA-256, so any change to what the worker may call is a reviewed diff. Under the profile, plus apparmor=unconfined and systempaths=unconfined, the unmodified worker image confines as uid 1654 without --privileged or any capability, and a network-off child sees only loopback. The Dockerfile header said an allowlist run on an unprivileged pod "just gets Denied"; it actually aborts at launch, because the tool probe passes on the installed binaries. It also left CAP_SYS_ADMIN out of the allowlist tier, which `ip netns add` needs. The header now states per tier what the deployment must grant, with a Kubernetes securityContext for the same posture, and compose carries the security_opt block commented out so the default stack is unchanged. Both say that a masked /proc still passes the bubblewrap probe, which mounts no /proc, while every launch fails. A boot line on the worker reports the bubblewrap probe and its reason, whether the codespace-mcp helper is present, and whether the namespace probe (CanSeal) holds, so an operator sees the posture before the first run instead of reconstructing it from a refused or unconfined one. The worker-host E2E pins that the registrar emits it exactly once.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
backend/deploy/seccomp/codespace-worker.json. It is moby v20.10.20's default profile resolved for a container with no capabilities, plus one unconditional allow of the seven calls bubblewrap needs (clone,clone3,mount,pivot_root,setns,umount2,unshare).apparmor=unconfinedandsystempaths=unconfined, the unmodified worker image confines as uid 1654. It needs no--privilegedand no capability, and a network-off child sees only loopback.includes,excludesorarchMap. containerd decodes a Localhost profile straight intospecs.LinuxSeccomp(contrib/seccomp/seccomp.go) and silently drops those keys. A moby-format file would therefore have become an unconditional allow of 39 capability-gated calls on the Kubernetes path, among them the new mount API,open_by_handle_at,bpfandprocess_vm_readv/writev.derive-codespace-worker.shregenerates the file from the sha256-pinned moby source.Dockerfile.workerconfinement header per tier, with a KubernetessecurityContextfor the same posture (hostUsers: false,procMount: Unmasked, Localhost seccomp, AppArmor Unconfined, non-root, drop ALL;privileged: truewithrunAsUser: 1654on older clusters). Two old statements were wrong:CAP_SYS_ADMIN, becauseip netns addneeds it.security_optblock todocker-compose.yml; the default stack is unchanged. Both the compose comment and the Dockerfile say that a masked/procstill passes the bubblewrap probe (the probe mounts no/proc) while every launch fails. An operator should therefore check that the first run's agent starts, not rely on the boot line alone.Sandbox posture:line at worker boot. It gives the bubblewrap probe and its reason, whether thecodespace-mcphelper is present, and whetherFilteredEgressNetns.CanSealholds and why not.Test plan
RootlessWorkerPostureTests(13):LinuxSeccomp/LinuxSyscallkeys appear, so Docker, containerd and CRI-O read one filter;RecurringJobWorkerSmokeE2ETests.The_worker_host_logs_its_sandbox_posture_once_at_boot. It boots the real Worker-role host and fails if the registrar stops emitting the line.HangfireHostingRoleTests,SandboxConfigurationStartupTests(25)BubblewrapConfinementSandboxTests,DurableLaunchEgressE2ETests,SealedEgressE2ETests,CommandIsolationE2ETests(26), no host residuelo.pivot_rootremoved: the probe reports unavailable andRequireConfinementrefuses.CanSealholds.pivot_root, adding a capability-gated rule or anarchMapkey, and deleting the registrar call each turn a test red.