diff --git a/README.md b/README.md
index 2f4bd270..351b96e7 100644
--- a/README.md
+++ b/README.md
@@ -17,6 +17,9 @@
[](https://discord.gg/strix-ai)
+
+
+
@@ -34,7 +37,7 @@
> [!TIP]
-> **New!** Strix integrates seamlessly with GitHub Actions and CI/CD pipelines. Automatically scan for vulnerabilities on every pull request and block insecure code before it reaches production - [Get started with no setup required](https://app.strix.ai).
+> **New!** Strix integrates seamlessly with GitHub Actions and CI/CD pipelines. Automatically scan for vulnerabilities on every pull request and block insecure code before it reaches production - [Get started with no setup required](https://app.strix.ai?utm_source=github&utm_medium=readme&utm_content=tip_ci).
---
@@ -94,9 +97,17 @@ strix --target ./app-directory
---
-## ☁️ Strix Platform
+## Ways to Run Strix
-Try the Strix full-stack penetration testing platform at **[app.strix.ai](https://app.strix.ai)** - sign up for free, connect your repos and domains, and launch a pentest in minutes.
+- **Open Source** - free, runs locally with Docker and your own LLM key. [Quick Start](https://docs.strix.ai/quickstart)
+- **Strix Cloud** - no setup, validated findings, one-click autofix, and PR reviews. [Run a pentest →](https://app.strix.ai?intent=pentest&utm_source=github&utm_medium=readme&utm_content=table_cloud)
+- **Enterprise** - SSO, compliance-ready reports, VPC or self-hosted deployment. [Try Strix Enterprise →](https://strix.ai/demo?utm_source=github&utm_medium=readme&utm_content=table_demo)
+
+---
+
+## ☁️ Strix Cloud
+
+Try the Strix full-stack penetration testing platform at **[app.strix.ai](https://app.strix.ai?utm_source=github&utm_medium=readme&utm_content=cloud_heading)** - sign up for free, connect your repos and domains, and launch a pentest in minutes.
- **Validated findings with PoCs** - every vulnerability includes a working proof-of-concept exploit and reproduction steps
- **One-click autofix** - AI-generated security patches as ready-to-merge pull requests
@@ -104,7 +115,13 @@ Try the Strix full-stack penetration testing platform at **[app.strix.ai](https:
- **DevSecOps integrations** - GitHub, GitLab, Bitbucket, Slack, Jira, Linear, and CI/CD pipelines
- **Continuous learning** - AI that builds on past findings, adapts to your codebase, and reduces false positives over time
-[**Start your first pentest →**](https://app.strix.ai)
+[**Run a pentest →**](https://app.strix.ai?intent=pentest&utm_source=github&utm_medium=readme&utm_content=cloud_cta)
+
+## 🏢 Enterprise
+
+Get the same Strix experience with enterprise-grade controls: SSO (SAML/OIDC), custom compliance-ready penetration testing reports (SOC 2, ISO 27001, PCI DSS), dedicated support and SLA, custom deployment options (VPC or self-hosted), BYOK model support, and tailored AI pentesting agents optimized for your environment.
+
+[**Try Strix Enterprise →**](https://strix.ai/demo?utm_source=github&utm_medium=readme&utm_content=enterprise_cta)
---
@@ -333,10 +350,6 @@ Each server's tools are namespaced by `name`, for example `github_list_issues`.
See the [LLM Providers documentation](https://docs.strix.ai/llm-providers/overview) for all supported providers including Vertex AI, Bedrock, Azure, and local models.
-## Enterprise Pentesting
-
-Get the same Strix experience with [enterprise-grade](https://strix.ai/demo) controls: SSO (SAML/OIDC), custom compliance-ready penetration testing reports (SOC 2, ISO 27001, PCI DSS), dedicated support & SLA, custom deployment options (VPC/self-hosted), BYOK model support, and tailored AI pentesting agents optimized for your environment. [Learn more](https://strix.ai/demo).
-
## Documentation
Full documentation is available at **[docs.strix.ai](https://docs.strix.ai)** - including detailed guides for usage, CI/CD integrations, skills, and advanced configuration.
diff --git a/pyproject.toml b/pyproject.toml
index 79d3d7f2..b78fb3aa 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -1,6 +1,6 @@
[project]
name = "strix-agent"
-version = "1.6.1"
+version = "1.6.2"
description = "Open-source AI Hackers for your apps"
readme = "README.md"
license = "Apache-2.0"
diff --git a/scripts/install.sh b/scripts/install.sh
index 5e2e8a71..e179b558 100755
--- a/scripts/install.sh
+++ b/scripts/install.sh
@@ -346,6 +346,9 @@ echo -e "${MUTED}For more information visit ${NC}https://strix.ai"
echo -e "${MUTED}Supported models ${NC}https://docs.strix.ai/llm-providers/overview"
echo -e "${MUTED}Join our community ${NC}https://discord.gg/strix-ai"
echo ""
+echo -e "${MUTED}Run a pentest in Strix Cloud ${NC}https://app.strix.ai"
+echo -e "${MUTED}Enterprise ${NC}https://strix.ai/demo"
+echo ""
echo -e "${YELLOW}→${NC} Run ${MUTED}source ~/.$(basename $SHELL)rc${NC} or open a new terminal"
echo ""
diff --git a/strix/config/models.py b/strix/config/models.py
index babb643f..3babbe37 100644
--- a/strix/config/models.py
+++ b/strix/config/models.py
@@ -593,17 +593,27 @@ RECOMMENDED_MODEL_NAMES = (
_RECOMMENDED_MODEL_NAME_SET = frozenset(name.lower() for name in RECOMMENDED_MODEL_NAMES)
-FRONTIER_MODEL_FAMILIES = (
- (("azure", "azure_ai", "bedrock_mantle", "chatgpt", "openai"), ("gpt-5",)),
- (
- ("anthropic", "azure_ai", "bedrock", "claude", "databricks", "snowflake", "vertex_ai"),
- ("claude-fable-5", "claude-opus-5", "claude-opus-4", "claude-sonnet-5", "claude-sonnet-4"),
- ),
- (("google", "gemini", "vertex_ai"), ("gemini-3",)),
- (("deepseek",), ("deepseek-v4", "deepseek-r1", "deepseek-reasoner")),
- (("alibaba", "dashscope", "qwen"), ("qwen3.8", "qwen3.7", "qwen3-max")),
- (("moonshot", "moonshotai", "kimi"), ("kimi-k3", "kimi-k2.7", "kimi-k2.6")),
- (("zai", "z-ai", "zai-org", "zhipuai"), ("glm-5.3", "glm-5.2")),
+# Matched against the bare model name only: the route (``openai/``, ``openrouter/``,
+# a local gateway, ...) says nothing about the model's quality.
+FRONTIER_MODEL_PREFIXES = (
+ "gpt-5",
+ "claude-fable-5",
+ "claude-opus-5",
+ "claude-opus-4",
+ "claude-sonnet-5",
+ "claude-sonnet-4",
+ "gemini-3",
+ "deepseek-v4",
+ "deepseek-r1",
+ "deepseek-reasoner",
+ "qwen3.8",
+ "qwen3.7",
+ "qwen3-max",
+ "kimi-k3",
+ "kimi-k2.7",
+ "kimi-k2.6",
+ "glm-5.3",
+ "glm-5.2",
)
@@ -837,11 +847,8 @@ def is_recommended_or_frontier_model(model_name: str) -> bool:
return False
if name in _RECOMMENDED_MODEL_NAME_SET:
return True
- provider_name, bare_model_name = _split_model_provider(name)
- return any(
- _matches_frontier_family(provider_name, bare_model_name, provider_markers, prefixes)
- for provider_markers, prefixes in FRONTIER_MODEL_FAMILIES
- )
+ bare_model_name = name.rsplit("/", 1)[-1]
+ return _matches_model_prefix(bare_model_name, FRONTIER_MODEL_PREFIXES)
def _normalized_model_name(model_name: str) -> str:
@@ -853,28 +860,6 @@ def _normalized_model_name(model_name: str) -> str:
return name
-def _split_model_provider(model_name: str) -> tuple[str | None, str]:
- if "/" not in model_name:
- return None, model_name
- provider_name, bare_model_name = model_name.rsplit("/", 1)
- return provider_name, bare_model_name
-
-
-def _matches_frontier_family(
- provider_name: str | None,
- model_name: str,
- provider_markers: tuple[str, ...],
- model_prefixes: tuple[str, ...],
-) -> bool:
- if not _matches_model_prefix(model_name, model_prefixes):
- return False
- if provider_name is None:
- return True
- return _contains_provider_marker(
- provider_name, provider_markers, split_compound_names=True
- ) or _contains_provider_marker(model_name, provider_markers)
-
-
def _matches_model_prefix(model_name: str, model_prefixes: tuple[str, ...]) -> bool:
return any(
candidate.startswith(prefix)
@@ -892,16 +877,6 @@ def _model_name_candidates(model_name: str) -> tuple[str, ...]:
return (model_name, *suffixes)
-def _contains_provider_marker(
- value: str, provider_markers: tuple[str, ...], *, split_compound_names: bool = False
-) -> bool:
- parts = set(value.replace(".", "/").split("/"))
- if split_compound_names:
- for separator in ("_", "-"):
- parts.update(piece for part in tuple(parts) for piece in part.split(separator))
- return any(marker in parts for marker in provider_markers)
-
-
def is_known_openai_bare_model(model_name: str) -> bool:
import litellm
diff --git a/strix/core/runner.py b/strix/core/runner.py
index 71c08742..b40a36b0 100644
--- a/strix/core/runner.py
+++ b/strix/core/runner.py
@@ -45,6 +45,7 @@ from strix.core.paths import run_dir_for, runtime_state_dir
from strix.core.sessions import open_agent_session
from strix.report.state import get_global_report_state
from strix.runtime import session_manager
+from strix.telemetry import set_scan_phase
from strix.telemetry.logging import set_scan_id, setup_scan_logging
from strix.tools.output_store import (
WORKSPACE_SPILL_DIR,
@@ -116,6 +117,13 @@ def _record_mcp_connections(connections: list[ConnectedMcpServer]) -> None:
report_state.record_mcp_connections([connection.name for connection in connections])
+def _note_exit_reason(reason: str) -> None:
+ """Record why the scan stopped so the end-of-scan beacon reports it."""
+ report_state = get_global_report_state()
+ if report_state is not None and report_state.scan_ended_exit_reason is None:
+ report_state.scan_ended_exit_reason = reason
+
+
def _persist_mcp_status(roster: list[dict[str, Any]]) -> None:
"""Write the run's non-secret MCP connection status roster to run.json.
@@ -313,6 +321,7 @@ async def run_strix_scan(
root_id = uuid.uuid4().hex[:8]
logger.info("Bringing up sandbox session for scan %s", scan_id)
+ set_scan_phase("sandbox_init")
bundle = await session_manager.create_or_reuse(
scan_id,
image=image,
@@ -322,6 +331,7 @@ async def run_strix_scan(
)
report("Waiting for the first model response")
logger.info("Sandbox ready for scan %s", scan_id)
+ set_scan_phase("agent_setup")
sandbox_session = bundle["session"]
@@ -573,6 +583,7 @@ async def run_strix_scan(
async with coordinator._lock:
root_status = coordinator.statuses.get(root_id)
+ set_scan_phase("agent_loop")
result = await run_agent_loop(
agent=root_agent,
initial_input=initial_input,
@@ -610,6 +621,7 @@ async def run_strix_scan(
return result # noqa: TRY300
except BudgetExceededError as exc:
logger.info("Scan %s stopped: %s", scan_id, exc)
+ _note_exit_reason("budget_exceeded")
if root_id is not None:
with contextlib.suppress(Exception):
await coordinator.set_status(root_id, "stopped")
@@ -622,6 +634,7 @@ async def run_strix_scan(
exc,
scan_id,
)
+ _note_exit_reason("rate_limited")
if root_id is not None:
with contextlib.suppress(Exception):
await coordinator.set_status(root_id, "stopped")
diff --git a/strix/interface/cli_args.py b/strix/interface/cli_args.py
index dbb1ebdf..fc53e15f 100644
--- a/strix/interface/cli_args.py
+++ b/strix/interface/cli_args.py
@@ -98,6 +98,14 @@ Examples:
# Extra files placed in the sandbox workspace
strix --target ./my-project --workspace-file ./wordlist.txt
strix --target https://app.com --workspace-file ./openapi.yaml:specs/openapi.yaml
+
+Strix Cloud:
+ strix cloud login
+ strix cloud scans start --source . --yes --wait
+ strix cloud # list every cloud resource
+
+ Run a pentest in Strix Cloud https://app.strix.ai
+ Try Strix Enterprise https://strix.ai/demo
""",
)
diff --git a/strix/interface/environment.py b/strix/interface/environment.py
index 49bdd82b..522067df 100644
--- a/strix/interface/environment.py
+++ b/strix/interface/environment.py
@@ -14,6 +14,7 @@ from strix.interface.utils import (
image_exists,
process_pull_line,
)
+from strix.telemetry import report_error
logger = logging.getLogger(__name__)
@@ -44,6 +45,7 @@ def validate_environment() -> None:
f"[red]STRIX_LLM={settings.llm.model} uses your ChatGPT subscription, "
"but you're not signed in.[/] Run [cyan]strix auth login chatgpt[/] first."
)
+ report_error("subscription_not_signed_in")
sys.exit(1)
logger.info("Environment OK (ChatGPT subscription)")
return
@@ -153,6 +155,7 @@ def validate_environment() -> None:
console.print("\n")
console.print(panel)
console.print()
+ report_error("missing_required_config")
sys.exit(1)
logger.info(
"Environment OK (optional missing: %s)",
@@ -180,6 +183,7 @@ def check_docker_installed() -> None:
padding=(1, 2),
)
console.print("\n", panel, "\n")
+ report_error("docker_not_installed")
sys.exit(1)
logger.debug("Docker CLI present")
@@ -227,6 +231,7 @@ def pull_docker_image() -> None:
padding=(1, 2),
)
console.print(panel, "\n")
+ report_error("image_pull_failed", e)
sys.exit(1)
logger.info("Docker image %s ready", image)
diff --git a/strix/interface/main.py b/strix/interface/main.py
index 297c4838..c9bd5596 100644
--- a/strix/interface/main.py
+++ b/strix/interface/main.py
@@ -41,7 +41,8 @@ from strix.interface.update_check import (
from strix.interface.utils import (
build_final_stats_text,
)
-from strix.telemetry import posthog, scarf
+from strix.llm.warmup import start_import_warmup, wait_for_import_warmup
+from strix.telemetry import posthog, report_error, scarf, set_scan_phase
from strix.telemetry.logging import configure_dependency_logging
@@ -332,6 +333,11 @@ def display_completion_message(args: argparse.Namespace, results_path: Path) ->
"[#60a5fa]docs.strix.ai[/] [dim]·[/] "
"[#60a5fa]discord.gg/strix-ai[/]"
)
+ if not args.non_interactive:
+ console.print(
+ "[dim]Run a pentest in Strix Cloud[/] [#60a5fa]app.strix.ai[/] [dim]·[/] "
+ "[dim]Enterprise[/] [#60a5fa]strix.ai/demo[/]"
+ )
console.print()
if not args.non_interactive:
notify_update(console)
@@ -395,15 +401,18 @@ def _bootstrap_scan(args: argparse.Namespace) -> None:
happen inside the TUI so the interface paints immediately instead of
waiting on a model round trip.
"""
+ set_scan_phase("preflight")
try:
asyncio.run(warm_up_llm(show_model_warning=True))
except ModelConnectionError as exc:
+ report_error("model_connection_failed", exc)
_print_model_connection_error(exc, exc.model_name)
sys.exit(1)
persist_current()
try:
prepare_run(args)
except ValueError as e:
+ report_error("scan_preparation_failed", e)
_print_error_panel("SCAN PREPARATION FAILED", str(e))
sys.exit(1)
telemetry_start(args)
@@ -450,8 +459,6 @@ def main() -> None:
sys.exit(run_cloud(sys.argv[2:]))
- from strix.llm.warmup import start_import_warmup
-
start_import_warmup()
args = parse_arguments()
@@ -466,6 +473,9 @@ def main() -> None:
pull_docker_image()
validate_environment()
+ # Everything below imports the scan engine; do not race the warm-up thread.
+ wait_for_import_warmup()
+
if args.non_interactive:
_bootstrap_scan(args)
@@ -477,18 +487,21 @@ def main() -> None:
from strix.interface.cli import run_cli
asyncio.run(run_cli(args))
+ # Headless runs have no user to quit: the agent either finished
+ # (already beaconed as finished_by_tool) or stopped on its own.
+ exit_reason = "agent_stopped"
else:
asyncio.run(run_tui(args))
except InteractiveSetupUnavailableError as exc:
exit_reason = "error"
+ report_error("interactive_setup_unavailable", exc)
_print_error_panel("INTERACTIVE SETUP UNAVAILABLE", str(exc))
sys.exit(1)
except KeyboardInterrupt:
exit_reason = "interrupted"
- except Exception:
+ except Exception as exc:
exit_reason = "error"
- posthog.error("unhandled_exception")
- scarf.error("unhandled_exception")
+ report_error("unhandled_exception", exc)
raise
finally:
report_state = get_global_report_state()
diff --git a/strix/interface/tui/backend/controller.py b/strix/interface/tui/backend/controller.py
index 6f3b3fb3..b2f1eb75 100644
--- a/strix/interface/tui/backend/controller.py
+++ b/strix/interface/tui/backend/controller.py
@@ -192,7 +192,7 @@ class TuiController:
model_warning = ""
if model and not is_recommended_or_frontier_model(model):
model_warning = (
- f"{model} is not a recommended frontier model; pentest quality could be degraded"
+ f"{model} is not a recommended frontier model. Pentest quality could be degraded."
)
state = {
"setup_mode": self.setup_mode,
diff --git a/strix/interface/tui/internal/app/model_test.go b/strix/interface/tui/internal/app/model_test.go
index 59a3ad7c..92452e2e 100644
--- a/strix/interface/tui/internal/app/model_test.go
+++ b/strix/interface/tui/internal/app/model_test.go
@@ -370,6 +370,17 @@ func TestStartedSnapshotTransitionsToLiveView(t *testing.T) {
}
}
+func TestSplashModelWarningRendersTheBackendSentenceOnce(t *testing.T) {
+ warning := "openai/glm-5.3 is not a recommended frontier model. Pentest quality could be degraded."
+ got := ansi.Strip(splashModelWarning("openai/glm-5.3", warning))
+ if got != "⚠ "+warning {
+ t.Fatalf("splash warning = %q, want %q", got, "⚠ "+warning)
+ }
+ if got := ansi.Strip(splashModelWarning("other/model", warning)); got != "⚠ "+warning {
+ t.Fatalf("splash warning with unrelated model = %q", got)
+ }
+}
+
func TestSetupStartScreenFitsNarrowTerminal(t *testing.T) {
model := New(nil)
model.width, model.height = 40, 18
diff --git a/strix/interface/tui/internal/app/view.go b/strix/interface/tui/internal/app/view.go
index 6e71d0ee..8588df18 100644
--- a/strix/interface/tui/internal/app/view.go
+++ b/strix/interface/tui/internal/app/view.go
@@ -411,7 +411,7 @@ func (m Model) splashView() string {
welcome + "\n" + version + "\n" + tagline + "\n\n" +
start.String() + "\n\n" + url
if warn := m.snapshot.ModelWarning; warn != "" {
- content += "\n\n" + splashModelWarning(warn)
+ content += "\n\n" + splashModelWarning(m.snapshot.Model, warn)
}
panel := lipgloss.NewStyle().Border(lipgloss.RoundedBorder()).BorderForeground(green).Padding(1, 6).Align(lipgloss.Center).Render(content)
// #splash_screen background is solid black.
@@ -419,12 +419,16 @@ func (m Model) splashView() string {
lipgloss.WithWhitespaceBackground(black))
}
-// splashModelWarning ports SplashScreen._build_model_warning_text.
-func splashModelWarning(model string) string {
+// splashModelWarning renders the backend's full warning sentence, with the
+// model name highlighted when the sentence leads with it.
+func splashModelWarning(model, warning string) string {
yellow := lipgloss.Color("#eab308")
- return lipgloss.NewStyle().Bold(true).Foreground(yellow).Render("⚠ ") +
- lipgloss.NewStyle().Bold(true).Foreground(render.Cyan).Render(model) +
- lipgloss.NewStyle().Foreground(yellow).Render(" is not a recommended frontier model - pentest quality could be degraded")
+ out := lipgloss.NewStyle().Bold(true).Foreground(yellow).Render("⚠ ")
+ if model != "" && strings.HasPrefix(warning, model) {
+ out += lipgloss.NewStyle().Bold(true).Foreground(render.Cyan).Render(model)
+ warning = strings.TrimPrefix(warning, model)
+ }
+ return out + lipgloss.NewStyle().Foreground(yellow).Render(warning)
}
// chatPaneKey identifies everything the bordered trace depends on.
diff --git a/strix/interface/tui/runtime.py b/strix/interface/tui/runtime.py
index 728e7023..d398e5ab 100644
--- a/strix/interface/tui/runtime.py
+++ b/strix/interface/tui/runtime.py
@@ -37,6 +37,7 @@ from strix.interface.tui.sidecar import (
)
from strix.interface.utils import read_workspace_files
from strix.report.state import ReportState, set_global_report_state
+from strix.telemetry import report_error, set_scan_phase
from strix.utils.resource_paths import get_strix_resource_path
@@ -138,11 +139,13 @@ class GoTuiRuntime:
await self._preflight_model()
except Exception as exc:
logger.exception("Go TUI setup model preflight failed")
+ report_error("model_connection_failed", exc)
raise RuntimeError(f"Model connection failed: {exc}") from exc
async def _preflight_model(self) -> None:
model = (load_settings().llm.model or "").strip()
self.controller.add_message("Verifying model connection...")
+ set_scan_phase("preflight")
await preflight_model_connection(model)
self.model_verified = True
@@ -181,7 +184,11 @@ class GoTuiRuntime:
candidate.target = list(self.controller.targets)
candidate.target_list = []
build_targets_info(candidate)
- prepare_run(candidate)
+ try:
+ prepare_run(candidate)
+ except Exception as exc:
+ report_error("scan_preparation_failed", exc)
+ raise
telemetry_start(candidate)
vars(self.args).update(vars(candidate))
@@ -195,13 +202,21 @@ class GoTuiRuntime:
launch so the interface appears immediately.
"""
model = (load_settings().llm.model or "").strip()
+ set_scan_phase("preflight")
try:
await preflight_model_connection(model)
+ except Exception as exc:
+ logger.exception("Go TUI scan preparation failed")
+ report_error("model_connection_failed", exc)
+ self.controller.fail_preparation(str(exc))
+ return
+ try:
persist_current()
prepare_run(self.args)
telemetry_start(self.args)
except Exception as exc:
logger.exception("Go TUI scan preparation failed")
+ report_error("scan_preparation_failed", exc)
self.controller.fail_preparation(str(exc))
return
self.controller.scan_state = "running"
@@ -240,6 +255,9 @@ class GoTuiRuntime:
self.controller.scan_state = "completed" if report_status == "completed" else "stopped"
except Exception as exc:
logger.exception("Go TUI scan failed")
+ report_error("unhandled_exception", exc)
+ if self.report_state is not None and self.report_state.scan_ended_exit_reason is None:
+ self.report_state.scan_ended_exit_reason = "error"
self.scan_error = exc
self.controller.error = str(exc)
self.controller.scan_state = "failed"
diff --git a/strix/interface/utils.py b/strix/interface/utils.py
index faab1772..e051a185 100644
--- a/strix/interface/utils.py
+++ b/strix/interface/utils.py
@@ -19,6 +19,7 @@ from rich.panel import Panel
from rich.text import Text
from strix.config import load_settings
+from strix.telemetry import report_error
from strix.utils.api_spec import detect_spec_format
@@ -1602,7 +1603,8 @@ def check_docker_connection() -> Any:
try:
return docker.from_env()
- except DockerException:
+ except DockerException as exc:
+ report_error("docker_unavailable", exc)
console = Console()
error_text = Text()
error_text.append("DOCKER NOT AVAILABLE", style="bold red")
diff --git a/strix/interface/viewer/frontend/src/App.tsx b/strix/interface/viewer/frontend/src/App.tsx
index 97b22ce7..bbd24f4c 100644
--- a/strix/interface/viewer/frontend/src/App.tsx
+++ b/strix/interface/viewer/frontend/src/App.tsx
@@ -8,6 +8,7 @@ import {
Radar,
Rocket,
ArrowUpRight,
+ Building2,
History,
} from "lucide-react";
import type { Vulnerability, VulnerabilitySeverity } from "@/types/issues";
@@ -35,7 +36,7 @@ import {
type LoadedRun,
type RunsPayload,
} from "@/data/serverSource";
-import { SIGNUP_URL, ctaUrl, trackCta } from "@/lib/cta";
+import { SIGNUP_URL, DEMO_URL, ctaUrl, trackCta } from "@/lib/cta";
import { runTitle } from "@/lib/target-utils";
import Sidebar from "@/components/Sidebar";
import PastRunsView from "@/components/PastRunsView";
@@ -706,6 +707,31 @@ function OverviewTab({
)}
+ {finished && (
+
Strix Cloud
+Run your next pentest in Strix Cloud.
+Run this pentest with more depth
-Re-run this pentest on managed infra in the cloud.
+Run this pentest again in Strix Cloud.
+ SSO, compliance reports, or a private deployment?{" "} + trackCta("upgrade_book_demo", source)} + className="whitespace-nowrap text-[#aaa] underline underline-offset-2 transition-colors hover:text-white" + > + Try Strix Enterprise + +
diff --git a/strix/interface/viewer/static/assets/index-Bpn8GiSb.js b/strix/interface/viewer/static/assets/index-B94ANU8d.js similarity index 65% rename from strix/interface/viewer/static/assets/index-Bpn8GiSb.js rename to strix/interface/viewer/static/assets/index-B94ANU8d.js index 478501f7..8e12932b 100644 --- a/strix/interface/viewer/static/assets/index-Bpn8GiSb.js +++ b/strix/interface/viewer/static/assets/index-B94ANU8d.js @@ -1,4 +1,4 @@ -(function(){const t=document.createElement("link").relList;if(t&&t.supports&&t.supports("modulepreload"))return;for(const s of document.querySelectorAll('link[rel="modulepreload"]'))a(s);new MutationObserver(s=>{for(const o of s)if(o.type==="childList")for(const c of o.addedNodes)c.tagName==="LINK"&&c.rel==="modulepreload"&&a(c)}).observe(document,{childList:!0,subtree:!0});function r(s){const o={};return s.integrity&&(o.integrity=s.integrity),s.referrerPolicy&&(o.referrerPolicy=s.referrerPolicy),s.crossOrigin==="use-credentials"?o.credentials="include":s.crossOrigin==="anonymous"?o.credentials="omit":o.credentials="same-origin",o}function a(s){if(s.ep)return;s.ep=!0;const o=r(s);fetch(s.href,o)}})();function Ao(e){return e&&e.__esModule&&Object.prototype.hasOwnProperty.call(e,"default")?e.default:e}var fh={exports:{}},Xl={};/** +(function(){const t=document.createElement("link").relList;if(t&&t.supports&&t.supports("modulepreload"))return;for(const s of document.querySelectorAll('link[rel="modulepreload"]'))a(s);new MutationObserver(s=>{for(const o of s)if(o.type==="childList")for(const c of o.addedNodes)c.tagName==="LINK"&&c.rel==="modulepreload"&&a(c)}).observe(document,{childList:!0,subtree:!0});function r(s){const o={};return s.integrity&&(o.integrity=s.integrity),s.referrerPolicy&&(o.referrerPolicy=s.referrerPolicy),s.crossOrigin==="use-credentials"?o.credentials="include":s.crossOrigin==="anonymous"?o.credentials="omit":o.credentials="same-origin",o}function a(s){if(s.ep)return;s.ep=!0;const o=r(s);fetch(s.href,o)}})();function Mo(e){return e&&e.__esModule&&Object.prototype.hasOwnProperty.call(e,"default")?e.default:e}var hh={exports:{}},Xl={};/** * @license React * react-jsx-runtime.production.js * @@ -6,7 +6,7 @@ * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. - */var Q0;function Mk(){if(Q0)return Xl;Q0=1;var e=Symbol.for("react.transitional.element"),t=Symbol.for("react.fragment");function r(a,s,o){var c=null;if(o!==void 0&&(c=""+o),s.key!==void 0&&(c=""+s.key),"key"in s){o={};for(var d in s)d!=="key"&&(o[d]=s[d])}else o=s;return s=o.ref,{$$typeof:e,type:a,key:c,ref:s!==void 0?s:null,props:o}}return Xl.Fragment=t,Xl.jsx=r,Xl.jsxs=r,Xl}var W0;function Ok(){return W0||(W0=1,fh.exports=Mk()),fh.exports}var m=Ok(),hh={exports:{}},Ve={};/** + */var J0;function jk(){if(J0)return Xl;J0=1;var e=Symbol.for("react.transitional.element"),t=Symbol.for("react.fragment");function r(a,s,o){var c=null;if(o!==void 0&&(c=""+o),s.key!==void 0&&(c=""+s.key),"key"in s){o={};for(var d in s)d!=="key"&&(o[d]=s[d])}else o=s;return s=o.ref,{$$typeof:e,type:a,key:c,ref:s!==void 0?s:null,props:o}}return Xl.Fragment=t,Xl.jsx=r,Xl.jsxs=r,Xl}var ey;function Dk(){return ey||(ey=1,hh.exports=jk()),hh.exports}var m=Dk(),mh={exports:{}},Ve={};/** * @license React * react.production.js * @@ -14,7 +14,7 @@ * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. - */var J0;function Rk(){if(J0)return Ve;J0=1;var e=Symbol.for("react.transitional.element"),t=Symbol.for("react.portal"),r=Symbol.for("react.fragment"),a=Symbol.for("react.strict_mode"),s=Symbol.for("react.profiler"),o=Symbol.for("react.consumer"),c=Symbol.for("react.context"),d=Symbol.for("react.forward_ref"),h=Symbol.for("react.suspense"),f=Symbol.for("react.memo"),p=Symbol.for("react.lazy"),g=Symbol.for("react.activity"),b=Symbol.iterator;function y(L){return L===null||typeof L!="object"?null:(L=b&&L[b]||L["@@iterator"],typeof L=="function"?L:null)}var _={isMounted:function(){return!1},enqueueForceUpdate:function(){},enqueueReplaceState:function(){},enqueueSetState:function(){}},N=Object.assign,S={};function w(L,F,D){this.props=L,this.context=F,this.refs=S,this.updater=D||_}w.prototype.isReactComponent={},w.prototype.setState=function(L,F){if(typeof L!="object"&&typeof L!="function"&&L!=null)throw Error("takes an object of state variables to update or a function which returns an object of state variables.");this.updater.enqueueSetState(this,L,F,"setState")},w.prototype.forceUpdate=function(L){this.updater.enqueueForceUpdate(this,L,"forceUpdate")};function C(){}C.prototype=w.prototype;function E(L,F,D){this.props=L,this.context=F,this.refs=S,this.updater=D||_}var A=E.prototype=new C;A.constructor=E,N(A,w.prototype),A.isPureReactComponent=!0;var B=Array.isArray;function R(){}var H={H:null,A:null,T:null,S:null},z=Object.prototype.hasOwnProperty;function Y(L,F,D){var V=D.ref;return{$$typeof:e,type:L,key:F,ref:V!==void 0?V:null,props:D}}function j(L,F){return Y(L.type,F,L.props)}function I(L){return typeof L=="object"&&L!==null&&L.$$typeof===e}function Z(L){var F={"=":"=0",":":"=2"};return"$"+L.replace(/[=:]/g,function(D){return F[D]})}var P=/\/+/g;function k(L,F){return typeof L=="object"&&L!==null&&L.key!=null?Z(""+L.key):F.toString(36)}function $(L){switch(L.status){case"fulfilled":return L.value;case"rejected":throw L.reason;default:switch(typeof L.status=="string"?L.then(R,R):(L.status="pending",L.then(function(F){L.status==="pending"&&(L.status="fulfilled",L.value=F)},function(F){L.status==="pending"&&(L.status="rejected",L.reason=F)})),L.status){case"fulfilled":return L.value;case"rejected":throw L.reason}}throw L}function O(L,F,D,V,q){var Q=typeof L;(Q==="undefined"||Q==="boolean")&&(L=null);var J=!1;if(L===null)J=!0;else switch(Q){case"bigint":case"string":case"number":J=!0;break;case"object":switch(L.$$typeof){case e:case t:J=!0;break;case p:return J=L._init,O(J(L._payload),F,D,V,q)}}if(J)return q=q(L),J=V===""?"."+k(L,0):V,B(q)?(D="",J!=null&&(D=J.replace(P,"$&/")+"/"),O(q,F,D,"",function(oe){return oe})):q!=null&&(I(q)&&(q=j(q,D+(q.key==null||L&&L.key===q.key?"":(""+q.key).replace(P,"$&/")+"/")+J)),F.push(q)),1;J=0;var W=V===""?".":V+":";if(B(L))for(var te=0;teh?f-h:void 0;return dy(o,y,b,_)};if(t){const s=t+uy,o=a;a=c=>c.startsWith(s)?o(c.slice(s.length)):dy(rA,!1,c,void 0,!0)}if(r){const s=a;a=o=>r({className:o,parseClassName:s})}return a},aA=e=>{const t=new Map;return e.orderSensitiveModifiers.forEach((r,a)=>{t.set(r,1e6+a)}),r=>{const a=[];let s=[];for(let o=0;o h?f-h:void 0;return hy(o,y,b,_)};if(t){const s=t+fy,o=a;a=c=>c.startsWith(s)?o(c.slice(s.length)):hy(sA,!1,c,void 0,!0)}if(r){const s=a;a=o=>r({className:o,parseClassName:s})}return a},oA=e=>{const t=new Map;return e.orderSensitiveModifiers.forEach((r,a)=>{t.set(r,1e6+a)}),r=>{const a=[];let s=[];for(let o=0;oc))return;const H=t.events.length;let z=H,Y,j;for(;z--;)if(t.events[z][0]==="exit"&&t.events[z][1].type==="chunkFlow"){if(Y){j=t.events[z][1].end;break}Y=!0}for(w(a),R=H;R{if(typeof e=="string"){tA(e,t,r);return}if(typeof e=="function"){nA(e,t,r,a);return}rA(e,t,r,a)},tA=(e,t,r)=>{const a=e===""?t:iw(t,e);a.classGroupId=r},nA=(e,t,r,a)=>{if(iA(e)){Mp(e(a),t,r,a);return}t.validators===null&&(t.validators=[]),t.validators.push(XT(r,e))},rA=(e,t,r,a)=>{const s=Object.entries(e),o=s.length;for(let c=0;c"isThemeGetter"in e&&e.isThemeGetter===!0,aA=e=>{if(e<1)return{get:()=>{},set:()=>{}};let t=0,r=Object.create(null),a=Object.create(null);const s=(o,c)=>{r[o]=c,t++,t>e&&(t=0,a=r,r=Object.create(null))};return{get(o){let c=r[o];if(c!==void 0)return c;if((c=a[o])!==void 0)return s(o,c),c},set(o,c){o in r?r[o]=c:s(o,c)}}},Vm="!",fy=":",sA=[],hy=(e,t,r,a,s)=>({modifiers:e,hasImportantModifier:t,baseClassName:r,maybePostfixModifierPosition:a,isExternal:s}),lA=e=>{const{prefix:t,experimentalParseClassName:r}=e;let a=s=>{const o=[];let c=0,d=0,h=0,f;const p=s.length;for(let N=0;Nc))return;const H=t.events.length;let z=H,Y,j;for(;z--;)if(t.events[z][0]==="exit"&&t.events[z][1].type==="chunkFlow"){if(Y){j=t.events[z][1].end;break}Y=!0}for(w(a),R=H;R