v3.4.x: intelligent agent selection, whitebox, recon/code agents, Gemini, artifacts, RL, XBOW GUI

Harness intelligence:
- After recon, the model SELECTS which specialist agents match the target
  (select_agents) — runs the relevant subset, not blindly top-N
- RL reward store (rl.rs): per-agent weights persist to data/rl_state_rs.json,
  reward validated findings (severity-weighted), decay idle, bias next run
- Run artifacts persisted as JSON + MD (recon, exploitation transcript,
  findings, html report) under runs/<target>-<ts>/ for reuse by other AIs

Whitebox mode:
- run_whitebox: walks a repo, builds bounded source context, runs code agents,
  validates by adversarial vote. CLI `whitebox <path>` + web "White-box" mode

Agents: +12 recon (subdomain/tech/js/api/secrets/dns/content/param/waf/cloud/
graphql/osint) and +24 code SAST reviewers (sqli/cmdi/path/ssrf/xss/deser/
secrets/crypto/authz/idor/xxe/redirect/ssti/race/eval/csrf/random/logging/
upload/mass-assign/jwt/cors). Loader gains recon/ + code/ categories → 249 total

Models: +Google Gemini provider (API + gemini CLI subscription); installed_cli_
backends now detects gemini; chat_cli handles gemini/codex/grok + optional
Playwright MCP (.mcp.json) on the subscription path with autonomy flags

GUI: full XBOW-style redesign — sidebar (Operate/Library), topbar status, mode
segment (black-box/white-box), model panel, live console, severity cards,
agent browser with category filters, models view; responsive + aligned

Verified: cargo build --release clean; CLI agents/whitebox; LIVE subscription
run shows model selecting 23→4 agents, RL update, artifacts written; GUI +
white-box toggle in Playwright.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
CyberSecurityUP
2026-06-23 11:39:56 -03:00
co-authored by Claude Opus 4.8
parent bf56184912
commit 3ca3f269ee
53 changed files with 3684 additions and 209 deletions
+100 -23
View File
@@ -3,8 +3,8 @@
mod web;
use clap::{Parser, Subcommand};
use harness::{agents, models::ModelRef, pool::ModelPool, report, types::RunConfig};
use std::path::PathBuf;
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")]
@@ -37,6 +37,24 @@ enum Cmd {
/// instead of HTTP API keys.
#[arg(long)]
subscription: bool,
/// Enable Playwright MCP (browser proof) on the subscription/CLI path.
#[arg(long)]
mcp: 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<String>,
#[arg(long, default_value_t = 0)]
max_agents: usize,
#[arg(long, default_value_t = 2)]
vote_n: usize,
#[arg(long)]
offline: bool,
#[arg(long)]
subscription: bool,
},
/// Show agent library counts.
Agents,
@@ -88,7 +106,7 @@ async fn main() -> anyhow::Result<()> {
}
}
}
Cmd::Run { url, models, max_agents, vote_n, offline, subscription } => {
Cmd::Run { url, models, max_agents, vote_n, offline, subscription, mcp } => {
let url = if url.starts_with("http") { url } else { format!("https://{url}") };
let mut cfg = RunConfig::new(&url);
cfg.max_agents = max_agents;
@@ -98,26 +116,20 @@ async fn main() -> anyhow::Result<()> {
if !models.is_empty() {
cfg.models = models;
}
let lib = agents::load(&base);
let refs: Vec<ModelRef> = cfg.models.iter().map(|s| ModelRef::parse(s)).collect();
let pool = ModelPool::with_auth(refs, cfg.concurrency, cfg.subscription);
let (tx, mut rx) = tokio::sync::mpsc::channel::<String>(256);
let printer = tokio::spawn(async move {
while let Some(line) = rx.recv().await {
println!(" [*] {line}");
}
});
let out = harness::run(cfg.clone(), &lib, &pool, tx).await;
let _ = printer.await;
println!("\n=== {} validated finding(s) ===", out.findings.len());
println!("{}", serde_json::to_string_pretty(&out.findings)?);
let html = report::html(&url, &out.findings);
std::fs::create_dir_all(base.join("reports")).ok();
let rp = base.join("reports").join("report_rs.html");
std::fs::write(&rp, html).ok();
println!("report → {}", rp.display());
let out = run_engagement(&base, cfg, mcp, false).await?;
print_findings(&out);
}
Cmd::Whitebox { path, models, max_agents, vote_n, offline, subscription } => {
let mut cfg = RunConfig::new(&path);
cfg.max_agents = max_agents;
cfg.vote_n = vote_n;
cfg.offline = offline;
cfg.subscription = subscription;
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?;
@@ -125,3 +137,68 @@ async fn main() -> anyhow::Result<()> {
}
Ok(())
}
/// Shared engagement runner for CLI `run` / `whitebox`.
async fn run_engagement(base: &Path, mut cfg: RunConfig, mcp: bool, whitebox: bool) -> anyhow::Result<RunOutput> {
let lib = agents::load(base);
let workdir = base.join("runs").join(format!("{}-{}", sanitize(&cfg.target), now_ts()));
cfg.workdir = Some(workdir.display().to_string());
cfg.rl_path = Some(base.join("data").join("rl_state_rs.json").display().to_string());
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
}
}
} else {
None
};
let refs: Vec<ModelRef> = cfg.models.iter().map(|s| ModelRef::parse(s)).collect();
let pool = ModelPool::with_auth(refs, cfg.concurrency, cfg.subscription, mcp_config);
let (tx, mut rx) = tokio::sync::mpsc::channel::<String>(256);
let printer = tokio::spawn(async move {
while let Some(line) = rx.recv().await {
println!(" [*] {line}");
}
});
let out = if whitebox {
harness::run_whitebox(cfg, &lib, &pool, tx).await
} else {
harness::run(cfg, &lib, &pool, tx).await
};
let _ = printer.await;
Ok(out)
}
fn print_findings(out: &RunOutput) {
println!("\n=== {} validated finding(s) ===", out.findings.len());
println!("{}", serde_json::to_string_pretty(&out.findings).unwrap_or_default());
if !out.artifacts.is_empty() {
println!("artifacts: {}", out.artifacts.join(", "));
}
}
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);
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)
}