mirror of
https://github.com/CyberSecurityUP/NeuroSploit.git
synced 2026-08-15 14:10:22 +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
|
- **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
|
vote_n is 1, the pipeline emits a warning that validation is weaker (same model
|
||||||
validates its own findings).
|
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)) => {
|
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;
|
||||||
|
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.
|
// Live findings feed: surface each candidate the moment it appears.
|
||||||
for c in &f {
|
for c in &f {
|
||||||
let _ = txc.send(format!("finding: [{}] {} @ {}", c.severity, c.title, c.endpoint)).await;
|
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],
|
(Some(a), Some(b)) if b > a => &text[a..=b],
|
||||||
_ => match (text.find('{'), text.rfind('}')) {
|
_ => match (text.find('{'), text.rfind('}')) {
|
||||||
(Some(a), Some(b)) if b > a => &text[a..=b],
|
(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) {
|
let val: serde_json::Value = match serde_json::from_str(slice) {
|
||||||
Ok(v) => v,
|
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 {
|
let items: Vec<serde_json::Value> = match val {
|
||||||
serde_json::Value::Array(a) => a,
|
serde_json::Value::Array(a) => a,
|
||||||
|
|||||||
Reference in New Issue
Block a user