mirror of
https://github.com/usestrix/strix.git
synced 2026-10-20 05:03:58 +00:00
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 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TavwVMH7PvrD68JTDfd6Hb
This commit is contained in:
parent
f1386cad37
commit
edbb8fd908
6 changed files with 122 additions and 7 deletions
|
|
@ -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 [])
|
||||
|
|
|
|||
26
strix/core/targets.py
Normal file
26
strix/core/targets.py
Normal file
|
|
@ -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
|
||||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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]] = []
|
||||
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
47
tests/test_scan_targets.py
Normal file
47
tests/test_scan_targets.py
Normal file
|
|
@ -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
|
||||
Loading…
Add table
Reference in a new issue