From ea9a34120b7a613f36ebb212dda3a1582cf4c51c Mon Sep 17 00:00:00 2001 From: Sasha Mitchell Date: Tue, 11 Aug 2026 12:36:46 +0700 Subject: [PATCH] fix(interface): refuse container-runtime dirs and docker.sock mounts check_mountable_dir already blocks system trees and .docker credential dirs, but still admitted /var/run and directories that hold docker.sock. Those bind-mount writable into the sandbox and expose the host container runtime API. Expand the denylist and refuse runtime socket children. Fixes #1058. --- strix/interface/utils.py | 33 +++++++++++++++++++++++++++++++++ tests/test_local_sources.py | 15 +++++++++++++++ 2 files changed, 48 insertions(+) diff --git a/strix/interface/utils.py b/strix/interface/utils.py index 8dc950d2..31e43fce 100644 --- a/strix/interface/utils.py +++ b/strix/interface/utils.py @@ -1328,6 +1328,12 @@ _FORBIDDEN_MOUNT_TREES = frozenset( "/lib", "/lib64", "/nix/store", + "/run", + "/var/run", + "/private/var/run", + "/var/lib/docker", + "/var/lib/containers", + "/var/lib/containerd", "/run/current-system/sw", "/Applications", "/Library", @@ -1339,6 +1345,16 @@ _FORBIDDEN_MOUNT_TREES = frozenset( } ) +# Direct children that indicate a container-runtime API socket directory. +_FORBIDDEN_RUNTIME_SOCKET_NAMES = frozenset( + { + "docker.sock", + "podman.sock", + "containerd.sock", + "crio.sock", + } +) + # Refused themselves, but they hold projects too, so their contents are fine. _FORBIDDEN_MOUNT_ROOTS = frozenset( { @@ -1418,6 +1434,23 @@ def check_mountable_dir(path: Path) -> None: "holds credentials, not code." ) + try: + socket_child = next( + ( + entry.name + for entry in resolved.iterdir() + if entry.name.casefold() in _FORBIDDEN_RUNTIME_SOCKET_NAMES + ), + None, + ) + except OSError: + socket_child = None + if socket_child is not None: + raise ValueError( + f"Refusing to mount '{resolved}' into the sandbox: '{socket_child}' " + "exposes a container runtime API, not a codebase." + ) + def dedupe_local_targets(targets_info: list[dict[str, Any]]) -> list[dict[str, Any]]: result: list[dict[str, Any]] = [] diff --git a/tests/test_local_sources.py b/tests/test_local_sources.py index 9984bef6..e97c3f9c 100644 --- a/tests/test_local_sources.py +++ b/tests/test_local_sources.py @@ -166,6 +166,21 @@ def test_check_mountable_dir_rejects_system_subdirs() -> None: check_mountable_dir(system_subdir) +def test_check_mountable_dir_rejects_var_run() -> None: + var_run = next((p for p in (Path("/var/run"), Path("/private/var/run")) if p.is_dir()), None) + if var_run is None: + pytest.skip("no /var/run on this platform") + with pytest.raises(ValueError, match="Refusing to mount"): + check_mountable_dir(var_run) + + +def test_check_mountable_dir_rejects_runtime_socket_dir(tmp_path: Path) -> None: + sock = tmp_path / "docker.sock" + sock.write_text("") + with pytest.raises(ValueError, match="container runtime API"): + check_mountable_dir(tmp_path) + + def test_check_mountable_dir_accepts_a_project_under_the_home_root( tmp_path: Path, monkeypatch: pytest.MonkeyPatch ) -> None: