From 96f00c1c68cf64b00f3197bd1a1b5c0c4405c42e Mon Sep 17 00:00:00 2001 From: CyberSecurityUP Date: Wed, 24 Jun 2026 19:34:13 -0300 Subject: [PATCH] =?UTF-8?q?v3.4.1:=20CLI-only=20Rust=20harness=20=E2=80=94?= =?UTF-8?q?=20interactive=20wizard,=20smart=20selection,=20tool=20doctrine?= =?UTF-8?q?,=20Typst,=20status?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Remove Rust web server (axum/tower-http); CLI-only binary - Verbose logging (-v) + unique run-id output folder runs/ns--/ - status.json lifecycle (running → complete) + ✓ COMPLETE summary - Interactive wizard when run with no args; detailed --help with testphp/DVWA examples + Kali tip - Tool-usage doctrine injected into recon/exploit prompts: curl + rustscan/nmap (apt/brew/cargo install guidance) + browser via Playwright when present, else curl - Smart recon-aware selection: map recon signals → agent categories, only run matching agents; heuristic fallback when LLM selection is empty - Cross-model false-positive validation: voting prefers a model other than the finder - Playwright MCP auto-provision (npx) + per-backend support (claude/codex; gemini/grok degrade) - Gemini provider (API + gemini CLI subscription) - Typst report (report.typ + compiled report.pdf) via blank structured template - Lenient finding parsing (confidence as word/number) — fixes empty-results bug - bump version 3.4.0 -> 3.4.1 Co-Authored-By: Claude Opus 4.8 (1M context) --- neurosploit-rs/Cargo.lock | 339 +----------------- neurosploit-rs/Cargo.toml | 2 +- neurosploit-rs/app/Cargo.toml | 3 - neurosploit-rs/app/src/main.rs | 180 +++++++--- neurosploit-rs/app/src/web.rs | 236 ------------ neurosploit-rs/app/web/index.html | 327 ----------------- neurosploit-rs/cj_proof2.png | Bin 0 -> 4298 bytes neurosploit-rs/clickjack_proof.png | Bin 0 -> 80392 bytes neurosploit-rs/crates/harness/src/lib.rs | 6 +- neurosploit-rs/crates/harness/src/models.rs | 54 ++- neurosploit-rs/crates/harness/src/pipeline.rs | 151 +++++++- neurosploit-rs/crates/harness/src/pool.rs | 14 +- neurosploit-rs/crates/harness/src/report.rs | 68 +++- neurosploit-rs/crates/harness/src/types.rs | 4 + neurosploit-rs/templates/report.typ | 97 +++++ 15 files changed, 512 insertions(+), 969 deletions(-) delete mode 100644 neurosploit-rs/app/src/web.rs delete mode 100644 neurosploit-rs/app/web/index.html create mode 100644 neurosploit-rs/cj_proof2.png create mode 100644 neurosploit-rs/clickjack_proof.png create mode 100644 neurosploit-rs/templates/report.typ diff --git a/neurosploit-rs/Cargo.lock b/neurosploit-rs/Cargo.lock index 3d1d7ef..1fc57a6 100644 --- a/neurosploit-rs/Cargo.lock +++ b/neurosploit-rs/Cargo.lock @@ -67,81 +67,12 @@ version = "1.0.102" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c" -[[package]] -name = "async-trait" -version = "0.1.89" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9035ad2d096bed7955a320ee7e2230574d28fd3c3a0f186cbea1ff3c7eed5dbb" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - [[package]] name = "atomic-waker" version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" -[[package]] -name = "axum" -version = "0.7.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "edca88bc138befd0323b20752846e6587272d3b03b0343c8ea28a6f819e6e71f" -dependencies = [ - "async-trait", - "axum-core", - "base64", - "bytes", - "futures-util", - "http", - "http-body", - "http-body-util", - "hyper", - "hyper-util", - "itoa", - "matchit", - "memchr", - "mime", - "percent-encoding", - "pin-project-lite", - "rustversion", - "serde", - "serde_json", - "serde_path_to_error", - "serde_urlencoded", - "sha1", - "sync_wrapper", - "tokio", - "tokio-tungstenite", - "tower", - "tower-layer", - "tower-service", - "tracing", -] - -[[package]] -name = "axum-core" -version = "0.4.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09f2bd6146b97ae3359fa0cc6d6b376d9539582c7b4220f041a33ec24c226199" -dependencies = [ - "async-trait", - "bytes", - "futures-util", - "http", - "http-body", - "http-body-util", - "mime", - "pin-project-lite", - "rustversion", - "sync_wrapper", - "tower-layer", - "tower-service", - "tracing", -] - [[package]] name = "base64" version = "0.22.1" @@ -154,27 +85,12 @@ version = "2.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b4388bee8683e3d04af747c73422af53102d2bd24d9eadb6cbc100baef4b43f8" -[[package]] -name = "block-buffer" -version = "0.10.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" -dependencies = [ - "generic-array", -] - [[package]] name = "bumpalo" version = "3.20.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "72f5acc6cb2ba439de613abc23857ec3d78374d8ed5ac84e9d11336e87da8649" -[[package]] -name = "byteorder" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" - [[package]] name = "bytes" version = "1.12.0" @@ -249,41 +165,6 @@ version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1d07550c9036bf2ae0c684c4297d503f838287c83c53686d05370d0e139ae570" -[[package]] -name = "cpufeatures" -version = "0.2.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" -dependencies = [ - "libc", -] - -[[package]] -name = "crypto-common" -version = "0.1.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78c8292055d1c1df0cce5d180393dc8cce0abec0a7102adb6c7b1eef6016d60a" -dependencies = [ - "generic-array", - "typenum", -] - -[[package]] -name = "data-encoding" -version = "2.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4ae5f15dda3c708c0ade84bfee31ccab44a3da4f88015ed22f63732abe300c8" - -[[package]] -name = "digest" -version = "0.10.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" -dependencies = [ - "block-buffer", - "crypto-common", -] - [[package]] name = "displaydoc" version = "0.2.6" @@ -408,16 +289,6 @@ dependencies = [ "slab", ] -[[package]] -name = "generic-array" -version = "0.14.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" -dependencies = [ - "typenum", - "version_check", -] - [[package]] name = "getrandom" version = "0.2.17" @@ -440,22 +311,11 @@ dependencies = [ "cfg-if", "js-sys", "libc", - "r-efi 5.3.0", + "r-efi", "wasip2", "wasm-bindgen", ] -[[package]] -name = "getrandom" -version = "0.4.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "300e883d756b2e4ec94e02791f39b04b522276138852cfc41d9fb7e904106099" -dependencies = [ - "cfg-if", - "libc", - "r-efi 6.0.0", -] - [[package]] name = "heck" version = "0.5.0" @@ -501,12 +361,6 @@ version = "1.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87" -[[package]] -name = "httpdate" -version = "1.0.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" - [[package]] name = "hyper" version = "1.10.1" @@ -520,7 +374,6 @@ dependencies = [ "http", "http-body", "httparse", - "httpdate", "itoa", "pin-project-lite", "smallvec", @@ -732,24 +585,12 @@ version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "112b39cec0b298b6c1999fee3e31427f74f676e4cb9879ed1a121b43661a4154" -[[package]] -name = "matchit" -version = "0.7.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0e7465ac9959cc2b1404e8e2367b43684a6d13790fe23056cc8c6c5a6b7bcb94" - [[package]] name = "memchr" version = "2.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "88904434abc2901f197fe8cc55f0445e7ded921dba5911dad2e2b39b48e663c4" -[[package]] -name = "mime" -version = "0.3.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" - [[package]] name = "mio" version = "1.2.1" @@ -763,23 +604,20 @@ dependencies = [ [[package]] name = "neurosploit" -version = "3.4.0" +version = "3.4.1" dependencies = [ "anyhow", - "axum", "clap", "futures", "neurosploit-harness", "serde", "serde_json", "tokio", - "tower-http 0.5.2", - "uuid", ] [[package]] name = "neurosploit-harness" -version = "3.4.0" +version = "3.4.1" dependencies = [ "anyhow", "futures", @@ -879,7 +717,7 @@ dependencies = [ "rustc-hash", "rustls", "socket2", - "thiserror 2.0.18", + "thiserror", "tokio", "tracing", "web-time", @@ -894,13 +732,13 @@ dependencies = [ "bytes", "getrandom 0.3.4", "lru-slab", - "rand 0.9.4", + "rand", "ring", "rustc-hash", "rustls", "rustls-pki-types", "slab", - "thiserror 2.0.18", + "thiserror", "tinyvec", "tracing", "web-time", @@ -935,41 +773,14 @@ version = "5.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" -[[package]] -name = "r-efi" -version = "6.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf" - -[[package]] -name = "rand" -version = "0.8.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ca0ecfa931c29007047d1bc58e623ab12e5590e8c7cc53200d5202b69266d8a" -dependencies = [ - "libc", - "rand_chacha 0.3.1", - "rand_core 0.6.4", -] - [[package]] name = "rand" version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "44c5af06bb1b7d3216d91932aed5265164bf384dc89cd6ba05cf59a35f5f76ea" dependencies = [ - "rand_chacha 0.9.0", - "rand_core 0.9.5", -] - -[[package]] -name = "rand_chacha" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" -dependencies = [ - "ppv-lite86", - "rand_core 0.6.4", + "rand_chacha", + "rand_core", ] [[package]] @@ -979,16 +790,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" dependencies = [ "ppv-lite86", - "rand_core 0.9.5", -] - -[[package]] -name = "rand_core" -version = "0.6.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" -dependencies = [ - "getrandom 0.2.17", + "rand_core", ] [[package]] @@ -1067,7 +869,7 @@ dependencies = [ "tokio", "tokio-rustls", "tower", - "tower-http 0.6.11", + "tower-http", "tower-service", "url", "wasm-bindgen", @@ -1201,17 +1003,6 @@ dependencies = [ "zmij", ] -[[package]] -name = "serde_path_to_error" -version = "0.1.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "10a9ff822e371bb5403e391ecd83e182e0e77ba7f6fe0160b795797109d1b457" -dependencies = [ - "itoa", - "serde", - "serde_core", -] - [[package]] name = "serde_urlencoded" version = "0.7.1" @@ -1224,17 +1015,6 @@ dependencies = [ "serde", ] -[[package]] -name = "sha1" -version = "0.10.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba" -dependencies = [ - "cfg-if", - "cpufeatures", - "digest", -] - [[package]] name = "shlex" version = "2.0.1" @@ -1322,33 +1102,13 @@ dependencies = [ "syn", ] -[[package]] -name = "thiserror" -version = "1.0.69" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" -dependencies = [ - "thiserror-impl 1.0.69", -] - [[package]] name = "thiserror" version = "2.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4" dependencies = [ - "thiserror-impl 2.0.18", -] - -[[package]] -name = "thiserror-impl" -version = "1.0.69" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" -dependencies = [ - "proc-macro2", - "quote", - "syn", + "thiserror-impl", ] [[package]] @@ -1425,18 +1185,6 @@ dependencies = [ "tokio", ] -[[package]] -name = "tokio-tungstenite" -version = "0.24.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "edc5f74e248dc973e0dbb7b74c7e0d6fcc301c694ff50049504004ef4d0cdcd9" -dependencies = [ - "futures-util", - "log", - "tokio", - "tungstenite", -] - [[package]] name = "tower" version = "0.5.3" @@ -1450,23 +1198,6 @@ dependencies = [ "tokio", "tower-layer", "tower-service", - "tracing", -] - -[[package]] -name = "tower-http" -version = "0.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e9cd434a998747dd2c4276bc96ee2e0c7a2eadf3cae88e52be55a05fa9053f5" -dependencies = [ - "bitflags", - "bytes", - "http", - "http-body", - "http-body-util", - "pin-project-lite", - "tower-layer", - "tower-service", ] [[package]] @@ -1505,7 +1236,6 @@ version = "0.1.44" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100" dependencies = [ - "log", "pin-project-lite", "tracing-core", ] @@ -1525,30 +1255,6 @@ version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" -[[package]] -name = "tungstenite" -version = "0.24.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "18e5b8366ee7a95b16d32197d0b2604b43a0be89dc5fac9f8e96ccafbaedda8a" -dependencies = [ - "byteorder", - "bytes", - "data-encoding", - "http", - "httparse", - "log", - "rand 0.8.6", - "sha1", - "thiserror 1.0.69", - "utf-8", -] - -[[package]] -name = "typenum" -version = "1.20.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6f5e870be6c3b371b77fe0ee0bafb859fa4964b4404c27de1d380043c4dda20" - [[package]] name = "unicode-ident" version = "1.0.24" @@ -1573,12 +1279,6 @@ dependencies = [ "serde", ] -[[package]] -name = "utf-8" -version = "0.7.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9" - [[package]] name = "utf8_iter" version = "1.0.4" @@ -1591,23 +1291,6 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" -[[package]] -name = "uuid" -version = "1.23.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "144d6b123cef80b301b8f72a9e2ca4370ddec21950d0a103dd22c437006d2db7" -dependencies = [ - "getrandom 0.4.3", - "js-sys", - "wasm-bindgen", -] - -[[package]] -name = "version_check" -version = "0.9.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" - [[package]] name = "walkdir" version = "2.5.0" diff --git a/neurosploit-rs/Cargo.toml b/neurosploit-rs/Cargo.toml index e9aa091..7ccf9dc 100644 --- a/neurosploit-rs/Cargo.toml +++ b/neurosploit-rs/Cargo.toml @@ -3,7 +3,7 @@ members = ["crates/harness", "app"] resolver = "2" [workspace.package] -version = "3.4.0" +version = "3.4.1" edition = "2021" license = "MIT" repository = "https://github.com/JoasASantos/NeuroSploit" diff --git a/neurosploit-rs/app/Cargo.toml b/neurosploit-rs/app/Cargo.toml index 0cb2b19..1b0d85e 100644 --- a/neurosploit-rs/app/Cargo.toml +++ b/neurosploit-rs/app/Cargo.toml @@ -15,7 +15,4 @@ serde_json.workspace = true tokio.workspace = true anyhow.workspace = true futures.workspace = true -axum = { version = "0.7", features = ["ws"] } -tower-http = { version = "0.5", features = ["cors"] } clap = { version = "4", features = ["derive"] } -uuid = { version = "1", features = ["v4"] } diff --git a/neurosploit-rs/app/src/main.rs b/neurosploit-rs/app/src/main.rs index c424f90..56414b9 100644 --- a/neurosploit-rs/app/src/main.rs +++ b/neurosploit-rs/app/src/main.rs @@ -1,26 +1,39 @@ -//! NeuroSploit v3.4.0 — single binary: `serve` (web dashboard) or `run` (CLI). - -mod web; +//! NeuroSploit v3.4.1 — CLI: `run` (black-box) / `whitebox` (source) / `agents` / `models`. use clap::{Parser, Subcommand}; use harness::{agents, models::ModelRef, pool::ModelPool, types::RunConfig, RunOutput}; use std::path::{Path, PathBuf}; #[derive(Parser)] -#[command(name = "neurosploit", version, about = "NeuroSploit v3.4.0 — multi-model autonomous pentest harness")] +#[command( + name = "neurosploit", + version, + about = "NeuroSploit v3.4.1 — multi-model autonomous pentest harness", + long_about = "NeuroSploit v3.4.1 — a Rust multi-model harness that drives a pool of LLMs \ +(API key or local subscription: Claude/Codex/Gemini/Grok) to autonomously test a target. \ +After recon it INTELLIGENTLY selects only the agents matching the discovered surface, runs \ +them in parallel, then validates every finding by cross-model voting before reporting.\n\n\ +Run with NO arguments for an interactive wizard.\n\n\ +EXAMPLES:\n \ +# Black-box against a known test site (subscription, Opus, browser via Playwright if present)\n \ +neurosploit run http://testphp.vulnweb.com/ --subscription --model anthropic:claude-opus-4-8 --mcp -v\n\n \ +# Black-box via API keys with a multi-model voting panel\n \ +neurosploit run http://testphp.vulnweb.com/ --model anthropic:claude-opus-4-8 --model openai:gpt-5.1 --vote-n 3\n\n \ +# White-box source review of a cloned repo (DVWA)\n \ +git clone https://github.com/digininja/DVWA /tmp/DVWA\n \ +neurosploit whitebox /tmp/DVWA --subscription --model anthropic:claude-opus-4-8 -v\n\n \ +# Offline pipeline self-test (no keys/login)\n \ +neurosploit run http://testphp.vulnweb.com/ --offline\n\n\ +TIP: run inside Kali Linux (or `docker run -it kalilinux/kali-rolling`) so curl/nmap/rustscan/ffuf are available." +)] struct Cli { #[command(subcommand)] - cmd: Cmd, + cmd: Option, } #[derive(Subcommand)] enum Cmd { - /// Start the web dashboard. - Serve { - #[arg(long, default_value_t = 8788)] - port: u16, - }, - /// Run an engagement from the CLI. + /// Black-box: recon → intelligent agent selection → exploit → vote → report. Run { url: String, /// Models as provider:model (repeatable). First is primary; rest fail over + vote. @@ -30,20 +43,21 @@ enum Cmd { max_agents: usize, #[arg(long, default_value_t = 3)] vote_n: usize, - /// Exercise the pipeline without calling any model API. #[arg(long)] offline: bool, - /// Use local agentic CLI subscriptions (Claude Code / Codex / Grok) - /// instead of HTTP API keys. + /// Use local agentic CLI subscription (Claude/Codex/Gemini/Grok login). #[arg(long)] subscription: bool, - /// Enable Playwright MCP (browser proof) on the subscription/CLI path. + /// Enable Playwright MCP (auto-installed if missing; backends that don't + /// support MCP fall back to their built-in tools). #[arg(long)] mcp: bool, + /// Verbose: log each agent as it launches, recon, and votes. + #[arg(short, long)] + verbose: bool, }, /// White-box: analyse a local repository's source code for vulnerabilities. Whitebox { - /// Path to the repository to analyse. path: String, #[arg(long = "model")] models: Vec, @@ -55,6 +69,8 @@ enum Cmd { offline: bool, #[arg(long)] subscription: bool, + #[arg(short, long)] + verbose: bool, }, /// Show agent library counts. Agents, @@ -62,8 +78,7 @@ enum Cmd { Models, } -/// Locate the repo root that holds `agents_md/` (walk up from CWD, then fall -/// back to the crate's compile-time location). +/// Locate the repo root that holds `agents_md/`. fn find_base() -> PathBuf { if let Ok(b) = std::env::var("NEUROSPLOIT_BASE") { return PathBuf::from(b); @@ -80,7 +95,6 @@ fn find_base() -> PathBuf { } } } - // crate is at /neurosploit-rs/app → root is two levels up PathBuf::from(env!("CARGO_MANIFEST_DIR")) .parent() .and_then(|p| p.parent()) @@ -93,10 +107,18 @@ async fn main() -> anyhow::Result<()> { let cli = Cli::parse(); let base = find_base(); - match cli.cmd { + let cmd = match cli.cmd { + Some(c) => c, + None => interactive(&base).await?, // no args → wizard + }; + + match cmd { Cmd::Agents => { let lib = agents::load(&base); - println!("{{\"vulns\":{},\"meta\":{},\"total\":{}}}", lib.vulns.len(), lib.meta.len(), lib.total()); + println!( + "{{\"vulns\":{},\"recon\":{},\"code\":{},\"meta\":{},\"total\":{}}}", + lib.vulns.len(), lib.recon.len(), lib.code.len(), lib.meta.len(), lib.total() + ); } Cmd::Models => { for p in harness::providers() { @@ -106,55 +128,76 @@ async fn main() -> anyhow::Result<()> { } } } - Cmd::Run { url, models, max_agents, vote_n, offline, subscription, mcp } => { + Cmd::Run { url, models, max_agents, vote_n, offline, subscription, mcp, verbose } => { let url = if url.starts_with("http") { url } else { format!("https://{url}") }; let mut cfg = RunConfig::new(&url); cfg.max_agents = max_agents; cfg.vote_n = vote_n; cfg.offline = offline; cfg.subscription = subscription; + cfg.verbose = verbose; if !models.is_empty() { cfg.models = models; } let out = run_engagement(&base, cfg, mcp, false).await?; print_findings(&out); } - Cmd::Whitebox { path, models, max_agents, vote_n, offline, subscription } => { + Cmd::Whitebox { path, models, max_agents, vote_n, offline, subscription, verbose } => { let mut cfg = RunConfig::new(&path); cfg.max_agents = max_agents; cfg.vote_n = vote_n; cfg.offline = offline; cfg.subscription = subscription; + cfg.verbose = verbose; if !models.is_empty() { cfg.models = models; } let out = run_engagement(&base, cfg, false, true).await?; print_findings(&out); } - Cmd::Serve { port } => { - web::serve(base, port).await?; - } } Ok(()) } -/// Shared engagement runner for CLI `run` / `whitebox`. +/// Shared engagement runner for `run` / `whitebox`. async fn run_engagement(base: &Path, mut cfg: RunConfig, mcp: bool, whitebox: bool) -> anyhow::Result { let lib = agents::load(base); - let workdir = base.join("runs").join(format!("{}-{}", sanitize(&cfg.target), now_ts())); + + // Unique, sortable run id → runs// + let run_id = format!("ns-{}-{}", now_ts(), sanitize(&cfg.target)); + let workdir = base.join("runs").join(&run_id); + std::fs::create_dir_all(&workdir).ok(); cfg.workdir = Some(workdir.display().to_string()); cfg.rl_path = Some(base.join("data").join("rl_state_rs.json").display().to_string()); + write_status(&workdir, "running", &format!("\"target\":{:?}", cfg.target)); + println!(" ┌─ NeuroSploit v3.4.1"); + println!(" │ run id : {run_id}"); + println!(" │ target : {}", cfg.target); + println!(" │ models : {}", cfg.models.join(", ")); + println!(" │ output : {}", workdir.display()); + println!(" └─ mode : {}{}{}", + if whitebox { "white-box" } else { "black-box" }, + if cfg.subscription { " · subscription" } else { " · api" }, + if mcp { " · mcp" } else { "" }); + + // Playwright MCP: only for backends that support it; auto-provision if asked. let mcp_config = if mcp && cfg.subscription { - match harness::write_mcp_config(&workdir) { - Ok(p) => { - println!(" [*] Playwright MCP enabled → {}", p.display()); - Some(p.display().to_string()) - } - Err(e) => { - eprintln!(" [!] MCP config failed: {e}"); - None + let providers: Vec = cfg.models.iter().map(|m| ModelRef::parse(m).provider).collect(); + if providers.iter().any(|p| harness::mcp_supported(p)) { + match harness::ensure_playwright_mcp() { + Ok(()) => match harness::write_mcp_config(&workdir) { + Ok(p) => { + println!(" [*] Playwright MCP ready → {}", p.display()); + Some(p.display().to_string()) + } + Err(e) => { eprintln!(" [!] MCP config failed: {e}"); None } + }, + Err(e) => { eprintln!(" [!] Playwright MCP unavailable ({e}); using built-in tools"); None } } + } else { + eprintln!(" [!] selected backend(s) don't support MCP; using built-in tools"); + None } } else { None @@ -175,6 +218,14 @@ async fn run_engagement(base: &Path, mut cfg: RunConfig, mcp: bool, whitebox: bo harness::run(cfg, &lib, &pool, tx).await }; let _ = printer.await; + + // Final report via Typst (PDF if the `typst` binary is present) + HTML/MD already written. + match harness::report::typst_report(&out.target, &out.findings, &workdir) { + Ok(p) => println!(" [*] report → {}", p.display()), + Err(e) => eprintln!(" [!] typst report skipped: {e}"), + } + write_status(&workdir, "complete", &format!("\"findings\":{},\"agents_ran\":{}", out.findings.len(), out.agents_ran.len())); + println!(" ✓ COMPLETE — {} validated finding(s) · status: {}/status.json", out.findings.len(), workdir.display()); Ok(out) } @@ -189,16 +240,61 @@ fn print_findings(out: &RunOutput) { fn sanitize(s: &str) -> String { let s = s.replace("https://", "").replace("http://", ""); let mut o: String = s.chars().map(|c| if c.is_alphanumeric() { c } else { '_' }).collect(); - o.truncate(50); + o.truncate(40); let o = o.trim_matches('_').to_string(); - if o.is_empty() { - "target".into() - } else { - o - } + if o.is_empty() { "target".into() } else { o } } fn now_ts() -> u64 { use std::time::{SystemTime, UNIX_EPOCH}; SystemTime::now().duration_since(UNIX_EPOCH).map(|d| d.as_secs()).unwrap_or(0) } + +fn write_status(workdir: &Path, state: &str, extra: &str) { + let p = workdir.join("status.json"); + let _ = std::fs::write(&p, format!("{{\"state\":\"{state}\",\"ts\":{}{}}}", now_ts(), + if extra.is_empty() { String::new() } else { format!(",{extra}") })); +} + +fn prompt(q: &str, default: &str) -> String { + use std::io::Write; + print!(" {q}{}: ", if default.is_empty() { String::new() } else { format!(" [{default}]") }); + std::io::stdout().flush().ok(); + let mut s = String::new(); + std::io::stdin().read_line(&mut s).ok(); + let s = s.trim().to_string(); + if s.is_empty() { default.to_string() } else { s } +} + +/// Interactive wizard launched when `neurosploit` is run with no subcommand. +async fn interactive(base: &Path) -> anyhow::Result { + let lib = agents::load(base); + let backends = harness::installed_cli_backends(); + println!("\n ┌────────────────────────────────────────────┐"); + println!(" │ NeuroSploit v3.4.1 — interactive │"); + println!(" └────────────────────────────────────────────┘"); + println!(" agents: {} · detected CLI logins: {}\n", + lib.total(), if backends.is_empty() { "none".into() } else { backends.join(", ") }); + + let mode = prompt("Mode — (b)lack-box URL or (w)hite-box repo?", "b").to_lowercase(); + let whitebox = mode.starts_with('w'); + let target = if whitebox { + prompt("Repository path", "/tmp/DVWA") + } else { + prompt("Target URL", "http://testphp.vulnweb.com/") + }; + let model = prompt("Model (provider:model)", "anthropic:claude-opus-4-8"); + let sub = prompt("Use subscription login (no API key)? (y/n)", "y").to_lowercase().starts_with('y'); + let mcp = if whitebox { false } else { + prompt("Use Playwright MCP browser if available? (y/n)", "y").to_lowercase().starts_with('y') + }; + let max_agents: usize = prompt("Max agents (0 = all matching)", "5").parse().unwrap_or(5); + let vote_n: usize = prompt("Validator votes (N)", "3").parse().unwrap_or(3); + + let models = vec![model]; + Ok(if whitebox { + Cmd::Whitebox { path: target, models, max_agents, vote_n, offline: false, subscription: sub, verbose: true } + } else { + Cmd::Run { url: target, models, max_agents, vote_n, offline: false, subscription: sub, mcp, verbose: true } + }) +} diff --git a/neurosploit-rs/app/src/web.rs b/neurosploit-rs/app/src/web.rs deleted file mode 100644 index 88fe43e..0000000 --- a/neurosploit-rs/app/src/web.rs +++ /dev/null @@ -1,236 +0,0 @@ -//! Axum web dashboard for the v3.4.0 harness. - -use axum::{ - extract::{Path, State}, - response::Html, - routing::{get, post}, - Json, Router, -}; -use harness::{agents, models::ModelRef, pool::ModelPool, report, types::RunConfig}; -use serde_json::{json, Value}; -use std::{ - collections::HashMap, - path::PathBuf, - sync::{Arc, Mutex}, -}; - -struct RunState { - log: Vec, - done: bool, - result: Option, - report: Option, -} - -pub struct AppState { - base: PathBuf, - runs: Mutex>, -} - -pub async fn serve(base: PathBuf, port: u16) -> anyhow::Result<()> { - let state = Arc::new(AppState { base, runs: Mutex::new(HashMap::new()) }); - let app = Router::new() - .route("/", get(index)) - .route("/api/info", get(info)) - .route("/api/agents", get(agents_list)) - .route("/api/models", get(models_list)) - .route("/api/run", post(run)) - .route("/api/status/:id", get(status)) - .route("/report/:id", get(report_html)) - .with_state(state); - - let addr = format!("127.0.0.1:{port}"); - println!("NeuroSploit v3.4.0 dashboard → http://{addr}"); - let listener = tokio::net::TcpListener::bind(&addr).await?; - axum::serve(listener, app).await?; - Ok(()) -} - -async fn index() -> Html<&'static str> { - Html(include_str!("../web/index.html")) -} - -async fn info(State(st): State>) -> Json { - let lib = agents::load(&st.base); - let provs: Vec = harness::providers() - .iter() - .map(|p| json!({"key": p.key, "label": p.label, "kind": p.kind, "models": p.models})) - .collect(); - Json(json!({ - "version": "3.4.0", - "agents": {"vulns": lib.vulns.len(), "meta": lib.meta.len(), "recon": lib.recon.len(), "code": lib.code.len(), "total": lib.total()}, - "providers": provs, - "cli_backends": harness::installed_cli_backends(), - })) -} - -async fn agents_list(State(st): State>) -> Json { - let lib = agents::load(&st.base); - let v: Vec = lib - .vulns - .iter() - .chain(lib.recon.iter()) - .chain(lib.code.iter()) - .chain(lib.meta.iter()) - .map(|a| json!({"name": a.name, "title": a.title, "cwe": a.cwe, "kind": a.kind})) - .collect(); - Json(json!({ "agents": v })) -} - -async fn models_list() -> Json { - let provs: Vec = harness::providers() - .iter() - .map(|p| json!({"key": p.key, "label": p.label, "kind": p.kind, "models": p.models})) - .collect(); - Json(json!({ "providers": provs })) -} - -fn norm(u: &str) -> String { - if u.starts_with("http") { - u.to_string() - } else { - format!("https://{u}") - } -} - -async fn run(State(st): State>, Json(body): Json) -> Json { - let id = uuid::Uuid::new_v4().to_string(); - st.runs - .lock() - .unwrap() - .insert(id.clone(), RunState { log: vec![], done: false, result: None, report: None }); - - let st2 = st.clone(); - let id2 = id.clone(); - tokio::spawn(async move { - let base = st2.base.clone(); - - let mut targets: Vec = Vec::new(); - if let Some(arr) = body.get("targets").and_then(|v| v.as_array()) { - for t in arr { - if let Some(s) = t.as_str() { - if !s.trim().is_empty() { - targets.push(norm(s.trim())); - } - } - } - } - if targets.is_empty() { - if let Some(u) = body.get("url").and_then(|v| v.as_str()) { - if !u.trim().is_empty() { - targets.push(norm(u.trim())); - } - } - } - let models: Vec = body - .get("models") - .and_then(|v| v.as_array()) - .map(|a| a.iter().filter_map(|x| x.as_str().map(|s| s.to_string())).collect()) - .unwrap_or_default(); - let vote_n = body.get("vote_n").and_then(|v| v.as_u64()).unwrap_or(3) as usize; - let max_agents = body.get("max_agents").and_then(|v| v.as_u64()).unwrap_or(0) as usize; - let offline = body.get("offline").and_then(|v| v.as_bool()).unwrap_or(false); - let subscription = body.get("subscription").and_then(|v| v.as_bool()).unwrap_or(false); - let mcp = body.get("mcp").and_then(|v| v.as_bool()).unwrap_or(false); - let mode = body.get("mode").and_then(|v| v.as_str()).unwrap_or("web").to_string(); - // Whitebox uses a repo path instead of URLs. - if mode == "whitebox" { - if let Some(p) = body.get("repo").and_then(|v| v.as_str()) { - if !p.trim().is_empty() { - targets = vec![p.trim().to_string()]; - } - } - } - - let lib = agents::load(&base); - let refs: Vec = if models.is_empty() { - vec![ModelRef::parse("anthropic:claude-opus-4-8")] - } else { - models.iter().map(|s| ModelRef::parse(s)).collect() - }; - let mcp_config = if mcp && subscription { - harness::write_mcp_config(&base.join("runs").join("_mcp")).ok().map(|p| p.display().to_string()) - } else { - None - }; - let pool = ModelPool::with_auth(refs, 8, subscription, mcp_config); - let rl_path = base.join("data").join("rl_state_rs.json").display().to_string(); - - let (tx, mut rx) = tokio::sync::mpsc::channel::(256); - let stf = st2.clone(); - let idf = id2.clone(); - let fwd = tokio::spawn(async move { - while let Some(line) = rx.recv().await { - if let Ok(mut g) = stf.runs.lock() { - if let Some(r) = g.get_mut(&idf) { - r.log.push(line); - } - } - } - }); - - let mut all_findings = Vec::new(); - let mut all_ran = Vec::new(); - for url in &targets { - let mut cfg = RunConfig::new(url); - cfg.models = if models.is_empty() { - vec!["anthropic:claude-opus-4-8".into()] - } else { - models.clone() - }; - cfg.vote_n = vote_n; - cfg.max_agents = max_agents; - cfg.offline = offline; - cfg.subscription = subscription; - cfg.rl_path = Some(rl_path.clone()); - cfg.workdir = Some(base.join("runs").join(format!("{}-{}", slug(url), now_ts())).display().to_string()); - let _ = tx.send(format!("=== {}: {url} ===", if mode == "whitebox" { "whitebox repo" } else { "target" })).await; - let out = if mode == "whitebox" { - harness::run_whitebox(cfg, &lib, &pool, tx.clone()).await - } else { - harness::run(cfg, &lib, &pool, tx.clone()).await - }; - all_findings.extend(out.findings); - all_ran.extend(out.agents_ran); - } - drop(tx); - let _ = fwd.await; - - let report_html = report::html(targets.first().map(|s| s.as_str()).unwrap_or(""), &all_findings); - let result = json!({"findings": all_findings, "agents_ran": all_ran, "targets": targets}); - if let Ok(mut g) = st2.runs.lock() { - if let Some(r) = g.get_mut(&id2) { - r.result = Some(result); - r.report = Some(report_html); - r.done = true; - } - } - }); - - Json(json!({ "run_id": id })) -} - -async fn status(Path(id): Path, State(st): State>) -> Json { - let g = st.runs.lock().unwrap(); - match g.get(&id) { - Some(r) => Json(json!({"log": r.log, "done": r.done, "result": r.result, "has_report": r.report.is_some()})), - None => Json(json!({"error": "unknown run"})), - } -} - -async fn report_html(Path(id): Path, State(st): State>) -> Html { - let g = st.runs.lock().unwrap(); - Html(g.get(&id).and_then(|r| r.report.clone()).unwrap_or_else(|| "

no report

".into())) -} - -fn slug(s: &str) -> String { - let s = s.replace("https://", "").replace("http://", ""); - let mut o: String = s.chars().map(|c| if c.is_alphanumeric() { c } else { '_' }).collect(); - o.truncate(50); - let o = o.trim_matches('_').to_string(); - if o.is_empty() { "target".into() } else { o } -} - -fn now_ts() -> u64 { - use std::time::{SystemTime, UNIX_EPOCH}; - SystemTime::now().duration_since(UNIX_EPOCH).map(|d| d.as_secs()).unwrap_or(0) -} diff --git a/neurosploit-rs/app/web/index.html b/neurosploit-rs/app/web/index.html deleted file mode 100644 index dfc1a8a..0000000 --- a/neurosploit-rs/app/web/index.html +++ /dev/null @@ -1,327 +0,0 @@ - - - - - -NeuroSploit - - - -
- - -
-
-

Engagement

Configure and launch an autonomous run
-
online
-
- - -
-
- - -
-
-
-
-

Target

-
One or more URLs — the harness recons each, then intelligently selects matching agents.
-
- -
-
-
-
-
- - - -
-
-
-
-
-
-
-

Model panel

-
1st = primary · others fail over & form the validator jury.
-
-
-
-
-
-

Live execution

-
Recon → intelligent agent selection → parallel exploitation → N-model voting → report. Artifacts saved to runs/.
-
— idle. Launch an engagement to stream activity. —
-
-
- - -
-
-

Validated findings

-
Only findings confirmed by multi-model adversarial voting appear here.
-
-
No findings yet — run an engagement.
-
-
- - -
-
-

Report

-
HTML report + JSON/MD artifacts for reuse by other tools/AIs.
-
Run an engagement to generate a report.
-
-
- - -
-
-

Agent library

-
-
- - - - - - -
-
-
-
- - -
-
-

Providers & models

-
Use via API key or subscription (local CLI login). CLI-capable providers are tagged.
-
-
-
-
-
- - - diff --git a/neurosploit-rs/cj_proof2.png b/neurosploit-rs/cj_proof2.png new file mode 100644 index 0000000000000000000000000000000000000000..31bbd4df5ac692162d924149e24b81e86828d2e2 GIT binary patch literal 4298 zcmeAS@N?(olHy`uVBq!ia0y~yVA;UH!1R-Y2`Hj}#EXrAL4d*2#WAE}&YSCuybJ~$ z2Mj*l*`F$~>6+{RcYHuaw)sH9L&%ANp+Q5Dg@K`ClsOs>qlsWNGmMr5qb1^Kbud~j zja<~Xe@xPrTGeG28RFtmn?kq2WUMT c$a-dmPgWeCG-jW%0g5nqy85}Sb4q9e04S!JzW@LL literal 0 HcmV?d00001 diff --git a/neurosploit-rs/clickjack_proof.png b/neurosploit-rs/clickjack_proof.png new file mode 100644 index 0000000000000000000000000000000000000000..e0908f252ee8bee690ceed78ac97ad670342bc67 GIT binary patch literal 80392 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 z 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 { - cmd.arg("-p").arg("--model").arg(model); + cmd.arg("-p").arg("--model").arg(model).arg("--dangerously-skip-permissions"); + // Required to allow tool autonomy when running as root. + cmd.env("IS_SANDBOX", "1"); if let Some(mcp) = mcp_config { - cmd.arg("--mcp-config").arg(mcp).arg("--dangerously-skip-permissions"); - // Required to allow tool autonomy when running as root. - cmd.env("IS_SANDBOX", "1"); + cmd.arg("--mcp-config").arg(mcp); } } // Codex non-interactive exec (uses the ChatGPT/Codex login), prompt on stdin. "codex" => { - cmd.arg("exec").arg("--model").arg(model); + cmd.arg("exec").arg("--model").arg(model) + .arg("--dangerously-bypass-approvals-and-sandbox"); if let Some(mcp) = mcp_config { - cmd.arg("--config").arg(format!("mcp_config_file={mcp}")) - .arg("--dangerously-bypass-approvals-and-sandbox"); + cmd.arg("--config").arg(format!("mcp_config_file={mcp}")); } cmd.arg("-"); } @@ -173,13 +176,17 @@ impl ChatClient { } _ => {} } - cmd.stdin(Stdio::piped()).stdout(Stdio::piped()).stderr(Stdio::piped()); + cmd.stdin(Stdio::piped()).stdout(Stdio::piped()).stderr(Stdio::piped()).kill_on_drop(true); let mut child = cmd.spawn().map_err(|e| anyhow!("spawn {} failed: {}", bin, e))?; if let Some(mut stdin) = child.stdin.take() { stdin.write_all(prompt.as_bytes()).await?; // Drop closes stdin so the CLI processes the prompt and exits. } - let out = child.wait_with_output().await?; + // Cap a single agentic CLI turn so a stuck tool-loop can't hang the run. + let out = match tokio::time::timeout(Duration::from_secs(600), child.wait_with_output()).await { + Ok(r) => r?, + Err(_) => return Err(anyhow!("{} subscription CLI timed out after 600s", bin)), + }; let stdout = String::from_utf8_lossy(&out.stdout).trim().to_string(); let stderr = String::from_utf8_lossy(&out.stderr); if !out.status.success() { @@ -229,6 +236,35 @@ pub fn installed_cli_backends() -> Vec<&'static str> { ["claude", "codex", "grok", "gemini"].into_iter().filter(|b| binary_in_path(b)).collect() } +/// Does this provider's agentic CLI accept a Playwright MCP config? +/// Claude Code and Codex do; Gemini/Grok CLIs don't take an MCP-config flag, so +/// they fall back to their own built-in tools. +pub fn mcp_supported(provider: &str) -> bool { + matches!(provider, "anthropic" | "openai") +} + +/// Best-effort ensure the Playwright MCP server is available locally. Requires +/// `npx`; pre-warms `@playwright/mcp` so the first agent call isn't a cold start. +/// Returns Err with a clear reason when it can't be provisioned (caller then +/// degrades to built-in tools). +pub fn ensure_playwright_mcp() -> Result<()> { + if !binary_in_path("npx") { + return Err(anyhow!("npx (Node.js) not found — install Node to use Playwright MCP")); + } + // `npx -y @playwright/mcp@latest --help` installs the package into the npx + // cache on first run; ignore non-zero exit (some versions lack --help) as long + // as the package resolves. + let out = std::process::Command::new("npx") + .args(["-y", "@playwright/mcp@latest", "--help"]) + .stdout(Stdio::null()) + .stderr(Stdio::null()) + .status(); + match out { + Ok(_) => Ok(()), + Err(e) => Err(anyhow!("could not provision @playwright/mcp via npx: {e}")), + } +} + /// Write a Playwright `.mcp.json` into `dir` and return its path, so the agentic /// CLI can drive a real browser (DOM/JS/network/screenshots) during execution. pub fn write_mcp_config(dir: &std::path::Path) -> std::io::Result { diff --git a/neurosploit-rs/crates/harness/src/pipeline.rs b/neurosploit-rs/crates/harness/src/pipeline.rs index f42c0e9..9d9985a 100644 --- a/neurosploit-rs/crates/harness/src/pipeline.rs +++ b/neurosploit-rs/crates/harness/src/pipeline.rs @@ -11,6 +11,7 @@ use tokio::sync::mpsc::Sender; /// Result of an engagement run. #[derive(Default, Serialize)] pub struct RunOutput { + pub target: String, pub findings: Vec, pub agents_ran: Vec, pub candidates: usize, @@ -19,7 +20,28 @@ pub struct RunOutput { pub artifacts: Vec, } -const RECON_SYS: &str = "You are a web recon specialist. Map the target's attack surface and reply with a compact JSON object (tech, endpoints, auth, apis, ai_features). No prose."; +const RECON_SYS: &str = "You are a web recon specialist on an AUTHORIZED engagement. You have shell tools (curl etc.) — actively fetch the target, enumerate pages/params, and map the real attack surface. Do not ask for permission; proceed. Reply with a compact JSON object (tech, endpoints, params, auth, apis). No prose."; + +/// Tool-usage doctrine prepended to recon/exploit prompts so the agent knows +/// exactly what it may use. Best run on Kali Linux (or the Kali Docker image), +/// where these tools are preinstalled. +fn tool_doctrine(mcp_on: bool) -> String { + let browser = if mcp_on { + "A Playwright MCP browser IS available — use it for JS-heavy pages, DOM/JS execution, and to PROVE client-side issues (e.g. XSS firing); capture screenshots as evidence." + } else { + "No browser MCP is available — use `curl` (and `wget`) for all HTTP interaction; render/inspect responses directly." + }; + format!( + "TOOLING (authorized; best on Kali Linux or the kalilinux/kali-rolling Docker image):\n\ + - HTTP: `curl` (headers, methods, params, cookies), `wget`.\n\ + - Ports/services: `rustscan` if present, else `nmap`; if neither is installed you may \ + install via apt (`apt install -y nmap`), brew, or cargo (`cargo install rustscan`) — \ + otherwise probe common ports with `curl`/`nc`.\n\ + - Content/params: `ffuf`, `gobuster`, `gau`, `katana` when available.\n\ + - {browser}\n\ + Use only what is installed; degrade gracefully. Never run destructive or DoS actions.\n\n" + ) +} const VOTE_SYS: &str = "You are an adversarial security validator. Decide if the candidate finding is a REAL, reproducible, exploitable vulnerability with proof. Reply with JSON {\"verdict\":\"confirmed\"|\"rejected\",\"reason\":\"...\"}. Default to rejected when uncertain."; const CODE_VOTE_SYS: &str = "You are an adversarial source-code reviewer. Decide if the reported issue is a REAL vulnerability in the provided code (reachable, exploitable, not a false positive). Reply JSON {\"verdict\":\"confirmed\"|\"rejected\",\"reason\":\"...\"}."; @@ -40,9 +62,14 @@ pub async fn run(cfg: RunConfig, lib: &Library, pool: &ModelPool, tx: Sender { let _ = tx.send(format!("recon complete via {}", m.label())).await; + if cfg.verbose { + let snip: String = t.chars().take(280).collect(); + let _ = tx.send(format!(" recon> {}", snip.replace('\n', " "))).await; + } t } Err(e) => { @@ -63,22 +90,24 @@ pub async fn run(cfg: RunConfig, lib: &Library, pool: &ModelPool, tx: Sender = { - let mut sel: Vec = if chosen.is_empty() { - ranked.clone() - } else { - ranked.iter().filter(|a| chosen.iter().any(|c| c == &a.name)).cloned().collect() - }; + let selected: Vec = if !chosen.is_empty() { + let sel: Vec = + ranked.iter().filter(|a| chosen.iter().any(|c| c == &a.name)).cloned().collect(); if sel.is_empty() { - sel = ranked.clone(); + heuristic_select(&ranked, &recon, cap) + } else { + sel.into_iter().take(cap).collect() } - sel.into_iter().take(cap).collect() + } else { + // LLM selection failed/empty → recon-keyword heuristic, not a blind flat list. + let _ = tx.send("selection empty — using recon-keyword heuristic".into()).await; + heuristic_select(&ranked, &recon, cap) }; let _ = tx .send(format!("intelligently selected {} agent(s) matching recon: {}", selected.len(), @@ -87,6 +116,8 @@ pub async fn run(cfg: RunConfig, lib: &Library, pool: &ModelPool, tx: Sender)> = stream::iter(selected.iter().cloned()) .map(|ag| { let target = target.clone(); @@ -94,10 +125,18 @@ pub async fn run(cfg: RunConfig, lib: &Library, pool: &ModelPool, tx: Sender { let f = extract_findings(&text, &ag.name); @@ -145,7 +184,7 @@ pub async fn run_whitebox(cfg: RunConfig, lib: &Library, pool: &ModelPool, tx: S if cfg.offline || bytes == 0 { let artifacts = persist(&cfg, "{}", &context, &[]); - return RunOutput { findings: vec![], agents_ran: selected.iter().map(|a| a.name.clone()).collect(), candidates: 0, recon: String::new(), artifacts }; + return RunOutput { target: cfg.target.clone(), findings: vec![], agents_ran: selected.iter().map(|a| a.name.clone()).collect(), candidates: 0, recon: String::new(), artifacts }; } let raw: Vec<(String, String, Vec)> = stream::iter(selected.iter().cloned()) @@ -200,7 +239,12 @@ async fn select_agents(pool: &ModelPool, recon: &str, catalog: &[Agent], tx: &Se match pool.complete(SELECT_SYS, &user).await { Ok((m, text)) => { let names = parse_string_array(&text); - let _ = tx.send(format!("agent selection via {} → {} agent(s) chosen", m.label(), names.len())).await; + if names.is_empty() { + let preview: String = text.chars().take(120).collect(); + let _ = tx.send(format!("agent selection via {} returned no parseable list ({} chars): {}", m.label(), text.len(), preview.replace('\n', " "))).await; + } else { + let _ = tx.send(format!("agent selection via {} → {} agent(s) chosen", m.label(), names.len())).await; + } names } Err(e) => { @@ -217,16 +261,88 @@ fn parse_string_array(text: &str) -> Vec { } } +/// Fallback agent selection when the LLM selector fails: score each agent by +/// keyword overlap between its name/title and the recon text, always seed a +/// black-box baseline of high-yield web classes, and take the top `cap`. +fn heuristic_select(ranked: &[Agent], recon: &str, cap: usize) -> Vec { + const BASELINE: &[&str] = &[ + "sqli_error", "sqli_blind", "sqli_union", "xss_reflected", "xss_stored", "xss_dom", + "command_injection", "lfi", "path_traversal", "ssrf", "idor", "open_redirect", + "auth_bypass", "csrf", "ssti", "file_upload", "xxe", "information_disclosure", + "security_headers", "cors_misconfig", + ]; + let r = recon.to_lowercase(); + // Recon signal → agent-name substrings. Only agents whose surface the recon + // actually identified get the signal boost; the rest rely on the baseline. + let signals: &[(&str, &[&str])] = &[ + ("graphql", &["graphql"]), + ("jwt", &["jwt"]), + ("oauth", &["oauth", "oidc", "saml"]), + ("\"jwt\"", &["jwt"]), + ("api", &["api_", "bola", "bfla", "idor", "mass_assign", "rate_limit"]), + ("upload", &["file_upload", "zip_slip"]), + ("websocket", &["websocket"]), + ("\"ws\"", &["websocket"]), + ("graphql", &["graphql"]), + ("aws", &["aws_", "s3_", "imds", "cloud_"]), + ("gcp", &["gcp_", "gcs_", "metadata"]), + ("azure", &["azure_"]), + ("kubernetes", &["k8s_", "kubelet"]), + ("docker", &["docker_", "container_"]), + ("ai_features", &["llm_", "prompt_injection", "rag", "vector_db"]), + ("chat", &["llm_", "prompt_injection"]), + ("jinja", &["ssti"]), + ("flask", &["ssti", "ssrf", "command_injection"]), + ("php", &["lfi", "rfi", "sqli", "command_injection"]), + ("template", &["ssti", "csti"]), + ("redirect", &["open_redirect"]), + ("login", &["auth_bypass", "brute_force", "sqli", "default_credentials"]), + ("search", &["xss", "sqli"]), + ("cache", &["cache", "smuggl"]), + ]; + let mut scored: Vec<(i32, &Agent)> = ranked + .iter() + .map(|a| { + let mut score = 0; + if BASELINE.contains(&a.name.as_str()) { + score += 4; + } + // recon-signal mapping: boost agents matching identified surface + for (sig, names) in signals { + if r.contains(sig) && names.iter().any(|n| a.name.contains(n)) { + score += 6; + } + } + // direct keyword overlap with recon text + for tok in a.name.split('_') { + if tok.len() >= 4 && r.contains(tok) { + score += 2; + } + } + (score, a) + }) + .collect(); + scored.sort_by(|x, y| y.0.cmp(&x.0)); + let mut out: Vec = scored.iter().filter(|(s, _)| *s > 0).map(|(_, a)| (*a).clone()).collect(); + if out.is_empty() { + out = ranked.to_vec(); + } + out.into_iter().take(cap).collect() +} + async fn validate(candidates: Vec, pool: &ModelPool, sys: &str, vote_n: usize, tx: &Sender) -> Vec { + // Prefer a model other than the primary (likely finder) to adjudicate. + let finder = pool.candidates.first().map(|m| m.label()); let validated: Vec = stream::iter(candidates.into_iter()) .map(|mut f| { let txc = tx.clone(); + let finder = finder.clone(); async move { let q = format!( "Finding: {} | severity {} | {} | at {} | payload {} | evidence {}", f.title, f.severity, f.cwe, f.endpoint, f.payload, f.evidence ); - let (yes, total) = pool.vote(sys, &q, vote_n).await; + let (yes, total) = pool.vote(sys, &q, vote_n, finder.as_deref()).await; f.validated = total > 0 && yes * 2 >= total; f.votes = format!("{yes}/{total}"); if f.confidence == 0.0 && total > 0 { @@ -268,6 +384,7 @@ async fn finish(cfg: RunConfig, _lib: &Library, recon: String, transcript: Strin } RunOutput { + target: cfg.target.clone(), candidates: findings.len(), findings, agents_ran: selected.iter().map(|a| a.name.clone()).collect(), diff --git a/neurosploit-rs/crates/harness/src/pool.rs b/neurosploit-rs/crates/harness/src/pool.rs index 8145be8..cd087de 100644 --- a/neurosploit-rs/crates/harness/src/pool.rs +++ b/neurosploit-rs/crates/harness/src/pool.rs @@ -87,8 +87,18 @@ impl ModelPool { /// Ask up to `n` distinct models the same yes/no validation question and /// return (confirmations, total_votes). A model answering "yes"/"confirmed" /// counts as a confirmation. Used to cut false positives. - pub async fn vote(&self, system: &str, user: &str, n: usize) -> (usize, usize) { - let panel: Vec = self.candidates.iter().take(n.max(1)).cloned().collect(); + /// + /// `skip` names the model that produced the finding; when the panel has more + /// than one model, that model is moved to the back so a DIFFERENT model + /// adjudicates first (cross-model false-positive validation). + pub async fn vote(&self, system: &str, user: &str, n: usize, skip: Option<&str>) -> (usize, usize) { + let mut ordered: Vec = self.candidates.clone(); + if let Some(finder) = skip { + if ordered.len() > 1 { + ordered.sort_by_key(|m| m.label() == finder); // finder (true) sorts last + } + } + let panel: Vec = ordered.into_iter().take(n.max(1)).collect(); let mut confirmed = 0usize; let mut total = 0usize; for m in &panel { diff --git a/neurosploit-rs/crates/harness/src/report.rs b/neurosploit-rs/crates/harness/src/report.rs index 111d9bd..e7a723c 100644 --- a/neurosploit-rs/crates/harness/src/report.rs +++ b/neurosploit-rs/crates/harness/src/report.rs @@ -1,4 +1,9 @@ use crate::types::Finding; +use std::path::{Path, PathBuf}; + +/// The blank, structured Typst template (rendering logic). Data (`meta`, +/// `findings`) is prepended by `typst_report` to make a self-contained file. +const TYPST_TEMPLATE: &str = include_str!("../../../templates/report.typ"); fn sev_rank(s: &str) -> u8 { match s { @@ -74,9 +79,70 @@ pub fn html(target: &str, findings: &[Finding]) -> String { h4{{margin:12px 0 3px;font-size:12px;text-transform:uppercase;letter-spacing:.5px;color:#8b5cf6}}\ .b{{color:#8b5cf6;font-weight:800}}\

NeuroSploit Penetration Test Report

\ -
Target: {t} · v3.4.0 Rust harness · multi-model validated
\ +
Target: {t} · v3.4.1 Rust harness · multi-model validated
\
{chips}

Findings ({n})

{body}\

Authorized testing only. Findings confirmed by multi-model adversarial voting.

", t = esc(target), chips = chips, n = sorted.len(), body = body, ) } + +// ===== Typst report ===== + +/// Is the `typst` binary available on PATH? +fn typst_available() -> bool { + std::env::var_os("PATH") + .map(|p| std::env::split_paths(&p).any(|d| d.join("typst").is_file())) + .unwrap_or(false) +} + +fn sorted_findings(findings: &[Finding]) -> Vec { + let mut v = findings.to_vec(); + v.sort_by_key(|f| sev_rank(&f.severity)); + v +} + +/// Escape a string for embedding inside a Typst `"..."` literal (single line). +fn tq(s: &str) -> String { + let cleaned: String = s.replace('\\', "\\\\").replace('"', "\\\"").replace(['\n', '\r'], " "); + format!("\"{}\"", cleaned) +} + +/// Generate a self-contained `report.typ` (data + bundled template) in `dir` +/// and compile it to `report.pdf` via the `typst` binary. Falls back to leaving +/// the `.typ` when `typst` is unavailable. +pub fn typst_report(target: &str, findings: &[Finding], dir: &Path) -> std::io::Result { + std::fs::create_dir_all(dir)?; + let run_id = dir.file_name().and_then(|s| s.to_str()).unwrap_or("run").to_string(); + + let mut data = String::new(); + data.push_str(&format!( + "#let meta = (target: {}, run_id: {}, generated: {}, model: {})\n", + tq(target), tq(&run_id), tq("NeuroSploit v3.4.1"), tq("multi-model") + )); + data.push_str("#let findings = (\n"); + for f in sorted_findings(findings) { + data.push_str(&format!( + " (severity: {}, title: {}, agent: {}, cwe: {}, cvss: {}, endpoint: {}, payload: {}, evidence: {}, impact: {}, remediation: {}, votes: {}, confidence: {}),\n", + tq(&f.severity), tq(&f.title), tq(&f.agent), tq(&f.cwe), tq(&f.cvss), + tq(&f.endpoint), tq(&f.payload), tq(&f.evidence), tq(&f.impact), + tq(&f.remediation), tq(&f.votes), f.confidence, + )); + } + data.push_str(")\n\n"); + + let typ_path = dir.join("report.typ"); + std::fs::write(&typ_path, format!("{data}{TYPST_TEMPLATE}"))?; + + if typst_available() { + let pdf_path = dir.join("report.pdf"); + match std::process::Command::new("typst") + .arg("compile").arg(&typ_path).arg(&pdf_path).output() + { + Ok(o) if o.status.success() && pdf_path.exists() => return Ok(pdf_path), + Ok(o) => eprintln!("typst compile failed: {}", + String::from_utf8_lossy(&o.stderr).lines().next().unwrap_or("").trim()), + Err(e) => eprintln!("typst not runnable: {e}"), + } + } + Ok(typ_path) +} diff --git a/neurosploit-rs/crates/harness/src/types.rs b/neurosploit-rs/crates/harness/src/types.rs index 66d0594..d9cc0ad 100644 --- a/neurosploit-rs/crates/harness/src/types.rs +++ b/neurosploit-rs/crates/harness/src/types.rs @@ -80,6 +80,9 @@ pub struct RunConfig { /// Path to the RL reward state file. #[serde(default)] pub rl_path: Option, + /// Verbose: log each agent as it launches, recon snippet, and votes. + #[serde(default)] + pub verbose: bool, } fn default_vote() -> usize { @@ -101,6 +104,7 @@ impl RunConfig { subscription: false, workdir: None, rl_path: None, + verbose: false, } } } diff --git a/neurosploit-rs/templates/report.typ b/neurosploit-rs/templates/report.typ new file mode 100644 index 0000000..14ad44f --- /dev/null +++ b/neurosploit-rs/templates/report.typ @@ -0,0 +1,97 @@ +// NeuroSploit v3.4.1 — Typst report template (blank, structured). +// +// The harness generates `report.typ` per run by prepending a `findings` array +// and a `meta` dict, then including this template's rendering logic. This file +// is the reference/blank template: it renders a cover, an executive summary with +// severity counts, and one section per finding. Compile with: +// typst compile report.typ report.pdf +// +// Expected inputs (defined above this template in the generated file): +// #let meta = (target: "", run_id: "", generated: "", model: "") +// #let findings = ( (severity: "", title: "", agent: "", cwe: "", cvss: "", +// endpoint: "", payload: "", evidence: "", impact: "", +// remediation: "", votes: "", confidence: 0.0), ... ) + +#let sevcolor = ( + Critical: rgb("#c0392b"), High: rgb("#e67e22"), Medium: rgb("#f1c40f"), + Low: rgb("#3498db"), Info: rgb("#7f8c8d"), +) +#let sevbadge(s) = box( + fill: sevcolor.at(s, default: rgb("#7f8c8d")), inset: (x: 5pt, y: 2pt), + radius: 3pt, text(fill: white, weight: "bold", size: 8pt, upper(s)), +) +#let sevrank(s) = (Critical: 0, High: 1, Medium: 2, Low: 3, Info: 4).at(s, default: 5) + +#set page(margin: 2cm, numbering: "1", footer: context [ + #set text(size: 8pt, fill: gray) + NeuroSploit v3.4.1 · #meta.target · confidential + #h(1fr) #counter(page).display() +]) +#set text(font: ("Helvetica Neue", "Helvetica", "Arial"), size: 10pt) +#set heading(numbering: none) + +// ---- Cover ---- +#v(3cm) +#align(center)[ + #text(28pt, weight: "bold")[#text(fill: rgb("#7c5cff"))[Neuro]Sploit] + #v(2pt) + #text(15pt, fill: gray)[Penetration Test Report] + #v(1cm) + #text(13pt)[Target: #strong(meta.target)] + #v(4pt) + #text(10pt, fill: gray)[Run #meta.run_id · #meta.generated · models: #meta.model] +] +#pagebreak() + +// ---- Executive summary ---- += Executive Summary + +#let counts = (:) +#for f in findings { + counts.insert(f.severity, counts.at(f.severity, default: 0) + 1) +} +#if findings.len() == 0 [ + No validated findings were produced for this engagement. All candidate issues + were either unproven or rejected by multi-model adversarial validation. +] else [ + This engagement produced #strong(str(findings.len())) validated finding(s), + each confirmed by multi-model voting. + + #v(6pt) + #grid(columns: 5, gutter: 8pt, + ..("Critical", "High", "Medium", "Low", "Info").map(s => box( + width: 100%, inset: 8pt, radius: 6pt, stroke: 0.5pt + sevcolor.at(s), + align(center)[ + #text(18pt, weight: "bold", fill: sevcolor.at(s))[#str(counts.at(s, default: 0))] + #v(-4pt) #text(8pt, upper(s)) + ], + )) + ) +] + +#v(10pt) +#line(length: 100%, stroke: 0.5pt + gray) + +// ---- Findings ---- += Findings + +#let sorted = findings.sorted(key: f => sevrank(f.severity)) +#if sorted.len() == 0 [ + #text(fill: gray)[_Nothing to report._] +] +#for (i, f) in sorted.enumerate() [ + #block(breakable: false, width: 100%, inset: 10pt, radius: 6pt, + stroke: (left: 3pt + sevcolor.at(f.severity, default: gray), rest: 0.5pt + rgb("#dddddd")))[ + #sevbadge(f.severity) #h(6pt) #text(12pt, weight: "bold")[#str(i + 1). #f.title] + #v(4pt) + #text(9pt, fill: gray)[ + agent: #raw(f.agent) · CWE: #f.cwe · CVSS: #f.cvss · votes: #f.votes · confidence: #str(f.confidence) + ] + #v(2pt) #text(9pt)[Endpoint: #raw(f.endpoint)] + #v(5pt) #strong[Payload] #linebreak() #raw(f.payload) + #v(3pt) #strong[Evidence] #linebreak() #raw(f.evidence) + #v(3pt) #strong[Impact:] #f.impact + #v(2pt) #strong[Remediation:] #f.remediation + ] + #v(8pt) +]