Conversation
When run as root, directly or through sudo, enclave and its agent leave root-owned files in the project, and under sudo -E also in the regular user's config, state, and cache roots, so later runs as the regular user fail on them. Under rootful Docker the agent is also host root on every bind-mounted directory. Enclave now refuses to run as root before it writes any state. --allow-root or ENCLAVE_ALLOW_ROOT=1 opts in; there is deliberately no config key, so neither global nor project config can grant it. Help and version output work without the opt-in, the refusal stays on one line so it fits the --json result envelope, and user host commands pass the opt-in on to scripts that re-invoke $ENCLAVE_BIN. The RPM smoke test runs as root in its job container, so it now checks the refusal and then runs with ENCLAVE_ALLOW_ROOT=1. The opt-in has to lead to a working session, but an image for UID 0 could not be built: the Dockerfile renamed the user that already had the build UID to agent, and usermod cannot rename root while the build runs as root. The QEMU bundle build failed too, because busybox adduser refuses a UID in use. Both now add agent as a second passwd name for UID 0 and leave the root entry alone. Lookups by UID still return root's entry, which comes first. RUN steps therefore got HOME=/root, which put the build helpers in /root/.local/bin, off the agent's PATH, so the UID 0 branch replaces /root with a link to /home/agent. Sessions got USER=root and HOME=/root the same way, which made the default git identity root@enclave, so the runtime now exports USER=agent and HOME=/home/agent for UID 0 images, as the QEMU backend already did. Admin sessions keep root's values. The build UID rule moves to model.EffectiveBuildIdentity so the image build and the runtime share it. Non-root builds take the same path as before, but the Dockerfile change alters the image hash, so every image rebuilds once. Tested as root with Docker and QEMU sessions and the refusal, and as a regular user with --build-uid 0. Podman as root and devcontainer remoteUser: root are not covered. Part of eclipse-enclave#106. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
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.
What it does
Part of #106 (1 of 2); the sensitive-mount guard follows in a separate PR.
Root guard. Running enclave as root, directly or through
sudo, leaves root-owned files in the project and, undersudo -E, in the regular user's config, state, and cache roots, and later runs as the regular user fail on them. Under rootful Docker the agent is also host root on every bind mount. Enclave now refuses to run as root:internal/app/root_guard.go) runs early inapp.Run, before anything writes state. Help, version, and shell completion still work.--allow-rootorENCLAVE_ALLOW_ROOT=1opts in, and each allowed run prints a warning. There is deliberately no config key, so neither global nor project config can grant it.tools|features add|update|remove --jsonit lands in the result envelope's error field. It names the sudo user when there is one and points to the docker group and rootless podman.$ENCLAVE_BIN.ENCLAVE_ALLOW_ROOT=1.Root sessions work. Getting past the guard was not enough: no image could be built for UID 0 (a root host, or
--build-uid 0).agent, andusermodcannot renamerootwhile the build runs as root. A new UID 0 branch addsagentas a second passwd and group name for UID 0 and leavesrootalone. The existing branches, which non-root builds take, are unchanged.build-bundle.sh): the same approach, because busyboxadduserrefuses a UID in use.RUNsteps therefore gotHOME=/root, and the build helpers landed off the agent'sPATH(enclave-install-tool: not found). The UID 0 branch replaces/rootwith a link to/home/agent.USER=rootandHOME=/rootthe same way, so the default git identity wasroot@enclave. The runtime now exportsUSER=agentandHOME=/home/agentfor UID 0 images, as the QEMU backend already did. Admin sessions keep root's values, andHOME/USERfrom the project.envstill win.effectiveBuildIdentitymoves tomodel.EffectiveBuildIdentity, so the image build and the runtime share the "--build-uid, else the host UID" rule.Worth a close look: in UID 0 images
/rootbecomes a symlink to/home/agent. Root's passwd entry is untouched. On the Debian and Ubuntu bases/rootonly holds.bashrcand.profile, which the agent's home gets from skel; a custom base image with more in/rootwould lose it. Non-root images keep a real/root.Docs: a new "Running as root" section in
docs/cli-reference.md, plusconfiguration.md,security/README.md,windows.md,README.md,ARCHITECTURE.md, andDEV.md.How to test
make testcovers the guard (internal/app/root_guard_test.go), flag parsing (internal/cli/parse_test.go), and the UID 0 session environment (internal/runtime/runtime_devcontainer_test.go).As root, from a throwaway directory (the UID 0 agent can write root-owned files into the project):
Without sudo,
--build-uid 0 --build-gid 0takes the same build path. Point the XDG roots at a scratch directory, because the UID 0 agent leaves root-owned files behind:Leave out
--slimfor now (see Follow-ups).What I verified:
make build,make lint, andmake test. The only failure is ininternal/wslshim, which fails on any host with/usr/bin/enclaveinstalled (see Follow-ups).--build-uid 0:USER=agent,HOME=/home/agent, default git identityagent@enclave, a writable home, and working default features. The admin shell keepsUSER=root.agent, with a real/root.0/0,0/1000,1000/1000, and00/00, the bundle user setup, and a full UID 0 QEMU bundle build.Follow-ups
Related to this change:
--allow-root=falsestill opts in, because bool flags ignore their value (as--verbosedoes). That needs a general parser fix, left out of this PR.sudo enclave --allow-rootin a regular user's checkout. That is git's own protection, and enclave sets nosafe.directory. Should we document it or handle it?remoteUser: root(left out on purpose). QEMU UID 0 bundles don't get the/rootlink; their build doesn't need it, but tools that ignore$HOME, such as ssh, use/rootin the VM.Pre-existing, found while testing:
--slimbuilds fail for every UID with"/extensions/features": not found.prepareBuildContextstages only the selected features, but the Dockerfile copiesextensions/featuresunconditionally.internal/runtime/ide_bridge.go) is nested in the Claude config store, and the container runtime creates itsidemount point there as root on the host. fix: pre-create the tool skills directory in the config store #93 pre-createdskillsandmemory, but notide.TestProbeScriptExitsNotFoundWhenNothingIsInstalledininternal/wslshimfails on hosts with/usr/bin/enclaveinstalled, which is one of the probe's fallback paths.Breaking changes
Anyone who runs enclave as root today is now refused until they pass
--allow-rootor setENCLAVE_ALLOW_ROOT=1. That includes CI job containers,sudo, and WSL distributions created withwsl --import, which log in as root. The policy (refuse by default, a real opt-in, no config key) was agreed with the project lead. Separately, the Dockerfile change alters the image hash, so every image rebuilds once.Review checklist