Allow launches only under configured roots #3
Reference in New Issue
Block a user
Delete Branch "benvin/launch-dir-allowlist"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Why: a sandbox could reach the host through any writable dir it is launched from: $HOME, config, the API socket, a system dir, or a $PATH dir where a planted binary runs on the host.
Stacked on #2.
$HOMEminus dot-dirs still admits non-dot dirs on the host PATH (~/bin,~/go→~/go/bin);cd ~/bin && agent-sandboxpasses and lets the agent drop host executables, same class as the /run/user escape → refuse launch dirs that are or contain a$PATHentry (or drop the implicit$HOMEdefault and require allowed-paths)realpath(pwd)but main.go:276 bind-mounts the unresolvedpwd(os.Getwdreturns a symlinked$PWD); podman resolves the symlink at mount time, so a symlink swapped after the check (e.g. by an agent in another session with the parent mounted) mounts an unchecked target → mount andWorkDirthe resolved path that was checkedt.Skipon missing dir silently skipsrun-user/symlink-to-run-userwhere/run/user/$UIDis absent (CI pods) → create the target under the test tree or fail instead of skipinside(r, bad)only rejects roots under a forbidden dir, not roots containing one:allowed-paths=/varis accepted andcheckLaunchDir("/var/tmp")returns ok, so /var/tmp gets mounted rw → also rejectinside(bad, r)(or check pwd against forbiddenRoots in checkLaunchDir), and add/varto the TestCheckLaunchDirRoots bad list.filepath.Dir(paths)isconfigDir(), already first in the list → drop it.sensitiveDirs, so a non-dotXDG_CONFIG_HOME/XDG_DATA_HOME(e.g.~/cfg,~/data) lets you launch from~/cfg/systemdor~/data/containers(podman storage), which listed roots reject → in checkLaunchDir, refuse any pwd that overlapssensitiveDirs(home)for every root, default included, and add a refusal test.cfgandcfg + "/agent-sandbox/x"sit under /tmp, so they fail on the /tmp rule. Droppingfilepath.Dir(configDir())or theXDG_DATA_HOMEcontainers entry fromsensitiveDirsstill passes every test → use a non-/tmpXDG_CONFIG_HOME/XDG_DATA_HOMEfixture and check for the "overlaps" reason, not just "allowed-paths".forbiddenRootsloop is untested: deleting it passes every test;main_test.go:375("forbidden default root") is also satisfied by the$XDG_RUNTIME_DIR→/tmpfallback insensitiveDirs→ setXDG_RUNTIME_DIRelsewhere in that case so only the forbidden-root rule can refuseHOME=/tmp/home.filepath.Dir(realpath(paths))is untested: deleting it passes every test (theallowed-paths-dircase is caught byconfigDir()/the sensitive check first); without it, a launch dir holding the target of a symlinkedallowed-paths(e.g. a dotfiles repo) is mounted RW, and the sandbox can add any root → add a refusal case withallowed-pathssymlinked into a dir under a root.~/.local,~/.local/shareare rejected by the podman-storage sensitive check instead) → add a root like~/.cacheand assert the "dot-directory" error./,/home,~hit the forbidden/sensitive checks first, and the test only matches the "allowed-paths" substring) → assert the "$HOME" message for those roots.:check onsrcis untested → add a case with a symlink whose target has a:in it.