mirror of
https://github.com/CyberSecurityUP/NeuroSploit.git
synced 2026-07-09 04:47:54 +02:00
v3.5.1: live findings feed + 🔔 notifications + automatic partial summary
- Live findings feed: each candidate is surfaced (✦ possible finding [sev] title @ endpoint) the moment an agent returns it, not only at the end. - 🔔 notifications in the feed: evidence saved, phase complete (with severity breakdown = automatic partial summary). Renderer styles notify/finding tags. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -437,10 +437,12 @@ fn render_line(raw: &str) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
let (tag, rest) = match line.split_once(": ") {
|
let (tag, rest) = match line.split_once(": ") {
|
||||||
Some((t, r)) if matches!(t, "exec" | "danger" | "read" | "edit" | "tool" | "net" | "ai" | "plan" | "tokens") => (t, r),
|
Some((t, r)) if matches!(t, "exec" | "danger" | "read" | "edit" | "tool" | "net" | "ai" | "plan" | "tokens" | "notify" | "finding") => (t, r),
|
||||||
_ => ("", line),
|
_ => ("", line),
|
||||||
};
|
};
|
||||||
match tag {
|
match tag {
|
||||||
|
"notify" => println!(" \x1b[1;36m🔔 {}\x1b[0m", rest.trim()),
|
||||||
|
"finding" => println!(" \x1b[1;33m✦ possible finding\x1b[0m {who}{}", rest.trim()),
|
||||||
"exec" => card(&format!("{who}⌘ command"), rest, "\x1b[33m"),
|
"exec" => card(&format!("{who}⌘ command"), rest, "\x1b[33m"),
|
||||||
"danger" => card(&format!("{who}⚠ DANGEROUS command"), rest, "\x1b[1;31m"),
|
"danger" => card(&format!("{who}⚠ DANGEROUS command"), rest, "\x1b[1;31m"),
|
||||||
"read" => state("📄", "reading", &format!("{who}{rest}"), "\x1b[34m"),
|
"read" => state("📄", "reading", &format!("{who}{rest}"), "\x1b[34m"),
|
||||||
|
|||||||
@@ -181,6 +181,10 @@ pub async fn run(cfg: RunConfig, lib: &Library, pool: &ModelPool, tx: Sender<Str
|
|||||||
Ok((m, text)) => {
|
Ok((m, text)) => {
|
||||||
let f = extract_findings(&text, &ag.name);
|
let f = extract_findings(&text, &ag.name);
|
||||||
let _ = txc.send(format!("exploit {} via {} → {} candidate(s)", ag.name, m.label(), f.len())).await;
|
let _ = txc.send(format!("exploit {} via {} → {} candidate(s)", ag.name, m.label(), f.len())).await;
|
||||||
|
// Live findings feed: surface each candidate the moment it appears.
|
||||||
|
for c in &f {
|
||||||
|
let _ = txc.send(format!("finding: [{}] {} @ {}", c.severity, c.title, c.endpoint)).await;
|
||||||
|
}
|
||||||
(ag.name.clone(), text, f)
|
(ag.name.clone(), text, f)
|
||||||
}
|
}
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
@@ -647,8 +651,17 @@ async fn finish(cfg: RunConfig, _lib: &Library, recon: String, transcript: Strin
|
|||||||
|
|
||||||
let artifacts = persist(&cfg, &recon, &transcript, &findings);
|
let artifacts = persist(&cfg, &recon, &transcript, &findings);
|
||||||
if !artifacts.is_empty() {
|
if !artifacts.is_empty() {
|
||||||
|
let _ = tx.send(format!("notify: evidence saved → {}", cfg.workdir.clone().unwrap_or_default())).await;
|
||||||
let _ = tx.send(format!("artifacts saved: {}", artifacts.join(", "))).await;
|
let _ = tx.send(format!("artifacts saved: {}", artifacts.join(", "))).await;
|
||||||
}
|
}
|
||||||
|
// Automatic partial summary (phase complete).
|
||||||
|
{
|
||||||
|
let mut by: std::collections::BTreeMap<&str, usize> = Default::default();
|
||||||
|
for f in &findings { *by.entry(f.severity.as_str()).or_insert(0) += 1; }
|
||||||
|
let sev = if by.is_empty() { "none".to_string() }
|
||||||
|
else { by.iter().map(|(k, v)| format!("{k}:{v}")).collect::<Vec<_>>().join(" ") };
|
||||||
|
let _ = tx.send(format!("notify: phase complete — {} validated finding(s) [{}]", findings.len(), sev)).await;
|
||||||
|
}
|
||||||
|
|
||||||
RunOutput {
|
RunOutput {
|
||||||
target: cfg.target.clone(),
|
target: cfg.target.clone(),
|
||||||
|
|||||||
Reference in New Issue
Block a user