diff --git a/strix/runtime/session_manager.py b/strix/runtime/session_manager.py index e8b3a279..d018f350 100644 --- a/strix/runtime/session_manager.py +++ b/strix/runtime/session_manager.py @@ -6,6 +6,7 @@ import asyncio import logging import os import sys +import tempfile from pathlib import Path from typing import TYPE_CHECKING, Any @@ -13,7 +14,6 @@ from agents.sandbox.entries import BaseEntry, File, LocalDir from agents.sandbox.manifest import Environment, Manifest from strix.config import load_settings -from strix.core.paths import run_dir_for, runtime_state_dir from strix.runtime.backends import backend_supports_bind_mounts, get_backend from strix.runtime.caido_bootstrap import bootstrap_caido from strix.runtime.caido_handle import CaidoBootstrapHandle @@ -168,6 +168,17 @@ def build_extra_file_entries( return entries +def extra_file_staging_dir(scan_id: str) -> Path: + """A fresh host staging directory for a scan's extra-file bind mounts. + + The docker daemon resolves bind sources in its own filesystem. With a + remote daemon (e.g. a dind sidecar) the run directory is not shared, so + staging lives under the temp dir like every other bind-mount source. + """ + safe = "".join(c if c.isalnum() or c in "-_." else "-" for c in scan_id) + return Path(tempfile.mkdtemp(prefix=f"strix-extra-files-{safe}-")) + + def build_extra_file_bind_mounts( extra_files: list[dict[str, Any]], staging_dir: Path, @@ -284,9 +295,10 @@ async def create_or_reuse( bind_mounts = build_bind_mounts(local_sources) entries: dict[str | Path, BaseEntry] = {} if extra_files: - staging_dir = runtime_state_dir(run_dir_for(scan_id)) / "extra_files" bind_mounts.extend( - build_extra_file_bind_mounts(extra_files, staging_dir, local_sources) + build_extra_file_bind_mounts( + extra_files, extra_file_staging_dir(scan_id), local_sources + ) ) else: bind_mounts = [] diff --git a/tests/test_session_entries.py b/tests/test_session_entries.py index 60d6abfd..21cc915d 100644 --- a/tests/test_session_entries.py +++ b/tests/test_session_entries.py @@ -2,6 +2,7 @@ from __future__ import annotations +import tempfile from pathlib import Path from typing import Any @@ -18,6 +19,7 @@ from strix.runtime.session_manager import ( build_extra_file_bind_mounts, build_extra_file_entries, build_manifest_entries, + extra_file_staging_dir, ) @@ -319,6 +321,21 @@ def test_extra_file_bind_mounts_avoid_basename_collisions(tmp_path: Path) -> Non assert mounts[0]["source"] != mounts[1]["source"] +def test_extra_file_staging_lives_under_the_temp_dir_not_the_run_dir() -> None: + staging = extra_file_staging_dir("clients-release-evisort-dev_86b7") + + assert staging.is_dir() + assert staging.is_relative_to(Path(tempfile.gettempdir())) + assert "strix_runs" not in staging.parts + + +def test_extra_file_staging_dir_sanitizes_the_scan_id() -> None: + staging = extra_file_staging_dir("../weird id/../") + + assert staging.is_dir() + assert staging.is_relative_to(Path(tempfile.gettempdir())) + + def test_only_bind_mount_capable_backends_are_registered_as_such() -> None: assert backend_supports_bind_mounts("docker") assert not backend_supports_bind_mounts("e2b")