diff --git a/README.md b/README.md index fafe74d..b8c36a8 100755 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -

๐Ÿง  NeuroSploit v3.6.2

+

๐Ÿง  NeuroSploit v3.6.3

JoasASantos%2FNeuroSploit | Trendshift @@ -12,7 +12,7 @@

- + @@ -28,14 +28,15 @@ > > ๐Ÿ“– **New here? Read the [full Tutorial & User Guide โ†’](TUTORIAL.md)** โ€” every mode, flag, config and example explained. -> ๐Ÿ†• **New in v3.6.2 โ€” live Codex streaming + full activity feed:** -> Codex runs now **stream tool-by-tool** (`codex exec --json`) exactly like -> Claude Code โ€” every command, file edit, MCP call and token count appears live -> instead of a silent black box, so a long intense recon never looks frozen. The -> **`/logs`** feed and **`/status`** sign-of-line now capture the *actual -> commands each agent runs* (subfinder, httpx, nmap, curlโ€ฆ), not just pipeline -> phases. *(v3.6.1 added GPT-5.6 sol/terra/luna, the Codex exit-1 fix, `/logs` -> and richer `/status`; v3.6.0 added AI-agent/LLM/MCP/Skills/n8n testing + +> ๐Ÿ†• **New in v3.6.3 โ€” resumable interrupted runs + crash-proof browsing:** +> If a run is interrupted (terminal closed, Ctrl-C, crash), **`/continue` now +> relaunches it** on the same target and **carries the recovered findings +> forward** โ€” the offer appears right at launch. And opening **`/results`**, +> **`/finding`** or **`/report`** while a run streams no longer corrupts the +> terminal: live output is paused for the picker (still captured in `/logs`) and +> restored on exit. *(v3.6.2 added live Codex tool-by-tool streaming + the full +> activity feed in `/logs`/`/status`; v3.6.1 added GPT-5.6 sol/terra/luna and +> the Codex exit-1 fix; v3.6.0 added AI-agent/LLM/MCP/Skills/n8n testing + > onboarding wizard + Cloud scope.)* > *(v3.5.4 added robust attack chaining + false-positive reduction; v3.5.3 > GitHub/GitLab/Jira **[integrations](TUTORIAL-INTEGRATION.md)**; v3.5.2 the DEPTH diff --git a/RELEASE.md b/RELEASE.md index 635aff8..b0b9d85 100644 --- a/RELEASE.md +++ b/RELEASE.md @@ -1,7 +1,7 @@ -# NeuroSploit v3.6.2 โ€” Release Notes +# NeuroSploit v3.6.3 โ€” Release Notes **Release Date:** July 2026 -**Codename:** Live Codex +**Codename:** Resume & Recover **License:** MIT **Credits:** Joas A Santos & Red Team Leaders @@ -9,6 +9,26 @@ ## Highlights +- **Interrupted runs are resumable.** When a run is cut off (terminal closed, + Ctrl-C, crash, SSH drop), its findings were already checkpointed live and + recovered as a run on the next launch. Now `/continue` (or `/resume`) also + **relaunches the engagement** on the same target and **carries those findings + forward** โ€” steering agents to widen coverage and chain from what was already + found instead of re-reporting it. The offer is shown at launch right under the + recovery line. A fresh `/run` supersedes the pending resume. +- **Browsing no longer kills a live run.** Opening `/results`, `/finding` or + `/report` while a run streams used to let the background printer and the + full-screen picker fight over the terminal โ€” pressing Ctrl-C to escape could + take the whole process down. Live output is now paused while any picker is + open (still captured in `/logs`) and restored when you exit, so browsing + findings mid-run is safe. +- Findings merge (dedup by title + endpoint) across the interrupted and + continued runs, and the merged report is rewritten to include everything. + +--- + +## Previously in v3.6.2 + - **Codex now streams live, tool-by-tool.** `codex exec` is driven with `--json` and its JSONL event stream is parsed into the same categorized activity feed as Claude Code: every shell command it runs (`exec:`), file edit (`edit:`), diff --git a/TUTORIAL.md b/TUTORIAL.md index 537b482..222c87e 100644 --- a/TUTORIAL.md +++ b/TUTORIAL.md @@ -1,4 +1,4 @@ -# NeuroSploit โ€” Tutorial & User Guide (v3.6.2) +# NeuroSploit โ€” Tutorial & User Guide (v3.6.3) A complete, hands-on guide to installing, configuring and running NeuroSploit โ€” the autonomous, multi-model penetration-testing harness. @@ -98,7 +98,7 @@ Agents **degrade gracefully**: if `rustscan` is absent they use `nmap`; if neith ### Verify ```bash -neurosploit --version # neurosploit 3.6.2 +neurosploit --version # neurosploit 3.6.3 neurosploit agents # {"vulns":196,...,"chains":12,"total":417} neurosploit models # all providers & models ``` diff --git a/neurosploit-rs/Cargo.lock b/neurosploit-rs/Cargo.lock index a5b13b5..2510894 100644 --- a/neurosploit-rs/Cargo.lock +++ b/neurosploit-rs/Cargo.lock @@ -871,7 +871,7 @@ dependencies = [ [[package]] name = "neurosploit" -version = "3.6.2" +version = "3.6.3" dependencies = [ "anyhow", "clap", @@ -888,7 +888,7 @@ dependencies = [ [[package]] name = "neurosploit-harness" -version = "3.6.2" +version = "3.6.3" dependencies = [ "anyhow", "futures", diff --git a/neurosploit-rs/Cargo.toml b/neurosploit-rs/Cargo.toml index f583193..2ef74dc 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.6.2" +version = "3.6.3" edition = "2021" license = "MIT" repository = "https://github.com/JoasASantos/NeuroSploit" diff --git a/neurosploit-rs/app/src/main.rs b/neurosploit-rs/app/src/main.rs index 40fc0bc..f21e6e1 100644 --- a/neurosploit-rs/app/src/main.rs +++ b/neurosploit-rs/app/src/main.rs @@ -1,4 +1,4 @@ -//! NeuroSploit v3.6.2 โ€” interactive harness + CLI (`run` / `whitebox` / `agents` / `models`). +//! NeuroSploit v3.6.3 โ€” interactive harness + CLI (`run` / `whitebox` / `agents` / `models`). mod repl; mod tui; @@ -11,8 +11,8 @@ use std::path::{Path, PathBuf}; #[command( name = "neurosploit", version, - about = "NeuroSploit v3.6.2 โ€” multi-model autonomous pentest harness", - long_about = "NeuroSploit v3.6.2 โ€” a Rust multi-model harness that drives a pool of LLMs \ + about = "NeuroSploit v3.6.3 โ€” multi-model autonomous pentest harness", + long_about = "NeuroSploit v3.6.3 โ€” 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\ @@ -721,7 +721,7 @@ pub(crate) fn spawn_engagement(base: &Path, mut cfg: RunConfig, mcp: bool, mode: println!(" โ”‚ ua : {ua}"); write_status(&workdir, "running", &format!("\"target\":{:?}", cfg.target)); - println!(" โ”Œโ”€ NeuroSploit v3.6.2 ยท by Joas A Santos & Red Team Leaders"); + println!(" โ”Œโ”€ NeuroSploit v3.6.3 ยท by Joas A Santos & Red Team Leaders"); println!(" โ”‚ run id : {run_id}"); println!(" โ”‚ target : {}", cfg.target); println!(" โ”‚ models : {}", cfg.models.join(", ")); diff --git a/neurosploit-rs/app/src/repl.rs b/neurosploit-rs/app/src/repl.rs index 6787279..8f346a0 100644 --- a/neurosploit-rs/app/src/repl.rs +++ b/neurosploit-rs/app/src/repl.rs @@ -1,4 +1,4 @@ -//! NeuroSploit v3.6.2 โ€” interactive session (Claude-Code / Codex / Cursor-CLI style). +//! NeuroSploit v3.6.3 โ€” interactive session (Claude-Code / Codex / Cursor-CLI style). //! //! Launched when `neurosploit` runs with no subcommand. A persistent REPL with //! real line editing (arrow-key history recall, Ctrl-A/E/K, paste), model @@ -120,6 +120,10 @@ struct ActiveRun { resume: Arc, /// Fallback models to try first, pushed by /continue . fallback: Arc>>, + /// Suppress live background printing while a full-screen picker (dialoguer) + /// is open, so the two don't fight over the terminal and corrupt it. The + /// stream is still ingested (feed/checkpoint), just not printed meanwhile. + quiet: Arc, } /// On-disk checkpoint of an in-flight run's findings/commands, written live so a @@ -353,7 +357,7 @@ pub async fn repl(base: &Path) -> anyhow::Result<()> { let backends = harness::installed_cli_backends(); println!("\x1b[1m"); println!(" โ–ˆโ–ˆโ–ˆโ•— โ–ˆโ–ˆโ•—โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ•—โ–ˆโ–ˆโ•— โ–ˆโ–ˆโ•—โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ•— โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ•—"); - println!(" โ–ˆโ–ˆโ–ˆโ–ˆโ•— โ–ˆโ–ˆโ•‘โ–ˆโ–ˆโ•”โ•โ•โ•โ•โ•โ–ˆโ–ˆโ•‘ โ–ˆโ–ˆโ•‘โ–ˆโ–ˆโ•”โ•โ•โ–ˆโ–ˆโ•—โ–ˆโ–ˆโ•”โ•โ•โ•โ–ˆโ–ˆโ•— NeuroSploit v3.6.2"); + println!(" โ–ˆโ–ˆโ–ˆโ–ˆโ•— โ–ˆโ–ˆโ•‘โ–ˆโ–ˆโ•”โ•โ•โ•โ•โ•โ–ˆโ–ˆโ•‘ โ–ˆโ–ˆโ•‘โ–ˆโ–ˆโ•”โ•โ•โ–ˆโ–ˆโ•—โ–ˆโ–ˆโ•”โ•โ•โ•โ–ˆโ–ˆโ•— NeuroSploit v3.6.3"); println!(" โ–ˆโ–ˆโ•”โ–ˆโ–ˆโ•— โ–ˆโ–ˆโ•‘โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ•— โ–ˆโ–ˆโ•‘ โ–ˆโ–ˆโ•‘โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ•”โ•โ–ˆโ–ˆโ•‘ โ–ˆโ–ˆโ•‘ interactive harness"); println!(" โ–ˆโ–ˆโ•‘โ•šโ–ˆโ–ˆโ•—โ–ˆโ–ˆโ•‘โ–ˆโ–ˆโ•”โ•โ•โ• โ–ˆโ–ˆโ•‘ โ–ˆโ–ˆโ•‘โ–ˆโ–ˆโ•”โ•โ•โ–ˆโ–ˆโ•—โ–ˆโ–ˆโ•‘ โ–ˆโ–ˆโ•‘ by Joas A Santos"); println!(" โ–ˆโ–ˆโ•‘ โ•šโ–ˆโ–ˆโ–ˆโ–ˆโ•‘โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ•—โ•šโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ•”โ•โ–ˆโ–ˆโ•‘ โ–ˆโ–ˆโ•‘โ•šโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ•”โ• & Red Team Leaders"); @@ -370,6 +374,9 @@ pub async fn repl(base: &Path) -> anyhow::Result<()> { if resumed || past > 0 { println!(" โ†ป resumed project session from {} โ€” {} past run(s)", proj_dir().display(), past); } + // A recovered interrupted run, carried in memory so `/continue` can relaunch + // the engagement on the same target with these findings folded forward. + let mut resumable: Option<(String, Vec)> = None; // Recover an interrupted run (REPL was quit/crashed mid-engagement): its // live findings were checkpointed to disk โ€” fold them into /runs so // /results, /finding and /report still work. @@ -384,6 +391,8 @@ pub async fn repl(base: &Path) -> anyhow::Result<()> { save_runs(base, &h); println!(" \x1b[1;33mโ†ป recovered interrupted run on {} โ€” {} finding(s) saved as run #{}\x1b[0m (/results {id} ยท /report {id})", cp.target, cp.findings.len(), id); + println!(" \x1b[36m โ†ณ /continue to keep testing this target โ€” the {} finding(s) carry forward\x1b[0m", cp.findings.len()); + resumable = Some((cp.target.clone(), cp.findings.clone())); } clear_checkpoint(); } @@ -402,7 +411,7 @@ pub async fn repl(base: &Path) -> anyhow::Result<()> { if !queue.is_empty() && active.as_ref().map(|a| a.done.load(Ordering::Relaxed)).unwrap_or(true) { let next = queue.remove(0); println!("\n \x1b[1;35mโ–ถ next target\x1b[0m ({} left): {next}", queue.len()); - active = start_background(base, &s, &mut reader, history.clone(), Some(&next)).await; + active = start_background(base, &s, &mut reader, history.clone(), Some(&next), vec![]).await; } println!("{}", context_prompt(&s)); // dim context line above the prompt let Some(line) = reader.read(PROMPT) else { println!("\n bye."); break }; @@ -610,6 +619,7 @@ pub async fn repl(base: &Path) -> anyhow::Result<()> { if active.as_ref().map(|a| !a.done.load(Ordering::Relaxed)).unwrap_or(false) { println!(" a run is already active โ€” /status to check, /stop to halt it."); } else { + resumable = None; // a fresh /run supersedes any recovered interrupted run save_session(&s); // Multiple comma-separated targets โ†’ run sequentially (queue the rest). let targets = session_targets(&s); @@ -620,7 +630,7 @@ pub async fn repl(base: &Path) -> anyhow::Result<()> { if !queue.is_empty() { println!(" \x1b[1;35mโ–ถ multi-target\x1b[0m: {} URLs โ€” running sequentially", targets.len()); } - match start_background(base, &s, &mut reader, history.clone(), first.as_deref()).await { + match start_background(base, &s, &mut reader, history.clone(), first.as_deref(), vec![]).await { Some(a) => { active = Some(a); println!(" \x1b[1;35mโ–ถ running in background\x1b[0m โ€” keep typing ยท \x1b[36m/status\x1b[0m ยท \x1b[36m/stop\x1b[0m"); } None => { // no external printer (piped) โ†’ blocking fallback let mut h = history.lock().unwrap(); @@ -651,20 +661,46 @@ pub async fn repl(base: &Path) -> anyhow::Result<()> { } } "/continue" | "/resume" => { - match &active { - Some(a) if a.paused.load(Ordering::Relaxed) => { - if !arg.is_empty() { - let m = ModelRef::parse(arg); - println!(" \x1b[1;35mโ–ถ resuming with fallback model\x1b[0m {}:{}", m.provider, m.model); - a.fallback.lock().unwrap().push(m); - } else { - println!(" \x1b[1;35mโ–ถ resuming\x1b[0m โ€” retrying with the current model(s)."); - } - a.paused.store(false, Ordering::Relaxed); - a.resume.notify_waiters(); + let paused = active.as_ref().map(|a| a.paused.load(Ordering::Relaxed)).unwrap_or(false); + let working = active.as_ref().map(|a| !a.done.load(Ordering::Relaxed)).unwrap_or(false); + if paused { + let a = active.as_ref().unwrap(); + if !arg.is_empty() { + let m = ModelRef::parse(arg); + println!(" \x1b[1;35mโ–ถ resuming with fallback model\x1b[0m {}:{}", m.provider, m.model); + a.fallback.lock().unwrap().push(m); + } else { + println!(" \x1b[1;35mโ–ถ resuming\x1b[0m โ€” retrying with the current model(s)."); } - Some(a) if !a.done.load(Ordering::Relaxed) => println!(" run is not paused โ€” it's still working. /status to check."), - _ => println!(" no paused run. (a run pauses automatically if your tokens/quota run out)"), + a.paused.store(false, Ordering::Relaxed); + a.resume.notify_waiters(); + } else if working { + println!(" run is not paused โ€” it's still working. /status to check."); + } else if let Some((tgt, prior)) = resumable.take() { + // Continue an interrupted run: relaunch on the same target, carry + // the prior findings forward, and steer agents to extend coverage + // rather than re-report what was already found. + if s.target.is_none() && s.repo.is_none() { s.target = Some(tgt.clone()); } + let titles: Vec = prior.iter().map(|f| format!("[{}] {}", f.severity, f.title)).collect(); + let carry = format!( + "CONTINUE a prior interrupted engagement on this same target. These {} finding(s) are \ + ALREADY confirmed โ€” do NOT re-report them; instead widen coverage: chase untested \ + endpoints/params/methods, try new agent classes, and chain from these where possible: {}", + prior.len(), titles.join("; ")); + s.instructions = Some(match &s.instructions { + Some(prev) if !prev.trim().is_empty() => format!("{prev}\n\n{carry}"), + _ => carry, + }); + println!(" \x1b[1;35mโ–ถ continuing interrupted run\x1b[0m on {tgt} โ€” {} prior finding(s) carried forward", prior.len()); + match start_background(base, &s, &mut reader, history.clone(), None, prior).await { + Some(a) => { active = Some(a); println!(" \x1b[1;35mโ–ถ running in background\x1b[0m โ€” keep typing ยท \x1b[36m/status\x1b[0m ยท \x1b[36m/stop\x1b[0m"); } + None => { + let mut h = history.lock().unwrap(); + run(base, &s, &mut h).await; save_runs(base, &h); + } + } + } else { + println!(" no paused or interrupted run. (a run pauses on token/quota exhaustion; an interrupted run is offered for /continue at launch)"); } } "/runs" | "/history" => list_runs(&history.lock().unwrap()), @@ -735,7 +771,12 @@ pub async fn repl(base: &Path) -> anyhow::Result<()> { } } runs.extend(history.lock().unwrap().iter().rev().cloned()); // newest-first + // Silence live background output while the full-screen picker is + // open (they'd corrupt each other); restore + point to /logs after. + let live_now = active.as_ref().map(|a| { a.quiet.store(true, Ordering::Relaxed); !a.done.load(Ordering::Relaxed) }).unwrap_or(false); browse_results(&runs); + if let Some(a) = &active { a.quiet.store(false, Ordering::Relaxed); } + if live_now { println!(" \x1b[2m(run still streaming in background โ€” /logs for what happened while browsing)\x1b[0m"); } } } "/finding" | "/findings" => { @@ -744,7 +785,9 @@ pub async fn repl(base: &Path) -> anyhow::Result<()> { Some(a) if arg.is_empty() && !a.done.load(Ordering::Relaxed) => a.live.lock().unwrap().full.clone(), _ => { let h = history.lock().unwrap(); pick(&h, arg).map(|r| r.findings.clone()).unwrap_or_default() } }; + if let Some(a) = &active { a.quiet.store(true, Ordering::Relaxed); } finding_detail(&pool); + if let Some(a) = &active { a.quiet.store(false, Ordering::Relaxed); } } "/expand" | "/full" => { // Show full untruncated commands from the active run. @@ -762,7 +805,11 @@ pub async fn repl(base: &Path) -> anyhow::Result<()> { None => println!(" no active run โ€” /expand shows full commands while a run streams."), } } - "/report" => open_report(&history.lock().unwrap(), arg), + "/report" => { + if let Some(a) = &active { a.quiet.store(true, Ordering::Relaxed); } + open_report(&history.lock().unwrap(), arg); + if let Some(a) = &active { a.quiet.store(false, Ordering::Relaxed); } + } "/status" => { // Live status if a run is active, else a past run's status.json. match &active { @@ -1026,7 +1073,8 @@ async fn run(base: &Path, s: &Session, history: &mut Vec) { /// external printer while the REPL keeps accepting commands (/status, /stop). /// Returns None when no external printer is available (piped) โ†’ caller blocks. async fn start_background(base: &Path, s: &Session, reader: &mut Reader, - history: Arc>>, target_override: Option<&str>) -> Option { + history: Arc>>, target_override: Option<&str>, + seed: Vec) -> Option { // `target_override` runs one specific URL (used by the multi-target queue). let ov = target_override.map(|t| t.to_string()); // The onboarding scope steers infra/cloud/ai/skills; otherwise web black/white/grey. @@ -1084,8 +1132,10 @@ async fn start_background(base: &Path, s: &Session, reader: &mut Reader, let fallback = sp.fallback.clone(); let done = Arc::new(AtomicBool::new(false)); let choice = Arc::new(Mutex::new(StopMode::Run)); + let quiet = Arc::new(AtomicBool::new(false)); let soft_task = soft.clone(); // idle guardrail triggers a soft-stop (validate) let cancel_task = cancel.clone(); + let quiet_task = quiet.clone(); let sub_mcp = s.subscription && mcp; // for the "browser/tools never engaged" diagnostic let (live2, done2, hist2, choice2) = (live.clone(), done.clone(), history, choice.clone()); @@ -1111,7 +1161,12 @@ async fn start_background(base: &Path, s: &Session, reader: &mut Reader, // Exploitation has begun once agents launch / vote โ€” only then arm the guardrail. if low.contains("launching agent") || low.starts_with("exploit ") || low.starts_with("test ") || low.starts_with("ai ") || low.starts_with("skill ") || low.starts_with("vote") { exploiting = true; } - if let Some(out) = crate::render_compact(&line) { let _ = printer.print(out); } + // Don't print into the terminal while a full-screen picker is + // open (it would corrupt the picker); the line is still in the + // feed for /logs once the picker closes. + if !quiet_task.load(Ordering::Relaxed) { + if let Some(out) = crate::render_compact(&line) { let _ = printer.print(out); } + } // Checkpoint on each new finding. let snap = { let l = live2.lock().unwrap(); @@ -1161,7 +1216,7 @@ async fn start_background(base: &Path, s: &Session, reader: &mut Reader, } // Raw โ†’ report from the unvalidated candidates we captured live. - let (findings, validated_word) = if mode_choice == StopMode::Raw { + let (mut findings, validated_word) = if mode_choice == StopMode::Raw { let raw = live2.lock().unwrap().full.clone(); crate::report_raw(&target, &raw, &workdir); (raw, "unvalidated") @@ -1169,6 +1224,13 @@ async fn start_background(base: &Path, s: &Session, reader: &mut Reader, let out = crate::finalize_run(task_out, &workdir); (out.findings, "validated") }; + // Continued run (/continue on an interrupted run): fold the carried-forward + // prior findings back in (dedup by title+endpoint) and rewrite the report + // so the merged run shows everything found across both sessions. + if !seed.is_empty() { + findings = merge_findings(seed.clone(), findings); + crate::report_raw(&target, &findings, &workdir); + } let id = { let mut h = hist2.lock().unwrap(); @@ -1184,7 +1246,19 @@ async fn start_background(base: &Path, s: &Session, reader: &mut Reader, let _ = printer.print(format!("\x1b[36m report: {}\x1b[0m", crate::report_url(&workdir))); done2.store(true, Ordering::Relaxed); }); - Some(ActiveRun { live, cancel, soft, done, choice, paused, resume, fallback }) + Some(ActiveRun { live, cancel, soft, done, choice, paused, resume, fallback, quiet }) +} + +/// Merge two finding sets, deduping by (title, endpoint) โ€” used to carry a prior +/// interrupted run's findings forward into a continued run without duplicating. +fn merge_findings(prior: Vec, mut fresh: Vec) -> Vec { + use std::collections::HashSet; + let key = |f: &Finding| format!("{}|{}", f.title.trim().to_lowercase(), f.endpoint.trim().to_lowercase()); + let seen: HashSet = fresh.iter().map(key).collect(); + for p in prior { + if !seen.contains(&key(&p)) { fresh.push(p); } + } + fresh } /// Project-local store: `/.neurosploit/` so each project keeps its own @@ -1577,7 +1651,7 @@ fn help() { h("/status [n]", "live progress + findings while running (or a past run #)"); h("/logs [n]", "recent activity feed of the running test (recon/tools/findings)"); h("/stop", "stop: [1] validate+report [2] raw report now [3] discard"); - h("/continue", "resume a run paused on token/quota (change /model first to switch)"); + h("/continue", "resume a paused (token/quota) OR a recovered interrupted run โ€” carries findings forward"); h("/results [n]", "browse findings (target โ†’ vuln โ†’ detail; Esc = back)"); h("/finding [n]", "pick a finding and see its command + PoC + evidence"); h("/report [n]", "open a run's report (menu if several)"); diff --git a/neurosploit-rs/app/src/tui.rs b/neurosploit-rs/app/src/tui.rs index 2086288..762fcea 100644 --- a/neurosploit-rs/app/src/tui.rs +++ b/neurosploit-rs/app/src/tui.rs @@ -1,4 +1,4 @@ -//! NeuroSploit v3.6.2 โ€” TUI "Mission Control" mode. +//! NeuroSploit v3.6.3 โ€” TUI "Mission Control" mode. //! //! Concurrent panels that update live while the engagement runs in the //! background, with a composer input that stays active during execution: diff --git a/neurosploit-rs/crates/harness/src/belief.rs b/neurosploit-rs/crates/harness/src/belief.rs index 38a8c06..4da71c6 100644 --- a/neurosploit-rs/crates/harness/src/belief.rs +++ b/neurosploit-rs/crates/harness/src/belief.rs @@ -1,4 +1,4 @@ -//! POMDP belief-state world model (v3.6.2). +//! POMDP belief-state world model (v3.6.3). //! //! The target is only partially observable, so we don't track booleans โ€” we //! track a **belief**: a property graph whose nodes (host / service / vuln / diff --git a/neurosploit-rs/crates/harness/src/grounding.rs b/neurosploit-rs/crates/harness/src/grounding.rs index c1febba..288e408 100644 --- a/neurosploit-rs/crates/harness/src/grounding.rs +++ b/neurosploit-rs/crates/harness/src/grounding.rs @@ -1,4 +1,4 @@ -//! Verification / grounding engine (v3.6.2). +//! Verification / grounding engine (v3.6.3). //! //! Hard rule: **no claim enters the world model without a tool receipt** โ€” raw //! tool output, not the LLM's paraphrase. This is the empirical anti-hallucination diff --git a/neurosploit-rs/crates/harness/src/lib.rs b/neurosploit-rs/crates/harness/src/lib.rs index e64fbe1..1380af7 100644 --- a/neurosploit-rs/crates/harness/src/lib.rs +++ b/neurosploit-rs/crates/harness/src/lib.rs @@ -1,4 +1,4 @@ -//! NeuroSploit v3.6.2 harness โ€” a robust multi-model runtime for the +//! NeuroSploit v3.6.3 harness โ€” a robust multi-model runtime for the //! markdown-driven autonomous pentest engine. //! //! The harness loads the `agents_md/` library, drives a *pool* of LLM models diff --git a/neurosploit-rs/crates/harness/src/pomdp.rs b/neurosploit-rs/crates/harness/src/pomdp.rs index a69f2e4..f290b1f 100644 --- a/neurosploit-rs/crates/harness/src/pomdp.rs +++ b/neurosploit-rs/crates/harness/src/pomdp.rs @@ -1,4 +1,4 @@ -//! POMDP decision layer (v3.6.2): value-of-information planning + the +//! POMDP decision layer (v3.6.3): value-of-information planning + the //! anti-hallucination gate. //! //! The choice "scan more vs exploit now" is **not** a heuristic here โ€” it falls diff --git a/neurosploit-rs/crates/harness/src/probe.rs b/neurosploit-rs/crates/harness/src/probe.rs index 2a2d119..ae3d3de 100644 --- a/neurosploit-rs/crates/harness/src/probe.rs +++ b/neurosploit-rs/crates/harness/src/probe.rs @@ -1,4 +1,4 @@ -//! Deterministic HTTP request/response analysis (v3.6.2). +//! Deterministic HTTP request/response analysis (v3.6.3). //! //! Before the LLM recon runs, the harness performs a **real** probe of the //! target and captures observed facts โ€” status, headers, security headers, diff --git a/neurosploit-rs/crates/harness/src/report.rs b/neurosploit-rs/crates/harness/src/report.rs index 6186a49..3bbce24 100644 --- a/neurosploit-rs/crates/harness/src/report.rs +++ b/neurosploit-rs/crates/harness/src/report.rs @@ -97,9 +97,9 @@ 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.6.2 Rust harness ยท multi-model validated
\ +
Target: {t} ยท v3.6.3 Rust harness ยท multi-model validated
\
{chips}
{graph_block}

Findings ({n})

{body}\ -

Authorized testing only. Findings confirmed by multi-model adversarial voting.
NeuroSploit v3.6.2 ยท by Joas A Santos & Red Team Leaders

", +

Authorized testing only. Findings confirmed by multi-model adversarial voting.
NeuroSploit v3.6.3 ยท by Joas A Santos & Red Team Leaders

", t = esc(target), chips = chips, n = sorted.len(), body = body, graph_block = graph_block, ) } @@ -135,7 +135,7 @@ pub fn typst_report(target: &str, findings: &[Finding], dir: &Path) -> std::io:: 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.6.2"), tq("multi-model") + tq(target), tq(&run_id), tq("NeuroSploit v3.6.3"), tq("multi-model") )); data.push_str("#let findings = (\n"); for f in sorted_findings(findings) {