From edbb8fd908235d2c29cff230f949686c6fa9dff6 Mon Sep 17 00:00:00 2001 From: Samir Hanna Verza Date: Tue, 6 Oct 2026 19:24:21 -0300 Subject: [PATCH 1/2] fix(core): treat a cloned repository as a source-aware (white-box) scan A `repository` target is cloned into the sandbox workspace and collected into `local_sources` exactly like a `local_code` target, but the two white-box checks only looked at `type == "local_code"`. Scanning a GitHub repo, alone or beside a domain, therefore ran with is_whitebox=False: the source-aware skills were not preloaded for the root agent (coordination/source_aware_whitebox is not loadable on demand) and telemetry recorded the scan as black-box. The docs describe this invocation as white-box testing. Regressed in 1e641e5, which replaced `bool(args.local_sources)` with the `local_code` check. Add one `is_whitebox_scan` in a dependency-free `strix.core.targets` (the interface layer imports it before the agents SDK may be imported, so it cannot live in core.inputs) and use it from both the runner and scan setup. A repository that was not cloned still does not count, as it has no source. Tests: unit cases for each target mix, and a runner-level case asserting the root agent is built with is_whitebox=True for a cloned repository (it fails on main for the repository cases and passes for the local_code and web controls). Fixes #884. Supersedes #885, which no longer applies to main. Co-Authored-By: Claude Sonnet 5.5 Claude-Session: https://claude.ai/code/session_01TavwVMH7PvrD68JTDfd6Hb --- strix/core/runner.py | 3 +- strix/core/targets.py | 26 ++++++++++++++++++ strix/interface/scan_setup.py | 2 +- strix/interface/utils.py | 5 ---- tests/test_runner_root_prompt.py | 46 +++++++++++++++++++++++++++++++ tests/test_scan_targets.py | 47 ++++++++++++++++++++++++++++++++ 6 files changed, 122 insertions(+), 7 deletions(-) create mode 100644 strix/core/targets.py create mode 100644 tests/test_scan_targets.py diff --git a/strix/core/runner.py b/strix/core/runner.py index f43b1d7fa..32baf762d 100644 --- a/strix/core/runner.py +++ b/strix/core/runner.py @@ -44,6 +44,7 @@ from strix.core.inputs import ( ) from strix.core.paths import run_dir_for, runtime_state_dir from strix.core.sessions import open_agent_session +from strix.core.targets import is_whitebox_scan from strix.report.state import get_global_report_state from strix.runtime import session_manager from strix.telemetry import set_scan_phase @@ -363,7 +364,7 @@ async def run_strix_scan( try: targets = scan_config.get("targets") or [] scan_mode = str(scan_config.get("scan_mode") or "deep") - is_whitebox = any(t.get("type") == "local_code" for t in targets) + is_whitebox = is_whitebox_scan(targets) diff_scope = scan_config.get("diff_scope") is_diff_scoped = bool(isinstance(diff_scope, dict) and diff_scope.get("active")) skills = list(scan_config.get("skills") or []) diff --git a/strix/core/targets.py b/strix/core/targets.py new file mode 100644 index 000000000..189625d51 --- /dev/null +++ b/strix/core/targets.py @@ -0,0 +1,26 @@ +"""Helpers that read the resolved scan targets. + +Kept free of heavy imports on purpose: the interface layer needs them before the +agents SDK may be imported. +""" + +from __future__ import annotations + +from typing import Any + + +def is_whitebox_scan(targets_info: list[dict[str, Any]] | None) -> bool: + """True when any target puts source code in front of the agent (a source-aware scan). + + That is a local directory, and also a repository that was cloned for the scan: both + end up in the sandbox workspace, as ``collect_local_sources`` in + ``strix.interface.utils`` collects them. A repository that was not cloned has no + source to read, so it does not count. + """ + for target in targets_info or []: + target_type = target.get("type") + if target_type == "local_code": + return True + if target_type == "repository" and (target.get("details") or {}).get("cloned_repo_path"): + return True + return False diff --git a/strix/interface/scan_setup.py b/strix/interface/scan_setup.py index 0af126d01..8264ab820 100644 --- a/strix/interface/scan_setup.py +++ b/strix/interface/scan_setup.py @@ -17,6 +17,7 @@ from typing import TYPE_CHECKING, Any from strix.config import Settings, codex, load_settings from strix.core.paths import run_dir_for +from strix.core.targets import is_whitebox_scan from strix.interface.utils import ( assign_workspace_subdirs, clone_repository, @@ -25,7 +26,6 @@ from strix.interface.utils import ( derive_local_base_name, generate_run_name, infer_target_type, - is_whitebox_scan, read_target_list_file, resolve_diff_scope_context, rewrite_localhost_targets, diff --git a/strix/interface/utils.py b/strix/interface/utils.py index 7ab55d8ee..e09a80e3c 100644 --- a/strix/interface/utils.py +++ b/strix/interface/utils.py @@ -1305,11 +1305,6 @@ def assign_workspace_subdirs(targets_info: list[dict[str, Any]]) -> None: details["workspace_subdir"] = workspace_subdir -def is_whitebox_scan(targets_info: list[dict[str, Any]]) -> bool: - """True iff any target is a local source tree (whitebox / source-aware).""" - return any(t.get("type") == "local_code" for t in targets_info or []) - - def collect_local_sources(targets_info: list[dict[str, Any]]) -> list[dict[str, Any]]: local_sources: list[dict[str, Any]] = [] diff --git a/tests/test_runner_root_prompt.py b/tests/test_runner_root_prompt.py index 0e24908d4..0c50258c1 100644 --- a/tests/test_runner_root_prompt.py +++ b/tests/test_runner_root_prompt.py @@ -187,6 +187,52 @@ async def test_root_prompt_options_default_to_none( assert kwargs["system_prompt_context"] == {"scope": "built-in"} +@pytest.mark.asyncio +@pytest.mark.parametrize( + ("targets", "expected"), + [ + pytest.param( + [{"type": "repository", "details": {"cloned_repo_path": "/work/clones/repo"}}], + True, + id="cloned-repository", + ), + pytest.param( + [ + {"type": "web_application", "details": {}}, + {"type": "repository", "details": {"cloned_repo_path": "/work/clones/repo"}}, + ], + True, + id="repository-beside-a-domain", + ), + pytest.param( + [{"type": "local_code", "details": {"target_path": "/src/app"}}], + True, + id="local-directory", + ), + pytest.param([{"type": "web_application", "details": {}}], False, id="web-only"), + ], +) +async def test_root_agent_is_whitebox_when_source_is_in_the_workspace( + monkeypatch: pytest.MonkeyPatch, + tmp_path: Any, + targets: list[dict[str, Any]], + *, + expected: bool, +) -> None: + """A cloned repository is source-aware like a local directory, so the root agent gets + the source-aware skills; the runner used to look at ``local_code`` targets only.""" + captured = _patch_engine_scaffold(monkeypatch, tmp_path, {"scope": "built-in"}) + + await runner.run_strix_scan( + scan_config={"targets": targets, "scan_mode": "deep"}, + scan_id="scan-whitebox", + image="img", + coordinator=AgentCoordinator(), + ) + + assert captured["kwargs"]["is_whitebox"] is expected + + @pytest.mark.asyncio async def test_mcp_available_flag_set_when_a_connection_attaches( monkeypatch: pytest.MonkeyPatch, diff --git a/tests/test_scan_targets.py b/tests/test_scan_targets.py new file mode 100644 index 000000000..0ba718e74 --- /dev/null +++ b/tests/test_scan_targets.py @@ -0,0 +1,47 @@ +"""Which scans count as source-aware (white-box).""" + +from __future__ import annotations + +from typing import Any + +import pytest + +from strix.core.targets import is_whitebox_scan + + +def _web() -> dict[str, Any]: + return {"type": "web_application", "details": {"target_url": "https://app.example"}} + + +def _local() -> dict[str, Any]: + return {"type": "local_code", "details": {"target_path": "/src/app"}} + + +def _repo(*, cloned: bool = True) -> dict[str, Any]: + details: dict[str, Any] = {"target_repo": "https://github.com/org/repo"} + if cloned: + details["cloned_repo_path"] = "/work/clones/repo" + return {"type": "repository", "details": details} + + +@pytest.mark.parametrize( + ("targets", "expected"), + [ + pytest.param([], False, id="no-targets"), + pytest.param(None, False, id="targets-missing"), + pytest.param([_web()], False, id="web-only"), + pytest.param([_local()], True, id="local-directory"), + pytest.param([_repo()], True, id="cloned-repository"), + pytest.param([_repo(cloned=False)], False, id="repository-that-was-not-cloned"), + pytest.param([_repo(), _web()], True, id="repository-beside-a-domain"), + pytest.param([_web(), _repo()], True, id="domain-before-repository"), + ], +) +def test_source_in_front_of_the_agent_makes_the_scan_whitebox( + targets: list[dict[str, Any]] | None, *, expected: bool +) -> None: + assert is_whitebox_scan(targets) is expected + + +def test_a_target_without_details_does_not_break_the_check() -> None: + assert is_whitebox_scan([{"type": "repository"}]) is False From b9c1c0d0b331b8f3826e93bd4749f40c6e52262f Mon Sep 17 00:00:00 2001 From: Samir Hanna Verza Date: Fri, 16 Oct 2026 17:58:44 -0300 Subject: [PATCH 2/2] test: check the telemetry label for a cloned repository Review feedback on #1470: the tests covered is_whitebox_scan and the runner, but not what telemetry_start reports. Assert the is_whitebox label sent to both sinks for a cloned repository, one that was not cloned, and a web target. The cloned case fails with the old rule (type == local_code only). Co-Authored-By: Claude Sonnet 5.5 Claude-Session: https://claude.ai/code/session_01TavwVMH7PvrD68JTDfd6Hb --- tests/test_scan_targets.py | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/tests/test_scan_targets.py b/tests/test_scan_targets.py index 0ba718e74..3cd3ecc8e 100644 --- a/tests/test_scan_targets.py +++ b/tests/test_scan_targets.py @@ -2,11 +2,14 @@ from __future__ import annotations +import argparse +from types import SimpleNamespace from typing import Any import pytest from strix.core.targets import is_whitebox_scan +from strix.interface import scan_setup def _web() -> dict[str, Any]: @@ -45,3 +48,32 @@ def test_source_in_front_of_the_agent_makes_the_scan_whitebox( def test_a_target_without_details_does_not_break_the_check() -> None: assert is_whitebox_scan([{"type": "repository"}]) is False + + +@pytest.mark.parametrize( + ("targets", "expected"), + [ + pytest.param([_repo()], True, id="cloned-repository"), + pytest.param([_repo(cloned=False)], False, id="repository-that-was-not-cloned"), + pytest.param([_web()], False, id="web-only"), + ], +) +def test_telemetry_labels_the_scan_from_the_same_check( + monkeypatch: pytest.MonkeyPatch, targets: list[dict[str, Any]], *, expected: bool +) -> None: + started: list[dict[str, Any]] = [] + recorder = SimpleNamespace(start=lambda **kwargs: started.append(kwargs)) + monkeypatch.setattr(scan_setup, "posthog", recorder) + monkeypatch.setattr(scan_setup, "scarf", recorder) + monkeypatch.setattr( + scan_setup, "load_settings", lambda: SimpleNamespace(llm=SimpleNamespace(model="m")) + ) + monkeypatch.setattr(scan_setup.codex, "auth_mode", lambda _model: "api_key") + args = argparse.Namespace( + scan_mode="standard", targets_info=targets, non_interactive=True, instruction=None + ) + + scan_setup.telemetry_start(args) + + # Reported once to each sink, with the label the runner uses for the root agent. + assert [call["is_whitebox"] for call in started] == [expected, expected]