mirror of
https://github.com/CyberSecurityUP/NeuroSploit.git
synced 2026-08-15 06:00:28 +02:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
98616bca0b | ||
|
|
5b9d485025 | ||
|
|
d9c191ec39 | ||
|
|
54bf424c1d |
@@ -1,4 +1,4 @@
|
||||
<h1 align="center">🧠 NeuroSploit v3.6.0</h1>
|
||||
<h1 align="center">🧠 NeuroSploit v3.6.1</h1>
|
||||
|
||||
<p align="center">
|
||||
<a href="https://trendshift.io/repositories/22624?utm_source=trendshift-badge&utm_medium=badge&utm_campaign=badge-trendshift-22624" target="_blank" rel="noopener noreferrer"><img src="https://trendshift.io/api/badge/trendshift/repositories/22624/daily?language=Python" alt="JoasASantos%2FNeuroSploit | Trendshift" width="250" height="55"/></a>
|
||||
@@ -12,7 +12,7 @@
|
||||
</p>
|
||||
|
||||
<p align="center">
|
||||
<img src="https://img.shields.io/badge/Version-3.6.0-blue?style=flat-square">
|
||||
<img src="https://img.shields.io/badge/Version-3.6.1-blue?style=flat-square">
|
||||
<img src="https://img.shields.io/badge/Harness-Rust%20%7C%20tokio-e6b673?style=flat-square">
|
||||
<img src="https://img.shields.io/badge/License-MIT-green?style=flat-square">
|
||||
<img src="https://img.shields.io/badge/MD%20Agents-417-red?style=flat-square">
|
||||
@@ -28,7 +28,7 @@
|
||||
>
|
||||
> 📖 **New here? Read the [full Tutorial & User Guide →](TUTORIAL.md)** — every mode, flag, config and example explained.
|
||||
|
||||
> 🆕 **New in v3.6.0 — Cloud testing + REPL navigation + deeper recon:**
|
||||
> 🆕 **New in v3.6.1 — Cloud testing + REPL navigation + deeper recon:**
|
||||
> **AWS/GCP/Azure** agents (+17 → **375** total) with credentials wired through
|
||||
> `creds.yaml`; a more navigable **REPL** — **`/timeout`** idle guardrail,
|
||||
> **multi-target** `/target a,b,c` (sequential), an interactive **`/results`**
|
||||
|
||||
+20
@@ -1,3 +1,23 @@
|
||||
# NeuroSploit v3.6.1 — Release Notes
|
||||
|
||||
**Release Date:** July 2026
|
||||
**Codename:** GPT-5.6 models
|
||||
**License:** MIT
|
||||
**Credits:** Joas A Santos & Red Team Leaders
|
||||
|
||||
---
|
||||
|
||||
## Highlights
|
||||
|
||||
- **Added the GPT-5.6 model line** (OpenAI / ChatGPT): `openai:gpt-5.6-sol`
|
||||
(frontier / default), `openai:gpt-5.6-terra` (balanced), and
|
||||
`openai:gpt-5.6-luna` (fast & affordable) — alongside the existing GPT-5.x,
|
||||
Claude (incl. Sonnet 5), Grok 4.5 and the rest of the provider pool.
|
||||
- Everything from v3.6.0 (AI/LLM/MCP/Skills testing, n8n audit, onboarding
|
||||
wizard, intense multi-round recon) carries forward unchanged.
|
||||
|
||||
---
|
||||
|
||||
# NeuroSploit v3.6.0 — Release Notes
|
||||
|
||||
**Release Date:** July 2026
|
||||
|
||||
+2
-2
@@ -1,4 +1,4 @@
|
||||
# NeuroSploit — Tutorial & User Guide (v3.6.0)
|
||||
# NeuroSploit — Tutorial & User Guide (v3.6.1)
|
||||
|
||||
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.0
|
||||
neurosploit --version # neurosploit 3.6.1
|
||||
neurosploit agents # {"vulns":196,...,"chains":12,"total":417}
|
||||
neurosploit models # all providers & models
|
||||
```
|
||||
|
||||
+2
-2
@@ -14,7 +14,7 @@ function Ok ($m) { Write-Host " + $m" -ForegroundColor Green }
|
||||
function Warn($m){ Write-Host " ! $m" -ForegroundColor Yellow }
|
||||
|
||||
Write-Host ""
|
||||
Write-Host " NeuroSploit installer (Windows) — v3.6.0" -ForegroundColor Cyan
|
||||
Write-Host " NeuroSploit installer (Windows) — v3.6.1" -ForegroundColor Cyan
|
||||
|
||||
# arch → asset arch (only x64 prebuilt today; arm64 falls back to source)
|
||||
$rawArch = $env:PROCESSOR_ARCHITECTURE
|
||||
@@ -29,7 +29,7 @@ $ref = $env:NEUROSPLOIT_REF
|
||||
if (-not $ref) {
|
||||
try { $ref = (Invoke-RestMethod "https://api.github.com/repos/$slug/releases/latest").tag_name } catch { }
|
||||
}
|
||||
if (-not $ref) { $ref = "v3.6.0" }
|
||||
if (-not $ref) { $ref = "v3.6.1" }
|
||||
Say "Release: $ref"
|
||||
|
||||
New-Item -ItemType Directory -Force -Path $dir | Out-Null
|
||||
|
||||
Generated
+2
-2
@@ -871,7 +871,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "neurosploit"
|
||||
version = "3.6.0"
|
||||
version = "3.6.1"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"clap",
|
||||
@@ -888,7 +888,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "neurosploit-harness"
|
||||
version = "3.6.0"
|
||||
version = "3.6.1"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"futures",
|
||||
|
||||
@@ -3,7 +3,7 @@ members = ["crates/harness", "app"]
|
||||
resolver = "2"
|
||||
|
||||
[workspace.package]
|
||||
version = "3.6.0"
|
||||
version = "3.6.1"
|
||||
edition = "2021"
|
||||
license = "MIT"
|
||||
repository = "https://github.com/JoasASantos/NeuroSploit"
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
//! NeuroSploit v3.6.0 — interactive harness + CLI (`run` / `whitebox` / `agents` / `models`).
|
||||
//! NeuroSploit v3.6.1 — 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.0 — multi-model autonomous pentest harness",
|
||||
long_about = "NeuroSploit v3.6.0 — a Rust multi-model harness that drives a pool of LLMs \
|
||||
about = "NeuroSploit v3.6.1 — multi-model autonomous pentest harness",
|
||||
long_about = "NeuroSploit v3.6.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\
|
||||
@@ -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.0 · by Joas A Santos & Red Team Leaders");
|
||||
println!(" ┌─ NeuroSploit v3.6.1 · by Joas A Santos & Red Team Leaders");
|
||||
println!(" │ run id : {run_id}");
|
||||
println!(" │ target : {}", cfg.target);
|
||||
println!(" │ models : {}", cfg.models.join(", "));
|
||||
@@ -1076,10 +1076,16 @@ pub(crate) fn render_compact(raw: &str) -> Option<String> {
|
||||
"ai" => return None, // skip verbose model chatter in background feed
|
||||
_ => {
|
||||
let low = line.to_lowercase();
|
||||
if low.contains("recon complete") { "\x1b[36m 🔍 recon complete\x1b[0m".into() }
|
||||
// Recon / probe activity — SHOW it so a long recon (esp. via a
|
||||
// non-streaming CLI like codex) doesn't look frozen.
|
||||
if low.starts_with("probe:") { format!("\x1b[36m 🔎 {}\x1b[0m", trunc1(line, 130)) }
|
||||
else if low.contains("recon complete") { "\x1b[36m 🔍 recon complete\x1b[0m".into() }
|
||||
else if low.starts_with("recon") || low.starts_with("ai-recon") || low.contains("recon round") || low.contains("intensity") { format!("\x1b[36m 🔍 {}\x1b[0m", trunc1(line, 130)) }
|
||||
else if low.starts_with("skills audit") || low.starts_with("ai engagement") { format!("\x1b[36m 🤖 {}\x1b[0m", trunc1(line, 130)) }
|
||||
else if low.starts_with("loaded ") || low.starts_with("running ") { format!("\x1b[36m 🧭 {}\x1b[0m", trunc1(line, 130)) }
|
||||
else if low.contains("selected") && low.contains("agent") { format!("\x1b[36m 🧭 {}\x1b[0m", trunc1(line, 110)) }
|
||||
else if low.starts_with("vote") && low.contains("confirmed") { format!("\x1b[1;32m ✓ {}\x1b[0m", trunc1(line, 110)) }
|
||||
else if low.starts_with("exploit") || low.starts_with("test ") || low.contains("launching agent") { format!("\x1b[35m 🧪 {}\x1b[0m", trunc1(line, 110)) }
|
||||
else if low.starts_with("exploit") || low.starts_with("test ") || low.starts_with("ai ") || low.starts_with("skill ") || low.contains("launching agent") { format!("\x1b[35m 🧪 {}\x1b[0m", trunc1(line, 110)) }
|
||||
else if low.starts_with("vote") { format!("\x1b[2m · {}\x1b[0m", trunc1(line, 110)) }
|
||||
else if low.contains("fail") || low.contains("error") { format!("\x1b[31m ✗ {}\x1b[0m", trunc1(line, 110)) }
|
||||
else { return None; }
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
//! NeuroSploit v3.6.0 — interactive session (Claude-Code / Codex / Cursor-CLI style).
|
||||
//! NeuroSploit v3.6.1 — 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
|
||||
@@ -33,6 +33,9 @@ struct RunLive {
|
||||
commands: Vec<String>, // full untruncated commands for /expand & Ctrl+O
|
||||
agents: usize,
|
||||
agents_done: usize,
|
||||
last: String, // last meaningful activity line (sign of life)
|
||||
lines: usize, // total streamed lines (activity counter)
|
||||
feed: Vec<String>, // recent raw activity lines for /logs (capped)
|
||||
}
|
||||
impl RunLive {
|
||||
/// progress fraction in [0,1] (agents completed / total selected).
|
||||
@@ -48,9 +51,18 @@ impl RunLive {
|
||||
}
|
||||
fn ingest(&mut self, line: &str) {
|
||||
let low = line.to_lowercase();
|
||||
self.lines += 1;
|
||||
// Keep a compact activity trail for /logs and the /status sign-of-life
|
||||
// (skip the machine-only finding_json/ai/tokens noise).
|
||||
if !low.starts_with("finding_json:") && !low.starts_with("@") && !low.contains("ai:") && !low.starts_with("tokens:") {
|
||||
let clean: String = line.chars().take(160).collect();
|
||||
self.last = clean.clone();
|
||||
self.feed.push(clean);
|
||||
if self.feed.len() > 200 { self.feed.remove(0); }
|
||||
}
|
||||
if low.contains("token/quota exhausted") || low.contains("run is paused") { self.phase = "paused (quota)".into(); }
|
||||
else if low.contains("resumed — retrying") { self.phase = "exploiting".into(); }
|
||||
else if low.contains("recon complete") { self.phase = "recon".into(); }
|
||||
else if low.starts_with("recon") || low.starts_with("ai-recon") || low.contains("recon round") || low.contains("intensity") || low.starts_with("probe:") { self.phase = "recon".into(); }
|
||||
else if low.contains("selected") && low.contains("agent") {
|
||||
self.phase = "planning".into();
|
||||
if let Some(n) = line.split_whitespace().find_map(|t| t.parse::<usize>().ok()) { self.agents = n; }
|
||||
@@ -120,7 +132,7 @@ const COMMANDS: &[&str] = &[
|
||||
"/help", "/onboard", "/show", "/config", "/providers", "/model", "/key", "/sub", "/target",
|
||||
"/repo", "/auth", "/creds", "/focus", "/attach", "/context", "/mcp", "/offline",
|
||||
"/votes", "/chain", "/recon", "/timeout", "/proxy", "/burp", "/ua", "/agents", "/theme", "/clear", "/run", "/stop", "/continue", "/runs", "/results", "/report",
|
||||
"/status", "/diff", "/retest", "/validate", "/finding", "/expand", "/integrations", "/quit",
|
||||
"/status", "/logs", "/diff", "/retest", "/validate", "/finding", "/expand", "/integrations", "/quit",
|
||||
];
|
||||
|
||||
/// rustyline helper: Tab-completes `/commands` and `@filesystem-paths`,
|
||||
@@ -334,7 +346,7 @@ pub async fn repl(base: &Path) -> anyhow::Result<()> {
|
||||
let backends = harness::installed_cli_backends();
|
||||
println!("\x1b[1m");
|
||||
println!(" ███╗ ██╗███████╗██╗ ██╗██████╗ ██████╗");
|
||||
println!(" ████╗ ██║██╔════╝██║ ██║██╔══██╗██╔═══██╗ NeuroSploit v3.6.0");
|
||||
println!(" ████╗ ██║██╔════╝██║ ██║██╔══██╗██╔═══██╗ NeuroSploit v3.6.1");
|
||||
println!(" ██╔██╗ ██║█████╗ ██║ ██║██████╔╝██║ ██║ interactive harness");
|
||||
println!(" ██║╚██╗██║██╔══╝ ██║ ██║██╔══██╗██║ ██║ by Joas A Santos");
|
||||
println!(" ██║ ╚████║███████╗╚██████╔╝██║ ██║╚██████╔╝ & Red Team Leaders");
|
||||
@@ -753,17 +765,39 @@ pub async fn repl(base: &Path) -> anyhow::Result<()> {
|
||||
let mut by: std::collections::BTreeMap<&str, usize> = Default::default();
|
||||
for (sv, _) in &l.findings { *by.entry(sv.as_str()).or_insert(0) += 1; }
|
||||
let sev = if by.is_empty() { "0".into() } else { by.iter().map(|(k, v)| format!("{k}:{v}")).collect::<Vec<_>>().join(" ") };
|
||||
println!(" \x1b[1m▶ live\x1b[0m {} ({}) · phase {} · {:02}:{:02} · {} possible finding(s) [{}]",
|
||||
l.target, l.mode, l.phase, el / 60, el % 60, l.findings.len(), sev);
|
||||
println!(" \x1b[1m▶ live\x1b[0m {} ({}) · phase \x1b[36m{}\x1b[0m · {:02}:{:02} · {} finding(s) [{}]",
|
||||
l.target, l.mode, l.phase, el / 60, el % 60, l.full.len(), sev);
|
||||
if a.paused.load(Ordering::Relaxed) {
|
||||
println!(" \x1b[1;33m⏸ PAUSED — token/quota exhausted. /continue to resume, or /model <provider:model> then /continue to switch.\x1b[0m");
|
||||
}
|
||||
if l.agents > 0 { println!(" progress \x1b[36m{}\x1b[0m", l.bar(24)); }
|
||||
for (sv, t) in l.findings.iter().rev().take(5) { println!(" ✦ [{sv}] {t}"); }
|
||||
// Progress: a real bar once agents are selected; otherwise show the pre-exploit phase.
|
||||
if l.agents > 0 { println!(" progress \x1b[36m{}\x1b[0m · {} cmd(s) · {} activity line(s)", l.bar(24), l.commands.len(), l.lines); }
|
||||
else { println!(" \x1b[2m{} — no agents selected yet · {} cmd(s) · {} activity line(s)\x1b[0m", l.phase, l.commands.len(), l.lines); }
|
||||
// Sign of life: the latest activity line (so a long recon isn't a black box).
|
||||
if !l.last.is_empty() { println!(" \x1b[2mlast:\x1b[0m {}", trunc(&l.last, 116)); }
|
||||
for x in l.full.iter().rev().take(5) { println!(" ✦ [{}] {} \x1b[2m({})\x1b[0m", x.severity, x.title, x.endpoint); }
|
||||
println!(" \x1b[2m/logs — recent activity · /results — browse findings\x1b[0m");
|
||||
}
|
||||
_ => run_status(&history.lock().unwrap(), arg),
|
||||
}
|
||||
}
|
||||
"/logs" | "/log" | "/feed" => {
|
||||
match &active {
|
||||
Some(a) if !a.done.load(Ordering::Relaxed) => {
|
||||
let n: usize = arg.trim().parse().unwrap_or(25);
|
||||
let l = a.live.lock().unwrap();
|
||||
if l.feed.is_empty() { println!(" (no activity yet — the run is starting/reconning)"); }
|
||||
else {
|
||||
println!(" ── recent activity (last {} of {} lines) ──", n.min(l.feed.len()), l.lines);
|
||||
for line in l.feed.iter().rev().take(n).rev() {
|
||||
if let Some(out) = crate::render_compact(line) { println!("{out}"); }
|
||||
else { println!(" \x1b[2m{}\x1b[0m", trunc(line, 116)); }
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => println!(" no active run — /logs shows the live activity feed while a run streams."),
|
||||
}
|
||||
}
|
||||
"/quit" | "/exit" | "/q" => {
|
||||
if active.as_ref().map(|a| !a.done.load(Ordering::Relaxed)).unwrap_or(false) {
|
||||
if let Some(a) = &active { a.cancel.store(true, Ordering::Relaxed); }
|
||||
@@ -1034,7 +1068,7 @@ async fn start_background(base: &Path, s: &Session, reader: &mut Reader,
|
||||
let live = Arc::new(Mutex::new(RunLive {
|
||||
target: target.clone(), mode: mode_s, phase: "starting".into(),
|
||||
started: Instant::now(), findings: vec![], full: vec![], commands: vec![],
|
||||
agents: 0, agents_done: 0,
|
||||
agents: 0, agents_done: 0, last: String::new(), lines: 0, feed: vec![],
|
||||
}));
|
||||
let cancel = sp.cancel.clone();
|
||||
let soft = sp.soft.clone();
|
||||
@@ -1051,9 +1085,10 @@ async fn start_background(base: &Path, s: &Session, reader: &mut Reader,
|
||||
tokio::spawn(async move {
|
||||
let crate::Spawned { task, mut rx, workdir, .. } = sp;
|
||||
let mut last_saved = 0usize;
|
||||
let mut last_find = Instant::now(); // time of the last NEW finding
|
||||
let mut last_activity = Instant::now(); // last sign of PROGRESS (any activity)
|
||||
let mut idle_fired = false;
|
||||
let mut tool_events = 0usize; // exec/net/read/browser activity seen
|
||||
let mut exploiting = false; // guardrail only arms once exploitation starts
|
||||
let mut ticker = tokio::time::interval(std::time::Duration::from_secs(15));
|
||||
ticker.set_missed_tick_behavior(tokio::time::MissedTickBehavior::Skip);
|
||||
loop {
|
||||
@@ -1061,14 +1096,20 @@ async fn start_background(base: &Path, s: &Session, reader: &mut Reader,
|
||||
maybe = rx.recv() => {
|
||||
let Some(line) = maybe else { break };
|
||||
live2.lock().unwrap().ingest(&line);
|
||||
let low = line.to_lowercase();
|
||||
if line.contains("exec:") || line.contains("net:") || line.contains("read:") || line.contains("browser") { tool_events += 1; }
|
||||
// ANY streamed line is progress → reset the idle clock (a long
|
||||
// recon or active tool use must NOT count as idle).
|
||||
last_activity = Instant::now();
|
||||
// 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); }
|
||||
// Checkpoint on each new finding; also resets the idle clock.
|
||||
// Checkpoint on each new finding.
|
||||
let snap = {
|
||||
let l = live2.lock().unwrap();
|
||||
if l.full.len() != last_saved {
|
||||
last_saved = l.full.len();
|
||||
last_find = Instant::now();
|
||||
Some(LiveCheckpoint {
|
||||
target: l.target.clone(), mode: l.mode.into(), phase: l.phase.clone(),
|
||||
workdir: workdir.display().to_string(),
|
||||
@@ -1079,15 +1120,16 @@ async fn start_background(base: &Path, s: &Session, reader: &mut Reader,
|
||||
if let Some(c) = snap { save_checkpoint(&c); }
|
||||
}
|
||||
_ = ticker.tick() => {
|
||||
// Idle guardrail: no NEW finding within the window → soft-stop
|
||||
// (stop launching exploit agents, validate what was found).
|
||||
if idle_secs > 0 && !idle_fired && last_find.elapsed().as_secs() >= idle_secs
|
||||
// Idle guardrail: only after exploitation started AND no activity
|
||||
// (not just no finding) within the window → soft-stop & validate.
|
||||
// Recon never trips it — it streams progress lines that reset the clock.
|
||||
if idle_secs > 0 && !idle_fired && exploiting && last_activity.elapsed().as_secs() >= idle_secs
|
||||
&& !soft_task.load(Ordering::Relaxed) && !cancel_task.load(Ordering::Relaxed) {
|
||||
idle_fired = true;
|
||||
*choice2.lock().unwrap() = StopMode::Validate;
|
||||
soft_task.store(true, Ordering::Relaxed);
|
||||
let _ = printer.print(format!(
|
||||
"\x1b[33m⏹ idle guardrail: no new finding in {} min — stopping & validating what was found\x1b[0m",
|
||||
"\x1b[33m⏹ idle guardrail: no activity in {} min — stopping & validating what was found\x1b[0m",
|
||||
idle_secs / 60));
|
||||
}
|
||||
}
|
||||
@@ -1526,6 +1568,7 @@ fn help() {
|
||||
println!("\n \x1b[2mRUN & MONITOR\x1b[0m");
|
||||
h("/run", "launch (runs in the BACKGROUND — keep typing)");
|
||||
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("/results [n]", "browse findings (target → vuln → detail; Esc = back)");
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
//! NeuroSploit v3.6.0 — TUI "Mission Control" mode.
|
||||
//! NeuroSploit v3.6.1 — 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:
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
//! POMDP belief-state world model (v3.6.0).
|
||||
//! POMDP belief-state world model (v3.6.1).
|
||||
//!
|
||||
//! The target is only partially observable, so we don't track booleans — we
|
||||
//! track a **belief**: a property graph whose nodes (host / service / vuln /
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
//! Verification / grounding engine (v3.6.0).
|
||||
//! Verification / grounding engine (v3.6.1).
|
||||
//!
|
||||
//! 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
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
//! NeuroSploit v3.6.0 harness — a robust multi-model runtime for the
|
||||
//! NeuroSploit v3.6.1 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
|
||||
|
||||
@@ -25,7 +25,7 @@ pub fn providers() -> Vec<Provider> {
|
||||
Provider { key: "anthropic", label: "Anthropic Claude", base_url: "https://api.anthropic.com/v1", env_key: "ANTHROPIC_API_KEY", kind: "cli",
|
||||
models: vec!["claude-opus-4-8", "claude-sonnet-5", "claude-sonnet-4-6", "claude-haiku-4-5"] },
|
||||
Provider { key: "openai", label: "OpenAI (ChatGPT)", base_url: "https://api.openai.com/v1", env_key: "OPENAI_API_KEY", kind: "cli",
|
||||
models: vec!["gpt-5.5", "gpt-5.4", "gpt-5.4-mini", "gpt-5.3-codex", "gpt-5.2", "gpt-5.1", "gpt-5.1-codex", "o4"] },
|
||||
models: vec!["gpt-5.6-sol", "gpt-5.6-terra", "gpt-5.6-luna", "gpt-5.5", "gpt-5.4", "gpt-5.4-mini", "gpt-5.3-codex", "gpt-5.2", "gpt-5.1", "gpt-5.1-codex", "o4"] },
|
||||
Provider { key: "xai", label: "xAI Grok", base_url: "https://api.x.ai/v1", env_key: "XAI_API_KEY", kind: "cli",
|
||||
models: vec!["grok-4.5", "grok-4", "grok-4-fast"] },
|
||||
Provider { key: "gemini", label: "Google Gemini", base_url: "https://generativelanguage.googleapis.com/v1beta/openai", env_key: "GEMINI_API_KEY", kind: "cli",
|
||||
@@ -245,6 +245,19 @@ impl ChatClient {
|
||||
} else {
|
||||
"no output".to_string()
|
||||
};
|
||||
// Agentic CLIs (esp. `codex exec` in bypass-sandbox mode) exit
|
||||
// non-zero when a tool/command they ran internally returned non-zero
|
||||
// (e.g. a curl/nmap that failed) — even though they produced a valid
|
||||
// final answer. Treat that as success and use the output; only fail
|
||||
// hard on a genuine auth/rate/quota error or when there's no output.
|
||||
let low = format!("{stdout}\n{stderr}").to_lowercase();
|
||||
let hard = ["not logged in", "please log in", "please login", "run /login",
|
||||
"unauthorized", "not authenticated", "invalid api key", "no api key",
|
||||
"rate limit", "429", "quota", "credit balance", "usage limit"]
|
||||
.iter().any(|k| low.contains(k));
|
||||
if !stdout.is_empty() && !hard {
|
||||
return Ok(stdout);
|
||||
}
|
||||
return Err(anyhow!(
|
||||
"{} subscription CLI exit {}: {}",
|
||||
bin,
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
//! POMDP decision layer (v3.6.0): value-of-information planning + the
|
||||
//! POMDP decision layer (v3.6.1): value-of-information planning + the
|
||||
//! anti-hallucination gate.
|
||||
//!
|
||||
//! The choice "scan more vs exploit now" is **not** a heuristic here — it falls
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
//! Deterministic HTTP request/response analysis (v3.6.0).
|
||||
//! Deterministic HTTP request/response analysis (v3.6.1).
|
||||
//!
|
||||
//! Before the LLM recon runs, the harness performs a **real** probe of the
|
||||
//! target and captures observed facts — status, headers, security headers,
|
||||
|
||||
@@ -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}}</style></head><body>\
|
||||
<h1><span class=b>NeuroSploit</span> Penetration Test Report</h1>\
|
||||
<div class=meta>Target: <b>{t}</b> · v3.6.0 Rust harness · multi-model validated</div>\
|
||||
<div class=meta>Target: <b>{t}</b> · v3.6.1 Rust harness · multi-model validated</div>\
|
||||
<div>{chips}</div>{graph_block}<h2>Findings ({n})</h2>{body}\
|
||||
<p class=meta>Authorized testing only. Findings confirmed by multi-model adversarial voting.<br>NeuroSploit v3.6.0 · by <b>Joas A Santos</b> & <b>Red Team Leaders</b></p></body></html>",
|
||||
<p class=meta>Authorized testing only. Findings confirmed by multi-model adversarial voting.<br>NeuroSploit v3.6.1 · by <b>Joas A Santos</b> & <b>Red Team Leaders</b></p></body></html>",
|
||||
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.0"), tq("multi-model")
|
||||
tq(target), tq(&run_id), tq("NeuroSploit v3.6.1"), tq("multi-model")
|
||||
));
|
||||
data.push_str("#let findings = (\n");
|
||||
for f in sorted_findings(findings) {
|
||||
|
||||
@@ -28,7 +28,7 @@ cat <<'BANNER'
|
||||
|
||||
███╗ ██╗███████╗██╗ ██╗██████╗ ██████╗
|
||||
████╗ ██║██╔════╝██║ ██║██╔══██╗██╔═══██╗ NeuroSploit installer
|
||||
██╔██╗ ██║█████╗ ██║ ██║██████╔╝██║ ██║ v3.6.0 — Rust harness
|
||||
██╔██╗ ██║█████╗ ██║ ██║██████╔╝██║ ██║ v3.6.1 — Rust harness
|
||||
██║╚██╗██║██╔══╝ ██║ ██║██╔══██╗██║ ██║ by Joas A Santos
|
||||
██║ ╚████║███████╗╚██████╔╝██║ ██║╚██████╔╝ & Red Team Leaders
|
||||
╚═╝ ╚═══╝╚══════╝ ╚═════╝ ╚═╝ ╚═╝ ╚═════╝
|
||||
@@ -63,7 +63,7 @@ if [ -z "$REF" ]; then
|
||||
REF="$(dl "https://api.github.com/repos/${REPO_SLUG}/releases/latest" /dev/stdout 2>/dev/null \
|
||||
| grep -m1 '"tag_name"' | sed -E 's/.*"tag_name" *: *"([^"]+)".*/\1/' || true)"
|
||||
fi
|
||||
[ -z "$REF" ] && REF="v3.6.0"
|
||||
[ -z "$REF" ] && REF="v3.6.1"
|
||||
say "Release: $REF"
|
||||
|
||||
installed=0
|
||||
|
||||
Reference in New Issue
Block a user