mirror of
https://github.com/CyberSecurityUP/NeuroSploit.git
synced 2026-08-14 13:40:23 +02:00
fix(3.6.8): JSON parse resilience + diagnostics for local model failures
- extract_findings: log when model output has no JSON (was silent drop) - extract_findings: auto-fix trailing-comma JSON (common LLM mistake) - pipeline: emit response tail when agent returns 0 parseable findings - Helps diagnose why small/local models produce 0 findings on valid targets Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.6
parent
105c62af61
commit
e956b482b9
@@ -17,6 +17,13 @@
|
||||
- **Single-model + vote_n=1 warning.** When only one model is configured and
|
||||
vote_n is 1, the pipeline emits a warning that validation is weaker (same model
|
||||
validates its own findings).
|
||||
- **JSON parse resilience for local models.** `extract_findings` now logs when a
|
||||
model returns text but no parseable JSON (previously silent drop — 0 findings
|
||||
with no diagnostic). Also auto-fixes trailing-comma JSON (`[...,]`) which small
|
||||
models commonly produce.
|
||||
- **Visible diagnostics when agents return 0 findings.** Pipeline emits the
|
||||
response tail so the operator can see what the model actually returned (helps
|
||||
debug model quality issues with local/small models).
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -598,6 +598,10 @@ pub async fn run(cfg: RunConfig, lib: &Library, pool: &ModelPool, tx: Sender<Str
|
||||
Ok((m, text)) => {
|
||||
let f = extract_findings(&text, &ag.name);
|
||||
let _ = txc.send(format!("exploit {} via {} → {} candidate(s)", ag.name, m.label(), f.len())).await;
|
||||
if f.is_empty() && !text.trim().is_empty() && text.trim() != "[]" {
|
||||
let tail: String = text.chars().rev().take(120).collect::<String>().chars().rev().collect();
|
||||
let _ = txc.send(format!("⚠ agent {} returned text but 0 parseable findings (model may have produced malformed JSON). Tail: {:?}", ag.name, tail)).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;
|
||||
@@ -1458,12 +1462,27 @@ fn extract_findings(text: &str, agent: &str) -> Vec<Finding> {
|
||||
(Some(a), Some(b)) if b > a => &text[a..=b],
|
||||
_ => match (text.find('{'), text.rfind('}')) {
|
||||
(Some(a), Some(b)) if b > a => &text[a..=b],
|
||||
_ => return vec![],
|
||||
_ => {
|
||||
if !text.trim().is_empty() && text.trim() != "[]" {
|
||||
eprintln!("[extract_findings] agent {agent}: model returned text but no JSON array/object found (len={}); raw tail: {:?}",
|
||||
text.len(), &text[text.len().saturating_sub(200)..]);
|
||||
}
|
||||
return vec![];
|
||||
}
|
||||
},
|
||||
};
|
||||
let val: serde_json::Value = match serde_json::from_str(slice) {
|
||||
Ok(v) => v,
|
||||
Err(_) => return vec![],
|
||||
Err(e) => {
|
||||
eprintln!("[extract_findings] agent {agent}: JSON parse failed: {e}; slice head: {:?}",
|
||||
&slice[..slice.len().min(300)]);
|
||||
// Attempt to salvage: strip trailing comma before ] (common LLM mistake)
|
||||
let fixed = slice.replace(",]", "]").replace(",}", "}");
|
||||
match serde_json::from_str(&fixed) {
|
||||
Ok(v) => v,
|
||||
Err(_) => return vec![],
|
||||
}
|
||||
}
|
||||
};
|
||||
let items: Vec<serde_json::Value> = match val {
|
||||
serde_json::Value::Array(a) => a,
|
||||
|
||||
Reference in New Issue
Block a user