fix(scope): show host scope in CLI summaries

This commit is contained in:
oyasumi 2026-08-19 00:55:05 +00:00
parent 7b41840e4d
commit 00593da239
7 changed files with 50 additions and 25 deletions

View file

@ -77,7 +77,7 @@ SYSTEM-VERIFIED SCOPE:
AUTHORIZED TARGETS:
{% for target in system_prompt_context.authorized_targets %}
{% if target.type == "web_host" %}
- web_host: {{ target.value }} (includes {{ target.value }} and *.{{ target.value }})
- host: {{ target.value }} (includes {{ target.value }} and *.{{ target.value }})
{% elif target.type == "ip_address" %}
- ip_address: {{ target.value }} (exact address)
{% else %}

View file

@ -268,6 +268,21 @@ def build_scope_context(scan_config: dict[str, Any]) -> dict[str, Any]:
}
def build_scope_target_labels(targets: list[dict[str, Any]]) -> list[str]:
"""Build concise, deduplicated scope labels for CLI summaries."""
labels: list[str] = []
for target in build_scope_context({"targets": targets})["authorized_targets"]:
ttype = target["type"]
value = target["value"]
if ttype == "web_host":
labels.append(f"host: {value} (includes *.{value})")
elif ttype == "ip_address":
labels.append(f"ip: {value} (exact address)")
else:
labels.append(f"{ttype}: {value}")
return labels
def make_model_settings(
reasoning_effort: ReasoningEffort | None,
*,

View file

@ -20,6 +20,7 @@ from strix.runtime import session_manager
from .utils import (
build_live_stats_text,
build_target_summary_text,
format_vulnerability_report,
has_model_response,
read_workspace_files,
@ -44,16 +45,7 @@ async def run_cli(args: Any) -> None: # noqa: PLR0915
start_text = Text()
start_text.append("Penetration test initiated", style="bold #22c55e")
target_text = Text()
target_text.append("Target", style="dim")
target_text.append(" ")
if len(args.targets_info) == 1:
target_text.append(args.targets_info[0]["original"], style="bold white")
else:
target_text.append(f"{len(args.targets_info)} targets", style="bold white")
for target_info in args.targets_info:
target_text.append("\n ")
target_text.append(target_info["original"], style="white")
target_text = build_target_summary_text(args.targets_info)
results_text = Text()
results_text.append("Output", style="dim")

View file

@ -40,6 +40,7 @@ from strix.interface.update_check import (
)
from strix.interface.utils import (
build_final_stats_text,
build_target_summary_text,
)
from strix.telemetry import posthog, scarf
from strix.telemetry.logging import configure_dependency_logging
@ -270,16 +271,7 @@ def display_completion_message(args: argparse.Namespace, results_path: Path) ->
else:
completion_text.append("SESSION ENDED", style="bold #eab308")
target_text = Text()
target_text.append("Target", style="dim")
target_text.append(" ")
if len(args.targets_info) == 1:
target_text.append(args.targets_info[0]["original"], style="bold white")
else:
target_text.append(f"{len(args.targets_info)} targets", style="bold white")
for target_info in args.targets_info:
target_text.append("\n ")
target_text.append(target_info["original"], style="white")
target_text = build_target_summary_text(args.targets_info)
stats_text = build_final_stats_text(report_state)

View file

@ -21,12 +21,29 @@ from rich.panel import Panel
from rich.text import Text
from strix.config import load_settings
from strix.core.inputs import build_scope_target_labels
from strix.utils.api_spec import detect_spec_format
logger = logging.getLogger(__name__)
def build_target_summary_text(targets_info: list[dict[str, Any]]) -> Text:
"""Render configured targets as their deduplicated prompt-level scope."""
labels = build_scope_target_labels(targets_info)
target_text = Text()
target_text.append("Target", style="dim")
target_text.append(" ")
if len(labels) == 1:
target_text.append(labels[0], style="bold white")
else:
target_text.append(f"{len(labels)} targets", style="bold white")
for label in labels:
target_text.append("\n ")
target_text.append(label, style="white")
return target_text
def get_severity_color(severity: str) -> str:
severity_colors = {
"critical": "#dc2626",

View file

@ -12,9 +12,11 @@ from strix.agents.prompt import render_system_prompt
from strix.core.inputs import (
build_root_task,
build_scope_context,
build_scope_target_labels,
child_initial_input,
make_model_settings,
)
from strix.interface.utils import build_target_summary_text
def _child_kwargs(parent_history: list[Any]) -> dict[str, Any]:
@ -241,7 +243,7 @@ def test_build_scope_context_authorizes_nothing_without_targets() -> None:
def test_scope_prompt_authorizes_flag_and_instruction_hosts_with_subdomains() -> None:
config = {
config: dict[str, Any] = {
"targets": [
{
"type": "web_application",
@ -263,8 +265,8 @@ def test_scope_prompt_authorizes_flag_and_instruction_hosts_with_subdomains() ->
assert context["authorized_targets"] == [
{"type": "web_host", "value": "app.example.com", "workspace_path": ""}
]
assert "web_host: app.example.com (includes app.example.com and *.app.example.com)" in prompt
assert prompt.count("web_host: app.example.com") == 1
assert "host: app.example.com (includes app.example.com and *.app.example.com)" in prompt
assert prompt.count("host: app.example.com") == 1
assert "https://app.example.com/search?q=test" not in prompt
assert "https://app.example.com/search?q=test" in task
assert "https://api.example.net/v1" in task
@ -273,6 +275,13 @@ def test_scope_prompt_authorizes_flag_and_instruction_hosts_with_subdomains() ->
assert "scheme, port, path, query, or fragment" in prompt
assert "not `example.com`, sibling hosts such as `api.example.com`" in prompt
assert build_scope_target_labels(config["targets"]) == [
"host: app.example.com (includes *.app.example.com)"
]
assert build_target_summary_text(config["targets"]).plain == (
"Target host: app.example.com (includes *.app.example.com)"
)
def test_scope_prompt_keeps_web_ip_targets_exact() -> None:
context = build_scope_context(

View file

@ -128,7 +128,7 @@ async def test_root_prompt_options_flow_into_root_agent(
instructions_override = kwargs["instructions_override"]
assert "SYSTEM-VERIFIED SCOPE" in instructions_override
assert "AUTHORIZED TARGETS" in instructions_override
assert "web_host: example.com (includes example.com and *.example.com)" in instructions_override
assert "host: example.com (includes example.com and *.example.com)" in instructions_override
assert "exact hostname and all of its descendant subdomains" in instructions_override
assert "CUSTOM SCAN PROMPT" in instructions_override
assert "Network hosts explicitly named in these root scan instructions" in instructions_override