diff --git a/src/forge/sandbox/runner.py b/src/forge/sandbox/runner.py index a650cedbf..cf3063e56 100644 --- a/src/forge/sandbox/runner.py +++ b/src/forge/sandbox/runner.py @@ -432,7 +432,10 @@ def _build_volume_mounts( ) for host_path, container_path in skill_mounts: - mounts.append((host_path, container_path, "ro,Z")) + # Skill trees are shared by concurrent sandbox containers. A private + # SELinux label (``Z``) lets a later mount revoke an already-running + # container's access; ``z`` labels this read-only mount as shared. + mounts.append((host_path, container_path, "ro,z")) return mounts diff --git a/tests/unit/sandbox/test_runner_mounts.py b/tests/unit/sandbox/test_runner_mounts.py new file mode 100644 index 000000000..cf49122c3 --- /dev/null +++ b/tests/unit/sandbox/test_runner_mounts.py @@ -0,0 +1,23 @@ +from pathlib import Path +from types import SimpleNamespace + +from forge.sandbox.runner import ContainerRunner + + +def test_skill_mounts_use_shared_selinux_label(tmp_path: Path) -> None: + """Concurrent sandboxes must retain access to shared skill directories.""" + runner = object.__new__(ContainerRunner) + runner.settings = SimpleNamespace(llm_backend="openai") + workspace = tmp_path / "workspace" + task_file = tmp_path / "task.json" + skill_dir = tmp_path / "skills" + + mounts = runner._build_volume_mounts( + workspace, + task_file, + None, + [(skill_dir, "/skills/skill_0")], + ) + + assert (skill_dir, "/skills/skill_0", "ro,z") in mounts + assert (skill_dir, "/skills/skill_0", "ro,Z") not in mounts