diff --git a/docs/advanced/configuration.mdx b/docs/advanced/configuration.mdx
index e85e4716..994c4d66 100644
--- a/docs/advanced/configuration.mdx
+++ b/docs/advanced/configuration.mdx
@@ -32,11 +32,11 @@ Configure Strix using environment variables or a config file.
- Sampling temperature passed to the model. Unset by default (uses the provider default). Useful for steadier tool use on local/OpenAI-compatible models. Parameters unsupported by a given model are dropped automatically.
+ Sampling temperature (`0.0`–`2.0`) passed to the model. Unset by default (uses the provider default). Useful for steadier tool use on local/OpenAI-compatible models. Parameters unsupported by a given model are dropped automatically.
- Nucleus sampling `top_p` passed to the model. Unset by default.
+ Nucleus sampling `top_p` (`0.0`–`1.0`) passed to the model. Unset by default.
diff --git a/strix/config/models.py b/strix/config/models.py
index 8b36e5cd..6e7242a2 100644
--- a/strix/config/models.py
+++ b/strix/config/models.py
@@ -63,7 +63,8 @@ def configure_sdk_model_defaults(settings: Settings) -> None:
llm = settings.llm
set_tracing_disabled(True)
_configure_litellm_compatibility()
- _configure_litellm_request_timeout(llm.timeout)
+ if "timeout" in llm.model_fields_set:
+ _configure_litellm_request_timeout(llm.timeout)
if llm.api_key:
set_default_openai_key(llm.api_key, use_for_tracing=False)
_configure_litellm_default("api_key", llm.api_key)
@@ -133,11 +134,11 @@ def _configure_litellm_request_timeout(timeout: int) -> None:
"""Apply the configured ``LLM_TIMEOUT`` to LiteLLM-routed scan calls.
The SDK's LiteLLM model invokes ``litellm.acompletion`` without an explicit
- per-request timeout, so without this it falls back to LiteLLM's module
- default and the documented ``LLM_TIMEOUT`` only affected the warm-up call
- (``strix/interface/main.py``). Setting the module-level default makes
- ``export LLM_TIMEOUT=600`` take effect for the actual scan, restoring the
- documented behavior for slow local / self-hosted models.
+ per-request timeout, so before this the documented ``LLM_TIMEOUT`` only
+ affected the warm-up call (``strix/interface/main.py``) and scan calls fell
+ back to LiteLLM's ~6000s module default. The caller applies this only when
+ ``LLM_TIMEOUT`` is explicitly set, so users who never set it keep LiteLLM's
+ default instead of being silently capped at the 300s ``LLM_TIMEOUT`` default.
"""
import litellm
diff --git a/strix/config/settings.py b/strix/config/settings.py
index 578c3f44..973dea13 100644
--- a/strix/config/settings.py
+++ b/strix/config/settings.py
@@ -37,9 +37,9 @@ class LlmSettings(BaseSettings):
)
reasoning_effort: ReasoningEffort = Field(default="high", alias="STRIX_REASONING_EFFORT")
timeout: int = Field(default=300, alias="LLM_TIMEOUT")
- temperature: float | None = Field(default=None, alias="STRIX_LLM_TEMPERATURE")
- top_p: float | None = Field(default=None, alias="STRIX_LLM_TOP_P")
- max_tokens: int | None = Field(default=None, alias="STRIX_LLM_MAX_TOKENS")
+ temperature: float | None = Field(default=None, alias="STRIX_LLM_TEMPERATURE", ge=0.0, le=2.0)
+ top_p: float | None = Field(default=None, alias="STRIX_LLM_TOP_P", ge=0.0, le=1.0)
+ max_tokens: int | None = Field(default=None, alias="STRIX_LLM_MAX_TOKENS", ge=1)
class RuntimeSettings(BaseSettings):
diff --git a/strix/core/runner.py b/strix/core/runner.py
index 570b9f55..bb7bcb4c 100644
--- a/strix/core/runner.py
+++ b/strix/core/runner.py
@@ -151,6 +151,7 @@ async def run_strix_scan(
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)
+ source_in_scope = is_whitebox or any(t.get("type") == "repository" for t in targets)
skills = list(scan_config.get("skills") or [])
root_task = build_root_task(scan_config)
model_settings = make_model_settings(
@@ -220,7 +221,7 @@ async def run_strix_scan(
"agent_id": root_id,
"parent_id": None,
"interactive": interactive,
- "is_whitebox": is_whitebox,
+ "source_in_scope": source_in_scope,
"spawn_child_agent": spawn_child_agent,
}
diff --git a/strix/tools/reporting/tool.py b/strix/tools/reporting/tool.py
index 943c96ff..b18c7d13 100644
--- a/strix/tools/reporting/tool.py
+++ b/strix/tools/reporting/tool.py
@@ -489,12 +489,13 @@ async def create_vulnerability_report(
- Duplicating the same change across multiple locations.
"""
inner = ctx.context if isinstance(ctx.context, dict) else {}
- if not inner.get("is_whitebox") and code_locations:
- # Black-box scan: no source tree is available, so any file paths /
- # line numbers / snippets in code_locations can only be fabricated.
- # Drop them so a hallucinated "Code Analysis" section can never reach
- # the customer-facing report (#321).
- logger.info("Black-box scan: dropping code_locations from report %r", title)
+ if not inner.get("source_in_scope") and code_locations:
+ # No source tree is in scope (e.g. a URL/IP/domain black-box scan), so any
+ # file paths / line numbers / snippets in code_locations can only be
+ # fabricated. Drop them so a hallucinated "Code Analysis" section can never
+ # reach the customer-facing report (#321). Repository and local-code scans
+ # keep code_locations because the agent can actually read the source.
+ logger.info("No source in scope: dropping code_locations from report %r", title)
code_locations = None
raw_agent_id = inner.get("agent_id")