mirror of
https://github.com/usestrix/strix.git
synced 2026-09-07 08:25:56 +00:00
fix(scope): show host scope in CLI summaries
This commit is contained in:
parent
7b41840e4d
commit
00593da239
7 changed files with 50 additions and 25 deletions
|
|
@ -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 %}
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
*,
|
||||
|
|
|
|||
|
|
@ -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")
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue