diff --git a/CHANGELOG.md b/CHANGELOG.md index 2dd2b604ff..18b2a82b02 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,26 @@ Format based on [Keep a Changelog](https://keepachangelog.com/), versions follow ## [Unreleased] +### Added + +- Durable, redacted engagement event log with live SSE, correlation IDs, activity timeline/lanes, filters, and JSONL export +- Mission posture and topology panels for methodology coverage, validation, attack chains, agents, assets, hosts, endpoints, findings, and target notes +- Approval-gated Nmap scan profiles, canonical XML ingestion, saved scan history, host/service/route topology, and scan comparison +- Structured project/session memory with FTS5, provenance, trust, confidence, secret redaction, invalidation, failure reflection, and evaluation-gated procedural promotion +- Server-enforced read-only observer role and typed local execution-plane capability inventory +- Version-pinned MCP catalog with manual/optional install states and three additional owner-maintained integrations +- Safe recovery startup with `--safe` and namespaced extension configuration +- Target-selectable source builds and a localhost-only systemd user service template for Kali/Linux deployments + +### Changed + +- MCP tool materialization now filters schemas before conversion and enforces real context-budget eviction + +### Fixed + +- Release and local-binary installs now deploy the bundled HackBrowser worker to the runtime data directory +- Web update checks now honor disabled auto-updates, avoiding false upgrade prompts for managed source builds + ## [1.1.16] — 2026-08-08 ### Added diff --git a/README.md b/README.md index 1843a33519..d7cd88e929 100644 --- a/README.md +++ b/README.md @@ -125,7 +125,7 @@ That's it. CyberStrike launches a TUI in your terminal, asks for your LLM provid > **Already have a Claude Code or OpenAI subscription?** CyberStrike's intelligence layer sits on top of your existing AI subscription. No separate API costs — your current plan powers an entire pentest toolkit. -Explore the full documentation at **[docs.cyberstrike.io](https://docs.cyberstrike.io)** or visit **[cyberstrike.io](https://cyberstrike.io)** for demos and guides. +Explore the full documentation at [**docs.cyberstrike.io**](https://docs.cyberstrike.io) or visit [**cyberstrike.io**](https://cyberstrike.io) for demos and guides. --- @@ -150,31 +150,31 @@ CyberStrike isn't just a wrapper around an LLM. It's an intelligence layer that CyberStrike integrates with the entire AI ecosystem through 23 bundled SDK providers and 150+ providers via the [models.dev](https://models.dev) catalog. Here are the core integrations: -| Provider | Models | Notes | -| ------------------------- | ------------------------ | --------------------------------------- | -| **Anthropic** | Claude 4.5, Claude 4 | Best performance with extended thinking | -| **OpenAI** | GPT-5, GPT-4.1, o3, o4 | Full tool-use + reasoning support | -| **Google** | Gemini 2.5 Pro/Flash | Long context for large codebases | -| **Amazon Bedrock** | All Bedrock models | IAM auth, no API keys needed | -| **Azure OpenAI** | All Azure-hosted models | Enterprise deployments | -| **Google Vertex AI** | Gemini + Claude on GCP | Regional endpoints (EU/US) | -| **GitHub Copilot** | GPT-5, Claude, Gemini | Use your existing Copilot subscription | -| **xAI** | Grok 3, Grok 3 Mini | Real-time data access | -| **Groq** | LLaMA, Mixtral | Ultra-fast inference | -| **Mistral** | Mistral Large, Codestral | European data residency | -| **DeepSeek** | DeepSeek V3, R1 | Cost-effective alternative | -| **Cerebras** | LLaMA on Cerebras | Fastest inference available | -| **Cohere** | Command R+ | RAG-optimized models | -| **OpenRouter** | 300+ models | Single API, any model | -| **Together AI** | Open-source models | Fine-tuning support | -| **DeepInfra** | Open-source models | Pay-per-token, no GPU needed | -| **Perplexity** | Sonar models | Search-augmented generation | -| **Alibaba Cloud** | Qwen, Kimi, DashScope | Chinese model ecosystem | -| **Cloudflare AI Gateway** | Any provider via gateway | Caching, rate limiting, analytics | -| **Ollama** | Any GGUF model | Fully offline, local-only | -| **LM Studio** | Any local model | Desktop GUI + API server | -| **vLLM** | Any HuggingFace model | Self-hosted, GPU-optimized | -| **Any OpenAI-compatible** | — | Custom endpoints welcome | +| Provider | Models | Notes | +| --- | --- | --- | +| **Anthropic** | Claude 4.5, Claude 4 | Best performance with extended thinking | +| **OpenAI** | GPT-5, GPT-4.1, o3, o4 | Full tool-use + reasoning support | +| **Google** | Gemini 2.5 Pro/Flash | Long context for large codebases | +| **Amazon Bedrock** | All Bedrock models | IAM auth, no API keys needed | +| **Azure OpenAI** | All Azure-hosted models | Enterprise deployments | +| **Google Vertex AI** | Gemini + Claude on GCP | Regional endpoints (EU/US) | +| **GitHub Copilot** | GPT-5, Claude, Gemini | Use your existing Copilot subscription | +| **xAI** | Grok 3, Grok 3 Mini | Real-time data access | +| **Groq** | LLaMA, Mixtral | Ultra-fast inference | +| **Mistral** | Mistral Large, Codestral | European data residency | +| **DeepSeek** | DeepSeek V3, R1 | Cost-effective alternative | +| **Cerebras** | LLaMA on Cerebras | Fastest inference available | +| **Cohere** | Command R+ | RAG-optimized models | +| **OpenRouter** | 300+ models | Single API, any model | +| **Together AI** | Open-source models | Fine-tuning support | +| **DeepInfra** | Open-source models | Pay-per-token, no GPU needed | +| **Perplexity** | Sonar models | Search-augmented generation | +| **Alibaba Cloud** | Qwen, Kimi, DashScope | Chinese model ecosystem | +| **Cloudflare AI Gateway** | Any provider via gateway | Caching, rate limiting, analytics | +| **Ollama** | Any GGUF model | Fully offline, local-only | +| **LM Studio** | Any local model | Desktop GUI + API server | +| **vLLM** | Any HuggingFace model | Self-hosted, GPU-optimized | +| **Any OpenAI-compatible** | — | Custom endpoints welcome | > **Air-gapped environments?** Run CyberStrike entirely offline with Ollama or LM Studio. No data leaves your machine — ever. @@ -223,26 +223,26 @@ Your security tools don't have to run on your laptop. Deploy Bolt on one or many Switch between agents with `Tab`. Each one is a domain specialist. -| Agent | Focus | What It Does | -| ---------------------- | ------- | ------------------------------------------------------------------- | -| **cyberstrike** | General | Full-access primary agent — reconnaissance, exploitation, reporting | -| **web-application** | Web | OWASP Top 10, WSTG methodology, API security, session testing | -| **mobile-application** | Mobile | Android/iOS, Frida/Objection, MASTG/MASVS compliance | -| **cloud-security** | Cloud | AWS, Azure, GCP — IAM misconfigs, CIS benchmarks, exposed resources | -| **internal-network** | Network | Active Directory, Kerberos attacks, lateral movement, pivoting | +| Agent | Focus | What It Does | +| --- | --- | --- | +| **cyberstrike** | General | Full-access primary agent — reconnaissance, exploitation, reporting | +| **web-application** | Web | OWASP Top 10, WSTG methodology, API security, session testing | +| **mobile-application** | Mobile | Android/iOS, Frida/Objection, MASTG/MASVS compliance | +| **cloud-security** | Cloud | AWS, Azure, GCP — IAM misconfigs, CIS benchmarks, exposed resources | +| **internal-network** | Network | Active Directory, Kerberos attacks, lateral movement, pivoting | Plus **8 specialized proxy testers** that run automatically on intercepted traffic: -| Tester | What It Tests | -| ------------------------ | ---------------------------------------------------------------------------- | -| **IDOR** | Object-level access control — can user A reach user B's resources? | +| Tester | What It Tests | +| --- | --- | +| **IDOR** | Object-level access control — can user A reach user B's resources? | | **Authorization Bypass** | Vertical privilege escalation — can low-privilege users hit admin endpoints? | -| **Mass Assignment** | Unexpected writable fields — role, price, balance, userId in request bodies | -| **Injection** | SQL, command, LDAP, template injection across all input vectors | -| **Authentication** | Token validation, session fixation, credential exposure | -| **Business Logic** | Price manipulation, coupon reuse, race conditions, workflow bypass | -| **SSRF** | Internal host access via user-controlled URLs or redirect parameters | -| **File Attacks** | Path traversal, unrestricted upload, dangerous file types | +| **Mass Assignment** | Unexpected writable fields — role, price, balance, userId in request bodies | +| **Injection** | SQL, command, LDAP, template injection across all input vectors | +| **Authentication** | Token validation, session fixation, credential exposure | +| **Business Logic** | Price manipulation, coupon reuse, race conditions, workflow bypass | +| **SSRF** | Internal host access via user-controlled URLs or redirect parameters | +| **File Attacks** | Path traversal, unrestricted upload, dangerous file types | Each tester uses a **3-gate confirmation protocol**: execute a baseline request, execute the attack, compare responses. A finding is only reported when there is a measurable, reproducible difference — not on speculation. Duplicate findings (same endpoint + attack vector) are automatically suppressed across the session. @@ -254,12 +254,12 @@ CyberStrike ships with **7,600+ security skill files** — structured, Ed25519-s **Skill categories:** -| Category | Skills | What They Cover | -| ------------------------- | ------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | -| **Attack Methodologies** | 19 | JWT attacks, SSRF, SSTI, race conditions, request smuggling, cache poisoning, CORS, GraphQL, prototype pollution, XXE, WebSocket, subdomain takeover, host header injection, open redirect | -| **Post-Exploitation** | 5 | AWS, Azure, Kubernetes, Windows, macOS privilege escalation and persistence | -| **Compliance Frameworks** | 3 | CIS Benchmarks (AWS/Azure/GCP/K8s), NIST Framework, MITRE ATT&CK (Enterprise, Mobile, ICS) | -| **Domain Knowledge** | 8+ | Active Directory security, web security patterns, recon methodology, CI/CD attacks, Kerberos attacks, eBPF techniques | +| Category | Skills | What They Cover | +| --- | --- | --- | +| **Attack Methodologies** | 19 | JWT attacks, SSRF, SSTI, race conditions, request smuggling, cache poisoning, CORS, GraphQL, prototype pollution, XXE, WebSocket, subdomain takeover, host header injection, open redirect | +| **Post-Exploitation** | 5 | AWS, Azure, Kubernetes, Windows, macOS privilege escalation and persistence | +| **Compliance Frameworks** | 3 | CIS Benchmarks (AWS/Azure/GCP/K8s), NIST Framework, MITRE ATT&CK (Enterprise, Mobile, ICS) | +| **Domain Knowledge** | 8+ | Active Directory security, web security patterns, recon methodology, CI/CD attacks, Kerberos attacks, eBPF techniques | Each skill includes testing procedures, payloads, tool commands, and CWE mappings. Skills are tagged with OWASP WSTG IDs, CIS control IDs, and chain relationships — so agents know which skills to combine for multi-step attack chains. @@ -267,7 +267,7 @@ Each skill includes testing procedures, payloads, tool commands, and CWE mapping ### HackBrowser -> Full documentation: **[docs.cyberstrike.io/docs/tools/hacker-browser](https://docs.cyberstrike.io/docs/tools/hacker-browser/)** +> Full documentation: [**docs.cyberstrike.io/docs/tools/hacker-browser**](https://docs.cyberstrike.io/docs/tools/hacker-browser/) HackBrowser is CyberStrike's built-in Chromium browser. Start it from the TUI with `/hackbrowser`. As you browse, every HTTP request is captured and routed through the proxy-agent pipeline — no manual export, no Burp project files. @@ -305,29 +305,38 @@ Browser ──HTTPS──▶ Cloudflare Tunnel ──encrypted──▶ cloudfla ```bash export CYBERSTRIKE_SERVER_PASSWORD=your-secure-password +# Optional API/viewer credential with a strict read-only route allowlist: +export CYBERSTRIKE_OBSERVER_PASSWORD=your-observer-password cyberstrike web # In another terminal: cloudflared tunnel --url http://localhost:4096 run your-tunnel ``` +If user or project configuration prevents startup, run `cyberstrike web --safe` to start recovery mode without those config sources. Managed administrator policy is still enforced. + **Why this is secure:** - **Zero open ports** — CyberStrike binds to `localhost:4096`. `cloudflared` makes an outbound-only connection to Cloudflare's edge. No firewall rules, no port forwarding needed. - **End-to-end encryption** — Browser to Cloudflare edge is TLS. Cloudflare edge to your machine is an encrypted tunnel. No plaintext leaves your network. - **Password-protected API** — Every API request requires Basic Auth. Local requests on `localhost` bypass auth for convenience; remote requests via CF tunnel always require credentials (detects `X-Forwarded-For` / `CF-Connecting-IP`). +- **Read-only observers** — The optional `observer` account can read redacted activity, mission posture, topology, findings, and status, but cannot access configuration, secrets, raw events, PTYs, WebSockets, or mutation routes. - **Your data stays local** — LLM inference runs on your hardware. CyberStrike processes everything locally. The tunnel is just a secure pipe. **What's in the Web UI:** -| Tab | What It Does | -| ------------------- | ------------------------------------------------------------------------------ | -| **Chat** | Full conversation with all 13+ security agents | -| **MCP** | Live MCP server status, health, and tool counts | -| **Bolt** | Bolt remote server connection monitoring | -| **Vulnerabilities** | Discovered vulns with severity, PoC, and impact | -| **Web Context** | Endpoints, roles, credentials, and functions discovered during active sessions | - -**[app.cyberstrike.io](https://app.cyberstrike.io)** is a hosted static page (no backend, no data storage) for convenience. Or self-host: clone the repo and serve `packages/app/dist/` from your own domain. +| Tab | What It Does | +| --- | --- | +| **Chat** | Full conversation with all 13+ security agents | +| **MCP** | Live MCP server status, health, and tool counts | +| **Bolt** | Bolt remote server connection monitoring | +| **Vulnerabilities** | Discovered vulns with severity, PoC, and impact | +| **Web Context** | Endpoints, roles, credentials, and functions discovered during active sessions | +| **Mission** | Methodology phases, coverage, blockers, attack chains, agents, and safe CTAs | +| **Topology** | Evidence-linked assets, Nmap hosts/services/routes, scan history/diffs, endpoints, identities, and findings | +| **Activity** | Durable Agent/Tool/MCP/Bolt/Browser/PTY lanes with filtering and JSONL export | +| **Memory** | Trust-ranked structured memory, FTS search, redaction, notes, and invalidation | + +[**app.cyberstrike.io**](https://app.cyberstrike.io) is a hosted static page (no backend, no data storage) for convenience. Or self-host: clone the repo and serve `packages/app/dist/` from your own domain. --- @@ -362,16 +371,23 @@ Bolt is CyberStrike's remote tool server. Deploy it on any VPS, cloud instance, ### MCP Ecosystem -CyberStrike connects to specialized MCP servers that extend its capabilities — **176+ security tools** across 5 domains: +CyberStrike includes a curated MCP catalog with roughly **724 direct/composite security tools** across 11 default entries: -| Server | Tools | What It Adds | -| ---------------------------------------------------------------------- | ----- | -------------------------------------------------------------------- | -| [cloud-audit-mcp](https://github.com/badchars/cloud-audit-mcp) | 38 | Cloud security audits — 60+ checks across AWS, Azure, GCP | -| [github-security-mcp](https://github.com/badchars/github-security-mcp) | 39 | GitHub security posture — repo, org, actions, secrets, supply chain | -| [cve-mcp](https://github.com/badchars/cve-mcp) | 23 | CVE intelligence — NVD, EPSS, CISA KEV, GitHub Advisory, OSV | -| [osint-mcp](https://github.com/badchars/osint-mcp) | 37 | OSINT recon — Shodan, VirusTotal, SecurityTrails, Censys, DNS, WHOIS | +| Server | Tools | What It Adds | +| --- | --- | --- | +| [github-security-mcp](https://github.com/badchars/github-security-mcp) | 39 | GitHub org, repo, Actions, secrets, supply chain, and access posture | +| [cve-mcp](https://github.com/badchars/cve-mcp) | 41 | CVE intelligence across 11 vulnerability and exploitability sources | +| [osint-mcp-server](https://github.com/badchars/osint-mcp-server) | 37 | Shodan, VirusTotal, Censys, DNS, WHOIS, certificates, BGP, and archives | +| [cloud-audit-mcp](https://github.com/badchars/cloud-audit-mcp) | 38 | AWS, Azure, and GCP security audits with 60+ checks | +| [hackbrowser-mcp](https://github.com/badchars/hackbrowser-mcp) | 39 | Firefox security browser, isolated roles, traffic replay, active tests | +| [darknet-mcp-server](https://github.com/badchars/darknet-mcp-server) | 66 | Breach, ransomware, Tor, malware, blockchain, and exploit intelligence | +| [dns-security-mcp](https://github.com/badchars/dns-security-mcp) | 103 | DNSSEC, email, hijacking, tunneling, typosquatting, and certificates | +| [supply-chain-mcp-server](https://github.com/badchars/supply-chain-mcp-server) | 7/90 | 7 composite tools orchestrating 90 package and provenance techniques | +| [mcp-security-scanner](https://github.com/badchars/mcp-security-scanner) | 55 | Runtime, source, config, dependency, and OWASP MCP security analysis | +| [steganography-mcp](https://github.com/badchars/steganography-mcp) | 128 | Offline image, audio, video, document, and covert-channel analysis | +| [satellite-mcp](https://github.com/badchars/satellite-mcp) | 171 | Satellite, aviation, maritime, conflict, infrastructure, and GEOINT | -All open source. All installable with `npx`. Plug them into CyberStrike or use them standalone with any MCP-compatible client. +Runnable npm entries are version-pinned. `cloud-audit-mcp` and `hackbrowser-mcp` currently require manual installation from their repositories. The catalog also offers optional wireless-security, LOLBin, and fingerprinting servers. --- @@ -379,16 +395,16 @@ All open source. All installable with `npx`. Plug them into CyberStrike or use t CyberStrike agents have direct access to **56+ tools** without any external dependencies: -| Category | Tools | -| --------------------- | ----------------------------------------------------------------------------------- | -| **Execution** | Shell (bash), file read/write/edit/patch, directory listing, batch operations | -| **Discovery** | Web fetch, web search, code search, glob, grep, intel gathering | -| **Offensive** | HackBrowser, attack script execution, vulnerability reporting & triage | -| **Post-Exploitation** | AWS hook, Azure hook, Kubernetes hook, Windows hook, macOS hook, CI/CD pipe, eBPF | -| **Web Context** | Session context, endpoint/role/credential/function discovery and management | -| **Proxy** | HTTP/HTTPS interception, request replay, session context sharing across sub-testers | -| **Reporting** | Professional report generation, coverage notes, methodology tracking, VRT checks | -| **Integration** | MCP servers, Bolt remote tools, custom plugins, LSP | +| Category | Tools | +| --- | --- | +| **Execution** | Shell, typed host readiness, file read/write/edit/patch, directory listing, batch operations | +| **Discovery** | Web fetch, web search, code search, glob, grep, intel gathering | +| **Offensive** | Approval-gated Nmap profiles, HackBrowser, attack scripts, vulnerability reporting & triage | +| **Post-Exploitation** | AWS hook, Azure hook, Kubernetes hook, Windows hook, macOS hook, CI/CD pipe, eBPF | +| **Web Context** | Session context, endpoint/role/credential/function discovery and management | +| **Proxy** | HTTP/HTTPS interception, request replay, session context sharing across sub-testers | +| **Reporting** | Professional report generation, coverage notes, methodology tracking, VRT checks | +| **Integration** | MCP servers, Bolt remote tools, custom plugins, LSP | Plus a **plugin SDK** with 15+ hook types (tool interception, message transformation, permission prompts, shell environment) — build your own agents and tools, register them at runtime. @@ -398,15 +414,15 @@ Plus a **plugin SDK** with 15+ hook types (tool interception, message transforma CyberStrike includes built-in post-exploitation capabilities across multiple platforms — no external tools required. -| Platform | Capabilities | -| -------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| **macOS** | Chrome credential extraction, Keychain dumping, keylogging, TCC bypass, GateKeeper bypass, XProtect checks, SSH key extraction, DTrace system tracing | -| **Windows** | Post-exploitation hooks for privilege escalation and persistence | +| Platform | Capabilities | +| --- | --- | +| **macOS** | Chrome credential extraction, Keychain dumping, keylogging, TCC bypass, GateKeeper bypass, XProtect checks, SSH key extraction, DTrace system tracing | +| **Windows** | Post-exploitation hooks for privilege escalation and persistence | | **Linux/eBPF** | 29 kernel-level scripts — process execution monitoring, SSL/TLS sniffing, keystroke logging, namespace manipulation detection, rootkit detection, process/file/connection hiding | -| **AWS** | IAM enumeration, S3 exposure, Lambda backdoors, CloudTrail evasion | -| **Azure** | Identity enumeration, storage exposure, function exploitation | -| **Kubernetes** | Pod escape, service account abuse, secret extraction, RBAC exploitation | -| **CI/CD** | Pipeline injection, secret extraction, build artifact manipulation | +| **AWS** | IAM enumeration, S3 exposure, Lambda backdoors, CloudTrail evasion | +| **Azure** | Identity enumeration, storage exposure, function exploitation | +| **Kubernetes** | Pod escape, service account abuse, secret extraction, RBAC exploitation | +| **CI/CD** | Pipeline injection, secret extraction, build artifact manipulation | All post-exploitation tools are agent-driven — they execute based on context and findings, not as fixed scripts. @@ -431,6 +447,36 @@ scoop install cyberstrike curl -fsSL https://cyberstrike.io/install.sh | bash ``` +#### Build and deploy on Kali/Linux from source + +Source deployments require the compiled binary, the matching HackBrowser worker, and the web bundle. Use the repository-pinned Bun version: + +```bash +bun install --frozen-lockfile +bun run --cwd packages/app build +CYBERSTRIKE_BUILD_TARGET=linux-x64 bun run --cwd packages/cyberstrike script/build.ts + +# Installs the binary and its sibling HackBrowser worker. +./install --binary packages/cyberstrike/dist/cyberstrike-linux-x64/bin/cyberstrike + +# Install the locally built Web UI. +install -d "${XDG_DATA_HOME:-$HOME/.local/share}/cyberstrike/web" +cp -R packages/app/dist/. "${XDG_DATA_HOME:-$HOME/.local/share}/cyberstrike/web/" + +CYBERSTRIKE_SERVER_PASSWORD=change-me cyberstrike web --hostname 127.0.0.1 +``` + +Use `linux-x64-baseline` on x64 CPUs without AVX2, or the corresponding `*-musl` target on musl-based distributions. Back up the installed binary, configuration, and data directory before replacing a production deployment. + +For a persistent localhost-only deployment, install `contrib/systemd/cyberstrike-web.service` under `~/.config/systemd/user/`, create a mode `0600` `~/.config/cyberstrike/web.env` containing `CYBERSTRIKE_SERVER_PASSWORD`, then run: + +```bash +systemctl --user daemon-reload +systemctl --user enable --now cyberstrike-web.service +``` + +Use an SSH or authenticated Cloudflare tunnel for remote access rather than exposing port 4096 directly. + --- ### Who Is This For? @@ -494,13 +540,13 @@ This personal workstream is based on the upstream [CyberStrike](https://github.c CyberStrike is the core platform. These MCP servers extend its capabilities: -| Project | Domain | Tools | -| ---------------------------------------------------------------------- | --------------------------------------- | ----------------------------------------------------------- | -| **CyberStrike** | **Autonomous offensive security agent** | **13+ agents, 56+ tools, 7,600+ skills, 150+ AI providers** | -| [cloud-audit-mcp](https://github.com/badchars/cloud-audit-mcp) | Cloud security (AWS/Azure/GCP) | 38 tools, 60+ checks | -| [github-security-mcp](https://github.com/badchars/github-security-mcp) | GitHub security posture | 39 tools, 45 checks | -| [cve-mcp](https://github.com/badchars/cve-mcp) | Vulnerability intelligence | 23 tools, 5 sources | -| [osint-mcp](https://github.com/badchars/osint-mcp-server) | OSINT & reconnaissance | 37 tools, 12 sources | +| Project | Domain | Tools | +| --- | --- | --- | +| **CyberStrike** | **Autonomous offensive security agent** | **13+ agents, 56+ tools, 7,600+ skills, 150+ AI providers** | +| [cloud-audit-mcp](https://github.com/badchars/cloud-audit-mcp) | Cloud security (AWS/Azure/GCP) | 38 tools, 60+ checks | +| [github-security-mcp](https://github.com/badchars/github-security-mcp) | GitHub security posture | 39 tools, 45 checks | +| [cve-mcp](https://github.com/badchars/cve-mcp) | Vulnerability intelligence | 23 tools, 5 sources | +| [osint-mcp](https://github.com/badchars/osint-mcp-server) | OSINT & reconnaissance | 37 tools, 12 sources | --- diff --git a/contrib/systemd/cyberstrike-web.service b/contrib/systemd/cyberstrike-web.service new file mode 100644 index 0000000000..7436fc38fa --- /dev/null +++ b/contrib/systemd/cyberstrike-web.service @@ -0,0 +1,19 @@ +[Unit] +Description=CyberStrike Web UI +Documentation=https://github.com/CyberStrikeus/CyberStrike +Wants=network-online.target +After=network-online.target + +[Service] +Type=simple +Environment=BROWSER=none +Environment=CYBERSTRIKE_DISABLE_AUTOUPDATE=true +EnvironmentFile=%h/.config/cyberstrike/web.env +ExecStart=%h/.cyberstrike/bin/cyberstrike web --hostname 127.0.0.1 --port 4096 +Restart=on-failure +RestartSec=5 +TimeoutStopSec=30 +UMask=0077 + +[Install] +WantedBy=default.target diff --git a/install b/install index 34500d893d..91cab9b70e 100755 --- a/install +++ b/install @@ -73,7 +73,8 @@ while [[ $# -gt 0 ]]; do done INSTALL_DIR=$HOME/.cyberstrike/bin -mkdir -p "$INSTALL_DIR" +DATA_BIN_DIR="${XDG_DATA_HOME:-$HOME/.local/share}/cyberstrike/bin" +mkdir -p "$INSTALL_DIR" "$DATA_BIN_DIR" # If --binary is provided, skip all download/detection logic if [ -n "$binary_path" ]; then @@ -354,13 +355,24 @@ download_and_install() { mv "$tmp_dir/cyberstrike" "$INSTALL_DIR" chmod 755 "${INSTALL_DIR}/cyberstrike" + install_worker "$tmp_dir/hackbrowser-worker.js" rm -rf "$tmp_dir" } +install_worker() { + local worker_path="$1" + if [ ! -f "$worker_path" ]; then + return + fi + cp "$worker_path" "${DATA_BIN_DIR}/hackbrowser-worker.js" + chmod 755 "${DATA_BIN_DIR}/hackbrowser-worker.js" +} + install_from_binary() { print_message info "\n${MUTED}Installing ${NC}cyberstrike ${MUTED}from: ${NC}$binary_path" cp "$binary_path" "${INSTALL_DIR}/cyberstrike" chmod 755 "${INSTALL_DIR}/cyberstrike" + install_worker "$(dirname "$binary_path")/hackbrowser-worker.js" } if [ -n "$binary_path" ]; then diff --git a/packages/app/src/context/server.tsx b/packages/app/src/context/server.tsx index 9fc3d75b2e..add17758fa 100644 --- a/packages/app/src/context/server.tsx +++ b/packages/app/src/context/server.tsx @@ -2,7 +2,7 @@ import { createSimpleContext } from "@cyberstrike-io/ui/context" import { type Accessor, batch, createEffect, createMemo, onCleanup } from "solid-js" import { createStore } from "solid-js/store" import { Persist, persisted } from "@/utils/persist" -import { useCheckServerHealth } from "@/utils/server-health" +import { useCheckServerHealth, type ServerHealth } from "@/utils/server-health" type StoredProject = { worktree: string; expanded: boolean } type StoredServer = string | ServerConnection.HttpBase | ServerConnection.Http @@ -97,6 +97,7 @@ export const { use: useServer, provider: ServerProvider } = createSimpleContext( active: "" as ServerConnection.Key | "", healthy: undefined as boolean | undefined, needsAuth: false, + role: undefined as ServerHealth["role"], }) const healthy = () => state.healthy @@ -161,6 +162,7 @@ export const { use: useServer, provider: ServerProvider } = createSimpleContext( if (!alive) return setState("healthy", result.healthy) setState("needsAuth", !!result.needsAuth) + setState("role", result.role) }) .finally(() => { busy = false @@ -246,6 +248,7 @@ export const { use: useServer, provider: ServerProvider } = createSimpleContext( ready: isReady, healthy, needsAuth, + role: () => state.role, isLocal, get key() { return state.active as ServerConnection.Key diff --git a/packages/app/src/pages/layout.tsx b/packages/app/src/pages/layout.tsx index c22ce98309..07443c9fd2 100644 --- a/packages/app/src/pages/layout.tsx +++ b/packages/app/src/pages/layout.tsx @@ -1949,6 +1949,14 @@ export default function Layout(props: ParentProps) { return (
+ +
+ Read-only observer mode · execution, configuration, secrets, and terminal input are disabled +
+
- ) => string} - responding={ui.responding} - onDecide={decide} - inputRef={(el) => { - inputRef = el - }} - newSessionWorktree={newSessionWorktree()} - onNewSessionWorktreeReset={() => setStore("newSessionWorktree", "main")} - onSubmit={() => { - comments.clear() - resumeScroll() - }} - setPromptDockRef={(el) => (promptDock = el)} - /> + + Read-only observer mode +
+ } + > + ) => string} + responding={ui.responding} + onDecide={decide} + inputRef={(el) => { + inputRef = el + }} + newSessionWorktree={newSessionWorktree()} + onNewSessionWorktreeReset={() => setStore("newSessionWorktree", "main")} + onSubmit={() => { + comments.clear() + resumeScroll() + }} + setPromptDockRef={(el) => (promptDock = el)} + /> + handoff.terminal.get(params.dir!) ?? []} diff --git a/packages/app/src/pages/session/activity-panel.test.ts b/packages/app/src/pages/session/activity-panel.test.ts new file mode 100644 index 0000000000..68e57c026d --- /dev/null +++ b/packages/app/src/pages/session/activity-panel.test.ts @@ -0,0 +1,29 @@ +import { describe, expect, test } from "bun:test" +import { isActivity, mergeActivity } from "./activity" + +const event = (id: string, time: number, title = id) => ({ + id, + projectID: "project", + sessionID: "session", + type: "session.updated", + source: "agent" as const, + data: { title }, + time, +}) + +describe("activity history", () => { + test("preserves live events that arrive before history", () => { + expect(mergeActivity([event("one", 1)], [event("two", 2)])).toEqual([event("one", 1), event("two", 2)]) + }) + + test("keeps the live version of duplicate events", () => { + expect(mergeActivity([event("one", 1, "old")], [event("one", 1, "new")])).toEqual([ + event("one", 1, "new"), + ]) + }) + + test("ignores SSE heartbeats", () => { + expect(isActivity({})).toBe(false) + expect(isActivity(event("one", 1))).toBe(true) + }) +}) diff --git a/packages/app/src/pages/session/activity-panel.tsx b/packages/app/src/pages/session/activity-panel.tsx new file mode 100644 index 0000000000..6d0c80ec18 --- /dev/null +++ b/packages/app/src/pages/session/activity-panel.tsx @@ -0,0 +1,266 @@ +import { For, Show, createEffect, createMemo, createSignal, onCleanup } from "solid-js" +import { createStore, produce, reconcile } from "solid-js/store" +import { useParams } from "@solidjs/router" +import { Icon } from "@cyberstrike-io/ui/icon" +import { useSDK } from "@/context/sdk" +import { isActivity, mergeActivity, type Activity, type ActivitySource } from "./activity" + +const sources: Array<{ id: ActivitySource; label: string }> = [ + { id: "agent", label: "Agent" }, + { id: "tool", label: "Tool" }, + { id: "mcp", label: "MCP" }, + { id: "bolt", label: "Bolt" }, + { id: "browser", label: "Browser" }, + { id: "pty", label: "PTY" }, + { id: "finding", label: "Finding" }, + { id: "system", label: "System" }, +] + +const badge = (source: ActivitySource) => { + if (source === "tool") return "bg-surface-accent-base text-text-accent-base" + if (source === "mcp" || source === "bolt") return "bg-surface-info-base text-text-info-base" + if (source === "finding") return "bg-surface-warning-base text-text-warning-base" + if (source === "browser") return "bg-surface-success-base text-text-success-base" + return "bg-surface-base text-text-weak" +} + +const value = (data: Record, key: string) => + typeof data[key] === "string" || typeof data[key] === "number" ? String(data[key]) : "" + +const summary = (event: Activity) => { + const title = value(event.data, "title") + const tool = value(event.data, "tool") + const status = value(event.data, "status") + const name = value(event.data, "name") + const count = value(event.data, "count") + return [tool || name || event.type, status, title, count ? `${count} items` : ""].filter(Boolean).join(" · ") +} + +function ActivityRow(props: { event: Activity }) { + return ( +
+ + + {new Date(props.event.time).toLocaleTimeString([], { + hour: "2-digit", + minute: "2-digit", + second: "2-digit", + })} + + + {props.event.source.toUpperCase()} + + {summary(props.event)} + +
+        {JSON.stringify(
+          {
+            type: props.event.type,
+            correlationID: props.event.correlationID,
+            parentID: props.event.parentID,
+            ...props.event.data,
+          },
+          null,
+          2,
+        )}
+      
+
+ ) +} + +export function ActivityPanel() { + const params = useParams() + const sdk = useSDK() + const [events, setEvents] = createStore([]) + const [source, setSource] = createSignal("all") + const [search, setSearch] = createSignal("") + const [mode, setMode] = createSignal<"timeline" | "lanes">("timeline") + const [follow, setFollow] = createSignal(true) + const [error, setError] = createSignal("") + let scroll!: HTMLDivElement + + const add = (event: Activity) => { + const index = events.findIndex((item) => item.id === event.id) + if (index !== -1) { + setEvents(index, reconcile(event)) + return + } + setEvents( + produce((draft) => { + draft.push(event) + if (draft.length > 2_000) draft.splice(0, draft.length - 2_000) + }), + ) + } + + const merge = (incoming: Activity[]) => { + setEvents(reconcile(mergeActivity(incoming, [...events]))) + } + + createEffect(() => { + const sessionID = params.id + if (!sessionID) { + setEvents(reconcile([])) + return + } + + const abort = new AbortController() + const client = sdk.createClient({ + directory: sdk.directory, + throwOnError: true, + signal: abort.signal, + }) + setError("") + void client.eventLog + .list({ sessionID, limit: 500 }) + .then((response) => merge(response.data ?? [])) + .catch((cause) => { + if (!abort.signal.aborted) setError(cause instanceof Error ? cause.message : String(cause)) + }) + void (async () => { + try { + const response = await client.eventLog.stream( + { sessionID }, + { + onSseError: (cause) => { + if (!abort.signal.aborted) setError(cause instanceof Error ? cause.message : String(cause)) + }, + }, + ) + for await (const event of response.stream) { + if (isActivity(event)) add(event) + } + } catch (cause) { + if (!abort.signal.aborted) setError(cause instanceof Error ? cause.message : String(cause)) + } + })() + onCleanup(() => abort.abort()) + }) + + const filtered = createMemo(() => { + const query = search().trim().toLowerCase() + return events.filter((event) => { + if (source() !== "all" && event.source !== source()) return false + if (!query) return true + return `${event.type} ${summary(event)} ${event.correlationID ?? ""}`.toLowerCase().includes(query) + }) + }) + + createEffect(() => { + filtered().length + if (!follow() || !scroll) return + requestAnimationFrame(() => scroll.scrollTo({ top: scroll.scrollHeight })) + }) + + const download = () => { + const body = filtered().map((event) => JSON.stringify(event)).join("\n") + const url = URL.createObjectURL(new Blob([body], { type: "application/x-ndjson" })) + const anchor = document.createElement("a") + anchor.href = url + anchor.download = `cyberstrike-activity-${params.id ?? "session"}.jsonl` + anchor.click() + URL.revokeObjectURL(url) + } + + return ( +
+
+ + + {(item) => ( + + )} + +
+ setSearch(event.currentTarget.value)} + /> + + + +
+ +
{error()}
+
+
+ 0} + fallback={
No activity yet
} + > + + {(event) => } +
+ } + > +
+ source() === "all" || source() === item.id)}> + {(item) => { + const lane = createMemo(() => filtered().filter((event) => event.source === item.id)) + return ( +
+
+ {item.label} + {lane().length} +
+ {(event) => } +
+ ) + }} +
+
+ + +
+
+ ) +} diff --git a/packages/app/src/pages/session/activity.ts b/packages/app/src/pages/session/activity.ts new file mode 100644 index 0000000000..3d932752c5 --- /dev/null +++ b/packages/app/src/pages/session/activity.ts @@ -0,0 +1,31 @@ +export type ActivitySource = "agent" | "tool" | "mcp" | "bolt" | "browser" | "pty" | "finding" | "system" + +export type Activity = { + id: string + projectID: string + sessionID?: string + type: string + source: ActivitySource + correlationID?: string + parentID?: string + data: Record + time: number +} + +export const isActivity = (event: unknown): event is Activity => { + if (!event || typeof event !== "object") return false + const value = event as Partial + return ( + typeof value.id === "string" && + typeof value.type === "string" && + typeof value.source === "string" && + typeof value.time === "number" && + !!value.data && + typeof value.data === "object" + ) +} + +export const mergeActivity = (history: Activity[], live: Activity[], limit = 2_000) => { + const byID = new Map([...history, ...live].map((event) => [event.id, event])) + return [...byID.values()].sort((a, b) => a.time - b.time).slice(-limit) +} diff --git a/packages/app/src/pages/session/memory-panel.tsx b/packages/app/src/pages/session/memory-panel.tsx new file mode 100644 index 0000000000..dbacbdb7f1 --- /dev/null +++ b/packages/app/src/pages/session/memory-panel.tsx @@ -0,0 +1,254 @@ +import { For, Show, createEffect, createSignal, onCleanup } from "solid-js" +import { createStore, reconcile } from "solid-js/store" +import { useParams } from "@solidjs/router" +import type { MemoryListResponse } from "@cyberstrike-io/sdk/v2/client" +import { Icon } from "@cyberstrike-io/ui/icon" +import { useSDK } from "@/context/sdk" + +type Kind = MemoryListResponse[number]["kind"] + +const kinds: Array<{ id: Kind; label: string }> = [ + { id: "working", label: "Working" }, + { id: "episodic", label: "Episodic" }, + { id: "semantic", label: "Semantic" }, + { id: "procedural", label: "Procedural" }, +] + +const trust = (value: MemoryListResponse[number]["trust"]) => { + if (value === "human") return "bg-surface-success-base text-text-success-base" + if (value === "tool") return "bg-surface-info-base text-text-info-base" + if (value === "untrusted") return "bg-surface-critical-base text-text-critical-base" + return "bg-surface-base text-text-weak" +} + +export function MemoryPanel() { + const params = useParams() + const sdk = useSDK() + const [items, setItems] = createStore([]) + const [query, setQuery] = createSignal("") + const [kind, setKind] = createSignal("all") + const [error, setError] = createSignal("") + const [form, setForm] = createStore({ + open: false, + project: false, + kind: "episodic" as Kind, + title: "", + content: "", + saving: false, + }) + + const load = async () => { + const sessionID = params.id + const filter = kind() + try { + const response = query().trim() + ? await sdk.client.memory.search({ + query: query().trim(), + sessionID, + kind: filter === "all" ? undefined : filter, + limit: 100, + }) + : await sdk.client.memory.list({ + sessionID, + kind: filter === "all" ? undefined : filter, + limit: 200, + }) + setItems(reconcile(response.data ?? [])) + setError("") + } catch (cause) { + setError(cause instanceof Error ? cause.message : String(cause)) + } + } + + createEffect(() => { + params.id + kind() + const search = query() + const timer = setTimeout(load, search ? 250 : 0) + onCleanup(() => clearTimeout(timer)) + }) + + const add = async () => { + const title = form.title.trim() + const content = form.content.trim() + if (!title || !content || form.saving) return + setForm("saving", true) + try { + const response = await sdk.client.memory.create({ + sessionID: form.project ? undefined : params.id, + kind: form.kind, + title, + content, + confidence: 1, + tags: ["human-confirmed"], + }) + if (response.data) setItems((current) => [response.data!, ...current]) + setForm({ + open: false, + project: false, + kind: "episodic", + title: "", + content: "", + saving: false, + }) + setError("") + } catch (cause) { + setForm("saving", false) + setError(cause instanceof Error ? cause.message : String(cause)) + } + } + + const invalidate = async (id: string) => { + try { + await sdk.client.memory.invalidate({ entryID: id }) + setItems(reconcile(items.filter((item) => item.id !== id))) + } catch (cause) { + setError(cause instanceof Error ? cause.message : String(cause)) + } + } + + return ( +
+
+ Persistent memory + +
+
+ + + {(item) => ( + + )} + +
+
+ setQuery(event.currentTarget.value)} + /> +
+ +
+
+ + +
+ setForm("title", event.currentTarget.value)} + /> +