From 59f49a1fa2fe4a336ebb41ef8e6ce3034d5fee56 Mon Sep 17 00:00:00 2001 From: Ahmed Allam Date: Tue, 21 Jul 2026 18:26:32 +0000 Subject: [PATCH 01/12] fix(prompt): make root agent orchestrate-only and fold fixing into reporting --- strix/agents/prompt.py | 1 + strix/agents/prompts/system_prompt.jinja | 37 +++++++++++++++--------- strix/skills/coordination/root_agent.md | 7 ++--- 3 files changed, 28 insertions(+), 17 deletions(-) diff --git a/strix/agents/prompt.py b/strix/agents/prompt.py index 8000eb09..20f10d0f 100644 --- a/strix/agents/prompt.py +++ b/strix/agents/prompt.py @@ -91,6 +91,7 @@ def render_system_prompt( loaded_skill_names=list(skill_content.keys()), available_skills=get_available_skills(), interactive=interactive, + is_root=is_root, system_prompt_context=system_prompt_context or {}, **skill_content, ) diff --git a/strix/agents/prompts/system_prompt.jinja b/strix/agents/prompts/system_prompt.jinja index 0ebd7c79..8ae6632b 100644 --- a/strix/agents/prompts/system_prompt.jinja +++ b/strix/agents/prompts/system_prompt.jinja @@ -1,5 +1,14 @@ You are Strix, an advanced AI application security validation agent developed by OmniSecure Labs. Your purpose is to perform authorized security verification, reproduce and validate weaknesses on in-scope assets, and help remediate real security issues. You follow all instructions and rules provided to you exactly as written in the system prompt at all times. +{% if is_root %} + +YOU ARE THE ROOT AGENT. Your job is ORCHESTRATION, not hands-on testing. +- You accomplish security work by DELEGATING to specialized subagents via create_agent — you do NOT run scanners, crawlers, fuzzers, or send exploit/injection payloads yourself. +- IMPORTANT — how to read this prompt as root: the rest of this system prompt is written in the second person ("you") and describes the hands-on testing methodology (recon, mapping, scanning, payload spraying, PoC building, fixing). When you are the root agent, treat every such hands-on instruction as something you ensure gets done BY A SUBAGENT, not as a task you perform in your own turns. The "map the target", "recon first", "mandatory initial phases", and "spray payloads" directives are DELEGATION REQUIREMENTS for you — spawn recon/mapping/testing subagents to satisfy them. +- Do NOT probe endpoints, run "basic" or "quick" injection/XSS/etc. tests, or do exploratory scanning before delegating. Even a single quick test on a discovered endpoint is out of role: spin up a subagent instead. +- Your own turns should be spent on: reading scope/config, decomposing the target, spawning and monitoring subagents, tracking todos/notes/coverage, deciding next steps, and aggregating results into the final report. + +{% endif %} - Security assessment and vulnerability scanning @@ -125,10 +134,8 @@ WHITE-BOX TESTING (code provided): - Local execution, unit/integration testing, patch verification, and HTTP requests against locally started in-scope services are normal authorized white-box validation - If dynamically running the code proves impossible after exhaustive attempts, pivot to comprehensive static analysis. - Try to infer how to run the code based on its structure and content. -- FIX discovered vulnerabilities in code in same file. -- Test patches to confirm vulnerability removal. -- Do not stop until all reported vulnerabilities are fixed. -- Include code diff in final report. +- Derive the code fix as PART OF reporting, not as a separate later pass: create_vulnerability_report already requires the concrete patch inline (`code_locations` with verbatim `fix_before`/`fix_after` and `fix_pr_body`), so the reporting agent that analyzes the root cause is the one that produces the fix. Do NOT spawn a downstream agent afterwards to re-derive/re-apply the same patch. +- If you also apply and verify the patch in the repo (edit the file, re-test that the vulnerability is gone), do it in the same agent/turn while the analysis is fresh — right before or as part of filing the report — never as a second re-analysis pass. COMBINED MODE (code + deployed target present): - Treat this as static analysis plus dynamic testing simultaneously @@ -206,7 +213,7 @@ VALIDATION REQUIREMENTS: - Document complete attack chain - Keep going until you find something that matters - A vulnerability is ONLY considered reported when a reporting agent uses create_vulnerability_report (or create_dependency_report for known-CVE dependency/supply-chain findings) with full details. Mentions in agent_finish, finish_scan, or generic messages are NOT sufficient -- Do NOT patch/fix before reporting: first create the vulnerability report via create_vulnerability_report (by the reporting agent). Only after reporting is completed should fixing/patching proceed +- Reporting and fixing are ONE step, not two: when source is available, the reporting agent derives the concrete fix and files it INLINE via create_vulnerability_report (`code_locations` with `fix_before`/`fix_after` + `fix_pr_body`) — the report is not complete without it. Do NOT report first and then spawn a separate downstream agent to re-derive and re-apply the same patch; that just re-does the analysis and wastes tokens. (Do not silently patch a finding WITHOUT filing a report — the report, with its embedded fix, is the deliverable.) - DEDUPLICATION: The create_vulnerability_report tool uses LLM-based deduplication. If it rejects your report as a duplicate, DO NOT attempt to re-submit the same vulnerability. Accept the rejection and move on to testing other areas. The vulnerability has already been reported by another agent @@ -262,7 +269,9 @@ DISK & SCRATCH HYGIENE: - If disk gets tight or a write fails for space, check what's large under /workspace and clean up files from your own task; leave another agent's files unless you've confirmed they're no longer in use MANDATORY INITIAL PHASES: - +{% if is_root %} +- ROOT AGENT: these phases are mandatory for the assessment, but you MUST accomplish them by delegating to reconnaissance/mapping subagents — do NOT run recon, crawling, enumeration, or mapping tools in your own turns. Spawn the appropriate subagent(s) and track their coverage. +{% endif %} BLACK-BOX TESTING - PHASE 1 (RECON & MAPPING): - COMPLETE full reconnaissance: subdomain enumeration, port scanning, service detection - MAP entire attack surface: all endpoints, parameters, APIs, forms, inputs @@ -291,13 +300,14 @@ ROOT AGENT ROLE: - The root agent should coordinate strategy, delegate meaningful work, track progress, maintain todo lists, maintain notes, monitor subagent results, and decide next steps - The root agent should keep a clear view of overall coverage, uncovered attack surfaces, validation status, and reporting/fixing progress - The root agent should avoid spending its own iterations on detailed testing, payload execution, or deep target-specific investigation when that work can be delegated to specialized subagents -- The root agent may do lightweight triage, quick verification, or setup work when necessary to unblock delegation, but its default mode should be coordinator/controller +- The root agent may do orchestration-support work needed to delegate well — reading scope/config, inspecting workspace layout, reading subagent output/reports, and light bookkeeping. It must NOT do the actual security testing itself: no running scanners/fuzzers/crawlers, no sending injection/XSS/SSRF/etc. payloads, and no "basic" or "quick" probing of discovered endpoints. If a check requires touching the target, delegate it to a subagent rather than doing it yourself +- Its default and near-exclusive mode is coordinator/controller - Subagents should do the substantive testing, validation, reporting, and fixing work - The root agent is responsible for ensuring that work is broken down clearly, tracked, and completed across the agent tree 1. **CREATE AGENTS SELECTIVELY** - Spawn subagents when delegation materially improves parallelism, specialization, coverage, or independent validation. Deeper delegation is allowed when the child has a meaningfully different responsibility from the parent. Do not spawn subagents for trivial continuation of the same narrow task. 2. **BLACK-BOX**: Discovery → Validation → Reporting (3 agents per vulnerability) -3. **WHITE-BOX**: Discovery → Validation → Reporting → Fixing (4 agents per vulnerability) +3. **WHITE-BOX**: Discovery → Validation → Reporting-with-fix (3 agents per vulnerability — the reporting agent derives and files the fix inline; do NOT add a separate fixing agent that re-derives the same patch) 4. **MULTIPLE VULNS = MULTIPLE CHAINS** - Each vulnerability finding gets its own validation chain 5. **CREATE AGENTS AS YOU GO** - Don't create all agents at start, create them when you discover new attack surfaces 6. **ONE JOB PER AGENT** - Each agent has ONE specific task only @@ -316,8 +326,7 @@ BLACK-BOX (domain/URL only): WHITE-BOX (source code provided): - Found authentication code issues? → Create authentication analysis agent - Auth agent finds potential vulnerability? → Create "Auth Validation Agent" -- Validation agent confirms vulnerability? → Create "Auth Reporting Agent" -- Reporting agent documents vulnerability? → Create "Auth Fixing Agent" (implement code fix and test it works) +- Validation agent confirms vulnerability? → Create "Auth Reporting Agent" that files the report AND its inline fix (`code_locations` + `fix_pr_body`) in one shot — no separate fixing agent VULNERABILITY WORKFLOW (MANDATORY FOR EVERY FINDING): @@ -338,9 +347,11 @@ Authentication Code Agent finds weak password validation ↓ Spawns "Auth Validation Agent" (proves it's exploitable) ↓ -If valid → Spawns "Auth Reporting Agent" (creates vulnerability report) +If valid → Spawns "Auth Reporting Agent" (creates the vulnerability report + WITH the fix inline: code_locations fix_before/fix_after + fix_pr_body, + applying/verifying the patch in the same turn if desired) ↓ -Spawns "Auth Fixing Agent" (implements secure code fix) +STOP - no separate fixing agent; the fix was derived once, at report time ``` CRITICAL RULES: @@ -376,7 +387,7 @@ FOCUS PRINCIPLES: REALISTIC TESTING OUTCOMES: - **No Findings**: Agent completes testing but finds no vulnerabilities - **Validation Failed**: Initial finding was false positive, validation agent confirms it's not exploitable -- **Valid Vulnerability**: Validation succeeds, spawns reporting agent and then fixing agent (white-box) +- **Valid Vulnerability**: Validation succeeds, spawns a reporting agent that files the report with the fix inline (white-box) — no separate fixing agent PERSISTENCE IS MANDATORY: - Real vulnerabilities take TIME - expect to need 2000+ steps minimum diff --git a/strix/skills/coordination/root_agent.md b/strix/skills/coordination/root_agent.md index 981c3b97..778e6d87 100644 --- a/strix/skills/coordination/root_agent.md +++ b/strix/skills/coordination/root_agent.md @@ -5,7 +5,7 @@ description: Orchestration layer that coordinates specialized subagents for secu # Root Agent -Orchestration layer for security assessments. This agent coordinates specialized subagents but does not perform testing directly. +Orchestration layer for security assessments. This agent coordinates specialized subagents but does not perform testing directly. You never run scanners, crawlers, or fuzzers and never send exploit/injection payloads yourself — not even a quick "basic" test on a discovered endpoint. Any work that touches the target is delegated to a subagent. You can create agents throughout the testing process—not just at the beginning. Spawn agents dynamically based on findings and evolving scope. @@ -18,7 +18,7 @@ You can create agents throughout the testing process—not just at the beginning ## Scope Decomposition -Before spawning agents, analyze the target: +Before spawning agents, analyze the target from the scan config/scope and any provided context (and, once recon subagents report, from their results) — not by running recon tools yourself: 1. **Identify attack surfaces** - web apps, APIs, infrastructure, etc. 2. **Define boundaries** - in-scope domains, IP ranges, excluded assets @@ -72,8 +72,7 @@ Before creating agents: Complex findings warrant specialized subagents: - Discovery agent finds potential vulnerability - Validation agent confirms exploitability -- Reporting agent documents with reproduction steps -- Fix agent provides remediation (if needed) +- Reporting agent documents with reproduction steps AND supplies the fix inline (the report tool carries the patch via `code_locations`/`fix_pr_body`) — do not add a separate fix agent that re-derives the same patch **Resource Efficiency** From 89a707ff513dee4a680cd9ef02f7a5f92b8d90a4 Mon Sep 17 00:00:00 2001 From: seanturner83 Date: Wed, 22 Jul 2026 15:25:38 +0100 Subject: [PATCH 02/12] =?UTF-8?q?sandbox:=20shrink=20image=207.2GB=20?= =?UTF-8?q?=E2=86=92=203.8GB=20(cache=20cleanup,=20multi-stage=20Go=20buil?= =?UTF-8?q?d,=20drop=20ZAP)=20(#474)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- containers/Dockerfile | 62 ++++++++++++++++++------ strix/agents/prompts/system_prompt.jinja | 7 ++- 2 files changed, 50 insertions(+), 19 deletions(-) diff --git a/containers/Dockerfile b/containers/Dockerfile index a2f4f8fa..4b3c84d2 100644 --- a/containers/Dockerfile +++ b/containers/Dockerfile @@ -1,3 +1,26 @@ +# --------------------------------------------------------------------------- +# Builder stage: compile the Go tools here so the Go toolchain (~225MB) and the +# module/build caches never reach the runtime image. The resulting binaries are +# statically linked and copied into the final stage. +# --------------------------------------------------------------------------- +FROM kalilinux/kali-rolling:latest AS gobuilder + +RUN apt-get update && \ + apt-get install -y kali-archive-keyring && \ + apt-get update && \ + apt-get install -y --no-install-recommends golang-go git ca-certificates + +ENV GOBIN=/out/bin +RUN mkdir -p /out/bin && \ + go install -v github.com/projectdiscovery/httpx/cmd/httpx@latest && \ + go install -v github.com/projectdiscovery/katana/cmd/katana@latest && \ + go install -v github.com/projectdiscovery/cvemap/cmd/vulnx@latest && \ + go install -v github.com/jaeles-project/gospider@latest && \ + go install -v github.com/projectdiscovery/interactsh/cmd/interactsh-client@latest + +# --------------------------------------------------------------------------- +# Runtime stage +# --------------------------------------------------------------------------- FROM kalilinux/kali-rolling:latest LABEL description="AI Agent Penetration Testing Environment with Comprehensive Automated Tools" @@ -19,14 +42,13 @@ RUN apt-get update && \ apt-get install -y --no-install-recommends \ wget curl git vim nano unzip tar \ apt-transport-https ca-certificates gnupg lsb-release \ - build-essential software-properties-common \ - gcc libc6-dev pkg-config libpcap-dev libssl-dev \ - python3 python3-pip python3-dev python3-venv python3-setuptools \ - golang-go \ + software-properties-common \ + gcc libc6-dev \ + python3 python3-pip python3-venv python3-setuptools \ net-tools dnsutils whois \ file xxd \ jq parallel ripgrep grep \ - less man-db procps htop \ + less procps htop \ iproute2 iputils-ping netcat-traditional \ nmap ncat ndiff \ sqlmap nuclei subfinder naabu ffuf \ @@ -66,11 +88,8 @@ RUN curl -LsSf https://astral.sh/uv/install.sh | env UV_INSTALL_DIR=/usr/local/b USER pentester WORKDIR /tmp -RUN go install -v github.com/projectdiscovery/httpx/cmd/httpx@latest && \ - go install -v github.com/projectdiscovery/katana/cmd/katana@latest && \ - go install -v github.com/projectdiscovery/cvemap/cmd/vulnx@latest && \ - go install -v github.com/jaeles-project/gospider@latest && \ - go install -v github.com/projectdiscovery/interactsh/cmd/interactsh-client@latest +# Go tools are built in the gobuilder stage; copy the static binaries only. +COPY --from=gobuilder --chown=pentester:pentester /out/bin/ /home/pentester/go/bin/ RUN nuclei -update-templates @@ -87,7 +106,10 @@ RUN npm install -g retire@latest && \ npm install -g js-beautify@latest && \ npm install -g @ast-grep/cli@latest && \ npm install -g tree-sitter-cli@latest && \ - npm install -g agent-browser@0.26.0 + npm install -g agent-browser@0.26.0 && \ + npm cache clean --force && \ + # ast-grep ships two identical binaries (`ast-grep` and `sg`); dedupe (~52MB) + ln -sf ast-grep /home/pentester/.npm-global/lib/node_modules/@ast-grep/cli/sg ENV AGENT_BROWSER_EXECUTABLE_PATH=/usr/bin/chromium ENV AGENT_BROWSER_USER_AGENT="Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36" @@ -132,7 +154,14 @@ RUN git clone https://github.com/aravind0x7/JS-Snooper.git && \ USER root -RUN curl -sSfL https://raw.githubusercontent.com/trufflesecurity/trufflehog/main/scripts/install.sh | sh -s -- -b /usr/local/bin +# Install trufflehog into a pentester-owned dir on PATH so its runtime self-update +# (which replaces the binary in place) succeeds: as non-root `pentester` it cannot +# overwrite a root-owned binary under /usr/local/bin, which otherwise fails with +# "cannot move binary" and aborts the scan. Pin the initial version for +# reproducible builds; self-update then pulls fresh detectors at runtime. +ARG TRUFFLEHOG_VERSION=3.95.9 +RUN curl -sSfL https://raw.githubusercontent.com/trufflesecurity/trufflehog/main/scripts/install.sh | sh -s -- -b /home/pentester/.local/bin "v${TRUFFLEHOG_VERSION}" && \ + chown -R pentester:pentester /home/pentester/.local RUN set -eux; \ ARCH="$(uname -m)"; \ case "$ARCH" in \ @@ -146,8 +175,6 @@ RUN set -eux; \ install -m 0755 /tmp/gitleaks /usr/local/bin/gitleaks; \ rm -f /tmp/gitleaks /tmp/gitleaks.tgz -RUN apt-get update && apt-get install -y zaproxy - RUN curl -sfL https://raw.githubusercontent.com/aquasecurity/trivy/main/contrib/install.sh | sh -s -- -b /usr/local/bin RUN apt-get install -y wapiti @@ -163,7 +190,12 @@ USER root RUN apt-get autoremove -y && \ apt-get autoclean && \ - rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* + rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* && \ + # Purge non-English locales (~160MB) + find /usr/share/locale -mindepth 1 -maxdepth 1 -type d \ + ! -name 'en' ! -name 'en_US' ! -name 'C' -exec rm -rf {} + && \ + # Remove package documentation and man pages not needed at runtime (~95MB) + rm -rf /usr/share/doc/* /usr/share/doc-base/* /usr/share/man/* ENV PATH="/home/pentester/go/bin:/home/pentester/.local/bin:/home/pentester/.npm-global/bin:/app/.venv/bin:$PATH" ENV VIRTUAL_ENV="/app/.venv" diff --git a/strix/agents/prompts/system_prompt.jinja b/strix/agents/prompts/system_prompt.jinja index 8ae6632b..39079c52 100644 --- a/strix/agents/prompts/system_prompt.jinja +++ b/strix/agents/prompts/system_prompt.jinja @@ -196,7 +196,7 @@ EFFICIENCY TACTICS: - For Caido proxy automation inside Python, explicitly import from `caido_api`: `from caido_api import list_requests, view_request, repeat_request, list_sitemap, view_sitemap_entry, scope_rules` -- Prefer established fuzzers/scanners where applicable: ffuf, sqlmap, zaproxy, nuclei, wapiti, arjun, httpx, katana, semgrep, bandit, trufflehog, nmap. Use scripts mainly to coordinate or validate around them, not to replace them without reason +- Prefer established fuzzers/scanners where applicable: ffuf, sqlmap, nuclei, wapiti, arjun, httpx, katana, semgrep, bandit, trufflehog, nmap. Use scripts mainly to coordinate or validate around them, not to replace them without reason - For trial-heavy vectors (SQLi, XSS, XXE, SSRF, RCE, auth/JWT, deserialization), DO NOT iterate payloads manually in the browser. Always spray payloads via Python scripts through `exec_command` or terminal tools. - When using established fuzzers/scanners, use the proxy for inspection where helpful - Generate/adapt large payload corpora: combine encodings (URL, unicode, base64), comment styles, wrappers, time-based/differential probes. Expand with wordlists/templates @@ -412,7 +412,6 @@ VULNERABILITY ASSESSMENT: - nuclei - Vulnerability scanner with templates - sqlmap - SQL injection detection/exploitation - trivy - Container/dependency vulnerability scanner -- zaproxy - OWASP ZAP web app scanner - wapiti - Web vulnerability scanner WEB FUZZING & DISCOVERY: @@ -450,10 +449,10 @@ PROXY & INTERCEPTION: - Ignore Caido proxy-generated 50x HTML error pages; these are proxy issues (might happen when requesting a wrong host or SSL/TLS issues, etc). PROGRAMMING: -- Python 3, uv, Go, Node.js/npm +- Python 3, uv, Node.js/npm - Full development environment - Docker is NOT available inside the sandbox. Do not run docker; rely on provided tools to run locally. -- You can install any additional tools/packages needed based on the task/context using package managers (apt, pip, npm, go install, etc.) +- You can install any additional tools/packages needed based on the task/context using package managers (apt, pip, npm, etc.) Directories: - /workspace - where you should work. From ef07bad9452d418a746370cc9e79353ed019b451 Mon Sep 17 00:00:00 2001 From: Ahmed Allam <49919286+0xallam@users.noreply.github.com> Date: Wed, 22 Jul 2026 08:34:52 -0700 Subject: [PATCH 03/12] Local viewer: UI polish and a Feedback & support tab (#847) --- strix/viewer/auth.py | 21 + strix/viewer/frontend/package-lock.json | 82 +-- strix/viewer/frontend/package.json | 1 + strix/viewer/frontend/src/App.tsx | 115 ++-- .../src/components/EmailReportView.tsx | 12 +- .../frontend/src/components/FeatureDetail.tsx | 96 --- .../frontend/src/components/FeedbackView.tsx | 134 ++++ .../frontend/src/components/PastRunsView.tsx | 2 +- .../viewer/frontend/src/components/ProCta.tsx | 118 +--- .../frontend/src/components/RunDetails.tsx | 8 +- .../frontend/src/components/Sidebar.tsx | 570 +++++++++++------- .../frontend/src/components/UpgradeModal.tsx | 150 +++++ .../src/components/live/AgentDetailModal.tsx | 84 ++- .../live/tool-renderers/LoadSkillRenderer.tsx | 9 +- .../vulnerability/VulnerabilityDetail.tsx | 68 +-- .../viewer/frontend/src/data/serverSource.ts | 22 +- strix/viewer/frontend/src/index.css | 159 ++++- strix/viewer/frontend/src/lib/pro-features.ts | 174 ------ strix/viewer/server.py | 29 + strix/viewer/static/assets/index-BU_tk5L-.js | 517 ---------------- strix/viewer/static/assets/index-BtBksasm.css | 10 + strix/viewer/static/assets/index-C0NveaV7.css | 10 - strix/viewer/static/assets/index-_2j_QfOq.js | 487 +++++++++++++++ strix/viewer/static/index.html | 4 +- 24 files changed, 1546 insertions(+), 1336 deletions(-) delete mode 100644 strix/viewer/frontend/src/components/FeatureDetail.tsx create mode 100644 strix/viewer/frontend/src/components/FeedbackView.tsx create mode 100644 strix/viewer/frontend/src/components/UpgradeModal.tsx delete mode 100644 strix/viewer/frontend/src/lib/pro-features.ts delete mode 100644 strix/viewer/static/assets/index-BU_tk5L-.js create mode 100644 strix/viewer/static/assets/index-BtBksasm.css delete mode 100644 strix/viewer/static/assets/index-C0NveaV7.css create mode 100644 strix/viewer/static/assets/index-_2j_QfOq.js diff --git a/strix/viewer/auth.py b/strix/viewer/auth.py index 1c7c5fef..c25d12b6 100644 --- a/strix/viewer/auth.py +++ b/strix/viewer/auth.py @@ -207,6 +207,26 @@ def otp_verify(email: str, code: str) -> dict[str, Any]: raise RelayError("unavailable") +def feedback_submit(email: str, message: str) -> None: + """Relay a feedback message + email to Strix. No verification is required; + the email is taken as given. Raises RelayError on failure.""" + status, data = _post_json( + "/api/oss/feedback", + {"email": email, "message": message}, + timeout=_OTP_TIMEOUT, + ) + if status == 200: + return + if status == 429: + raise RelayError("rate_limited") + if status == 400: + code = data.get("error") + if code in ("invalid_email", "invalid_message"): + raise RelayError(str(code)) + raise RelayError("invalid_message") + raise RelayError("unavailable") + + def report_send( token: str, pdf_bytes: bytes, @@ -241,6 +261,7 @@ def report_send( __all__ = [ "AUTH_PATH", "RelayError", + "feedback_submit", "forget", "is_verified", "otp_start", diff --git a/strix/viewer/frontend/package-lock.json b/strix/viewer/frontend/package-lock.json index 1c74cd0c..9f86e348 100644 --- a/strix/viewer/frontend/package-lock.json +++ b/strix/viewer/frontend/package-lock.json @@ -16,6 +16,7 @@ "lucide-react": "^0.563.0", "react": "^19.2.0", "react-dom": "^19.2.0", + "react-icons": "^5.7.0", "react-markdown": "^10.1.0", "remark-gfm": "^4.0.1", "tailwind-merge": "^3.4.0" @@ -62,6 +63,7 @@ "integrity": "sha512-RgHBCvtjbOK2gXSNBNIkNoEc9qoVEtau3hj8gEqKQuL3HZAibKarWFEI3Lfm6EYKkLalOh8eSrj9b+ch9H/VBA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@babel/code-frame": "^7.29.7", "@babel/generator": "^7.29.7", @@ -919,9 +921,6 @@ "arm" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -936,9 +935,6 @@ "arm" ], "dev": true, - "libc": [ - "musl" - ], "license": "MIT", "optional": true, "os": [ @@ -953,9 +949,6 @@ "arm64" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -970,9 +963,6 @@ "arm64" ], "dev": true, - "libc": [ - "musl" - ], "license": "MIT", "optional": true, "os": [ @@ -987,9 +977,6 @@ "loong64" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -1004,9 +991,6 @@ "loong64" ], "dev": true, - "libc": [ - "musl" - ], "license": "MIT", "optional": true, "os": [ @@ -1021,9 +1005,6 @@ "ppc64" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -1038,9 +1019,6 @@ "ppc64" ], "dev": true, - "libc": [ - "musl" - ], "license": "MIT", "optional": true, "os": [ @@ -1055,9 +1033,6 @@ "riscv64" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -1072,9 +1047,6 @@ "riscv64" ], "dev": true, - "libc": [ - "musl" - ], "license": "MIT", "optional": true, "os": [ @@ -1089,9 +1061,6 @@ "s390x" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -1106,9 +1075,6 @@ "x64" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -1123,9 +1089,6 @@ "x64" ], "dev": true, - "libc": [ - "musl" - ], "license": "MIT", "optional": true, "os": [ @@ -1349,9 +1312,6 @@ "arm64" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -1369,9 +1329,6 @@ "arm64" ], "dev": true, - "libc": [ - "musl" - ], "license": "MIT", "optional": true, "os": [ @@ -1389,9 +1346,6 @@ "x64" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -1409,9 +1363,6 @@ "x64" ], "dev": true, - "libc": [ - "musl" - ], "license": "MIT", "optional": true, "os": [ @@ -1654,6 +1605,7 @@ "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.17.tgz", "integrity": "sha512-MXfmqaVPEVgkBT/aY0aGCkRWWtByiYQXo3xdQ8r5RzuFrPiRn8Gar2tQdXSUQ2GKV3bkXckek89V8wQBY2Q/Aw==", "license": "MIT", + "peer": true, "dependencies": { "csstype": "^3.2.2" } @@ -1664,6 +1616,7 @@ "integrity": "sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==", "devOptional": true, "license": "MIT", + "peer": true, "peerDependencies": { "@types/react": "^19.2.0" } @@ -1786,6 +1739,7 @@ } ], "license": "MIT", + "peer": true, "dependencies": { "baseline-browser-mapping": "^2.10.42", "caniuse-lite": "^1.0.30001803", @@ -1966,6 +1920,7 @@ "resolved": "https://registry.npmjs.org/d3-selection/-/d3-selection-3.0.0.tgz", "integrity": "sha512-fmTRWbNMmsmWq6xJV8D19U/gw/bwrHfNXxrIN+HfZgnzqTHp9jOmKMhsTUjXOJnZOdZY9Q28y4yebKzqDKlxlQ==", "license": "ISC", + "peer": true, "engines": { "node": ">=12" } @@ -2543,9 +2498,6 @@ "arm64" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MPL-2.0", "optional": true, "os": [ @@ -2567,9 +2519,6 @@ "arm64" ], "dev": true, - "libc": [ - "musl" - ], "license": "MPL-2.0", "optional": true, "os": [ @@ -2591,9 +2540,6 @@ "x64" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MPL-2.0", "optional": true, "os": [ @@ -2615,9 +2561,6 @@ "x64" ], "dev": true, - "libc": [ - "musl" - ], "license": "MPL-2.0", "optional": true, "os": [ @@ -3628,6 +3571,7 @@ "integrity": "sha512-RvwwcruNjI1ncT5xRakeyS9Lf8lcItv34KD+aif+VH9kduAyfYBipGh12274xtenIPZ119/R9BdTBa8gAwSh0A==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">=12" }, @@ -3679,6 +3623,7 @@ "resolved": "https://registry.npmjs.org/react/-/react-19.2.7.tgz", "integrity": "sha512-HNe9WslTbXmFK8o8cmwgAeJFSBvt1bPdHCVKtaaV+WlAN36mpT4hcRpwbf3fY56ar2oIXzsBpOAiIRHAdY0OlQ==", "license": "MIT", + "peer": true, "engines": { "node": ">=0.10.0" } @@ -3688,6 +3633,7 @@ "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.7.tgz", "integrity": "sha512-t0BRVXvbiE/o20Hfw669rLbMCDWtYZLvmJigy2f0MxsXF+71pxhR3xOkspmsO8h3ZlNzyibAmtCa3l4lYKk6gQ==", "license": "MIT", + "peer": true, "dependencies": { "scheduler": "^0.27.0" }, @@ -3695,6 +3641,15 @@ "react": "^19.2.7" } }, + "node_modules/react-icons": { + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/react-icons/-/react-icons-5.7.0.tgz", + "integrity": "sha512-LBLy340Rzqy6+/yVhZKT3B/QpP1BZaesGqasf09HPOBzRarcDIFH0WwXlXQfE7q7ipxK4MSiC5DIBWURCny6fw==", + "license": "MIT", + "peerDependencies": { + "react": "*" + } + }, "node_modules/react-markdown": { "version": "10.1.0", "resolved": "https://registry.npmjs.org/react-markdown/-/react-markdown-10.1.0.tgz", @@ -4154,6 +4109,7 @@ "integrity": "sha512-NTKlcQjlAK7MlQoyb6LgaqHc8sso/pVyUJYWMws3jg21uTJw/LddqIFPcPqP6PzpgbIcZyKI85sFE4HBrQDA8A==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "esbuild": "^0.25.0", "fdir": "^6.4.4", diff --git a/strix/viewer/frontend/package.json b/strix/viewer/frontend/package.json index 37eaff55..e79db76f 100644 --- a/strix/viewer/frontend/package.json +++ b/strix/viewer/frontend/package.json @@ -17,6 +17,7 @@ "lucide-react": "^0.563.0", "react": "^19.2.0", "react-dom": "^19.2.0", + "react-icons": "^5.7.0", "react-markdown": "^10.1.0", "remark-gfm": "^4.0.1", "tailwind-merge": "^3.4.0" diff --git a/strix/viewer/frontend/src/App.tsx b/strix/viewer/frontend/src/App.tsx index 8afc025c..00c0b9c8 100644 --- a/strix/viewer/frontend/src/App.tsx +++ b/strix/viewer/frontend/src/App.tsx @@ -2,14 +2,10 @@ import { useCallback, useEffect, useMemo, useRef, useState } from "react"; import { ArrowLeft, AlertCircle, - Waypoints, + Bot, Mail, ChevronDown, - Wrench, - FileCheck2, - CalendarClock, Radar, - GitPullRequest, Rocket, ArrowUpRight, History, @@ -45,11 +41,10 @@ import PastRunsView from "@/components/PastRunsView"; import EmailReportView from "@/components/EmailReportView"; import { RunDetails } from "@/components/RunDetails"; import { TrustToast } from "@/components/TrustToast"; -import FeatureDetail from "@/components/FeatureDetail"; -import { ProTile, ProInlineCta, type ProItem } from "@/components/ProCta"; -import { FEATURES } from "@/lib/pro-features"; +import FeedbackView from "@/components/FeedbackView"; +import { ProInlineCta } from "@/components/ProCta"; -export type View = "overview" | "issues" | "agents" | "history" | "feature" | "email"; +export type View = "overview" | "issues" | "agents" | "history" | "email" | "feedback"; const TRUST_BANNER = "Your findings stay on your machine. They're rendered here locally in your browser and never uploaded or stored by Strix."; @@ -57,25 +52,12 @@ const TRUST_BANNER = const SEVERITY_ORDER: VulnerabilitySeverity[] = ["critical", "high", "medium", "low"]; const POLL_MS = 500; -// Curated inline CTAs. Continuous-coverage row on Overview (the restyled upsell -// tiles), plus the recommendations pairing. -const RECOMMENDATION_CTAS: ProItem[] = [ - { title: "One-click autofix + open a fix PR", desc: "Fix it for you and open a PR, retested.", slug: "autofix", icon: Wrench }, - { title: "Export SOC 2 / ISO 27001 report", desc: "Share an auditor-ready report with your team.", slug: "compliance", icon: FileCheck2 }, -]; -const COVERAGE_CTAS: ProItem[] = [ - { title: "Scheduled pentesting", desc: "Continuous coverage for your whole org.", slug: "scheduled", icon: CalendarClock }, - { title: "Attack surface monitoring", desc: "Continuous coverage for your whole org.", slug: "asm", icon: Radar }, - { title: "PR reviews", desc: "Pentest every pull request your team opens.", slug: "pr_reviews", icon: GitPullRequest }, -]; - export default function App() { const [activeRun, setActiveRun] = useState(null); const [run, setRun] = useState(null); const [error, setError] = useState(null); const [selectedId, setSelectedId] = useState(null); const [view, setView] = useState("overview"); - const [activeFeature, setActiveFeature] = useState(null); const [auth, setAuth] = useState(null); const [runs, setRuns] = useState(null); const [emailPurpose, setEmailPurpose] = useState<"report" | "verify">("report"); @@ -249,12 +231,6 @@ export default function App() { await refreshRuns(); }, [refreshAuth, refreshRuns]); - const selectFeature = useCallback((slug: string) => { - trackCta(slug, "sidebar_nav"); - setActiveFeature(slug); - userSetView("feature"); - }, [userSetView]); - const onForget = useCallback(async () => { await forgetAuth(); await refreshAuth(); @@ -266,11 +242,13 @@ export default function App() { { + // Clicking a sidebar view always lands on that section's top level, + // so leaving a specific issue's detail view and clicking "Issues" + // returns to the full findings list. + setSelectedId(null); if (v === "history") openHistory(); else userSetView(v); }} - activeFeature={activeFeature} - onSelectFeature={selectFeature} issuesCount={run?.vulnerabilities.length ?? 0} agentCount={agentCount} runCount={runs?.count ?? 0} @@ -285,7 +263,7 @@ export default function App() {
{/* Top bar */}
-
+
Strix
- Local results {run && }
{verified && runs && !runs.locked && runs.runs.length > 0 && ( @@ -322,14 +299,20 @@ export default function App() {
-
- {error && !run && view !== "history" && view !== "email" && view !== "feature" && ( +
+ {error && !run && view !== "history" && view !== "email" && (
)} + {/* Keyed wrapper: re-mounts on every view / finding / run change so the + page-in transition replays. */} +
{view === "email" ? ( setView(dest === "history" ? "history" : "overview")} /> - ) : view === "feature" && activeFeature && FEATURES[activeFeature] ? ( - + ) : view === "feedback" ? ( + setView(dest)} + /> ) : view === "history" ? (
@@ -412,6 +398,7 @@ export default function App() { )} ) : null} +
@@ -573,7 +560,7 @@ function FindingsList({
- Email report + Export report to PDF
@@ -673,26 +660,32 @@ function OverviewTab({ return (
- +
+ +
{total > 0 && ( -
+
)} {/* Primary CTA: the one primary on Overview. Hidden until the run is finished, since a live scan would only email a partial report. */} - {finished && } + {finished && ( +
+ +
+ )} {sections.length > 0 ? ( -
+
{sections.map((s) => ( ))}
) : reportMarkdown ? ( -
+
) : ( @@ -701,22 +694,6 @@ function OverviewTab({ ) )} - {/* Near Recommendations: act on the fixes. */} -
- {RECOMMENDATION_CTAS.map((item) => ( - - ))} -
- - {/* Continuous coverage for your org (restyled upsell tiles). */} -
-

Continuous coverage for your org

-
- {COVERAGE_CTAS.map((item) => ( - - ))} -
-
); } @@ -746,8 +723,7 @@ function TabButton({ function AgentsTab({ run, canSteer }: { run: LoadedRun; canSteer: boolean }) { const { agents, events } = run.transcript; const graphAgents = useMemo(() => buildGraphAgents(agents, events), [agents, events]); - // Clicking a graph node opens the agent's transcript in a modal (matching the - // cloud app); no node selected means no modal. + // Clicking a graph node opens the agent's transcript in a modal; no node selected means no modal. const [selectedId, setSelectedId] = useState(null); const selectedAgent = selectedId ? (agents.find((a) => a.id === selectedId) ?? null) : null; @@ -758,7 +734,7 @@ function AgentsTab({ run, canSteer }: { run: LoadedRun; canSteer: boolean }) {
-
- {selectedAgent && ( - setSelectedId(null)} - /> - )} + setSelectedId(null)} + />
); } diff --git a/strix/viewer/frontend/src/components/EmailReportView.tsx b/strix/viewer/frontend/src/components/EmailReportView.tsx index 7f84cddf..7c95365f 100644 --- a/strix/viewer/frontend/src/components/EmailReportView.tsx +++ b/strix/viewer/frontend/src/components/EmailReportView.tsx @@ -180,7 +180,7 @@ export default function EmailReportView({ const confirmationEmail = sentTo || auth?.email || email.trim(); return ( -
+
@@ -269,7 +266,6 @@ export default function EmailReportView({ className="w-full rounded-lg bg-black px-3 py-2.5 text-sm text-white outline-none transition-colors focus:border-[#444]" style={{ border: "1px solid #2a2a2a" }} /> - Use your work email.
- ); -} diff --git a/strix/viewer/frontend/src/components/FeedbackView.tsx b/strix/viewer/frontend/src/components/FeedbackView.tsx new file mode 100644 index 00000000..f9d6985f --- /dev/null +++ b/strix/viewer/frontend/src/components/FeedbackView.tsx @@ -0,0 +1,134 @@ +import { useState } from "react"; +import { ArrowLeft, AlertCircle, CheckCircle2 } from "lucide-react"; +import { IoChatbubblesOutline } from "react-icons/io5"; +import { submitFeedback } from "@/data/serverSource"; +import type { View } from "@/App"; + +const MAX_MESSAGE = 5000; + +const ERROR_COPY: Record = { + invalid_email: "That email doesn't look right.", + invalid_message: "Please write a little more.", + unavailable: "Couldn't send that just now. Try again.", +}; + +/** + * Feedback & support form. Collects a message plus a work email (no + * verification — the email is taken as-is) and relays it to Strix via the local + * server. Mirrors EmailReportView's centered-card styling and palette. + */ +export default function FeedbackView({ + defaultEmail, + onExit, +}: { + defaultEmail: string | null; + onExit: (dest: View) => void; +}) { + const [message, setMessage] = useState(""); + const [email, setEmail] = useState(defaultEmail ?? ""); + const [step, setStep] = useState<"form" | "sending" | "sent">("form"); + const [error, setError] = useState(null); + + const canSend = message.trim().length > 0 && email.trim().length > 0 && step !== "sending"; + + const send = async () => { + if (!canSend) return; + setStep("sending"); + setError(null); + const result = await submitFeedback(message.trim(), email.trim()); + if (result.ok) { + setStep("sent"); + return; + } + setStep("form"); + setError(ERROR_COPY[result.error] ?? ERROR_COPY.unavailable); + }; + + return ( +
+ + +
+
+ +
+ {step === "sent" ? ( +
+
+ ) : ( + <> +

+ Bugs, feature requests, or anything else. Tell us what's on your mind. +

+ + {error && ( +
+
+ )} + +