feat: human-in-loop validator (flag not delete), MD/JSON reports, SPA methodology, robust RL

- Validator no longer silently drops uncertain findings. New Finding.review_status
  (confirmed | needs-review) + review_reason. validate() keeps partial-support as
  needs-review (drops only zero-support noise); refute_pass() demotes refuted
  High/Crit to needs-review instead of deleting; grounding::gate() flags ungrounded
  as needs-review instead of retain-dropping. Reports separate the two buckets.
- Reports: report::write_all writes report.md (human) + report.json (structured
  confirmed/needs-review/all) + report.html + Typst PDF. Wired into finalize_run
  and report_raw. HTML shows a NEEDS REVIEW badge + reason.
- SPA/REST methodology: when recon detects a JS SPA and/or REST/GraphQL API,
  inject SPA_API_DOCTRINE — directions (not an answer key) for a Juice-Shop-class
  surface: map API from JS bundle, hidden client routes, SQLi login-bypass/UNION,
  JWT none/RS→HS forge, IDOR/BOLA + mass-assignment, path-traversal + poison null
  byte, forgot-password OSINT, exposed /metrics, DOM XSS, NoSQL, SSRF, redirect
  allowlist, XXE, coupon crypto. Agents still discover and prove live.
- RL reward shaping: confirmed (severity × confidence) strong, needs-review small
  positive lead, no-find slight decay — reliable agents rise in selection.
- Tests: grounding gate flag-not-delete; report md/json bucket separation.
This commit is contained in:
CyberSecurityUP
2026-07-30 20:06:05 -03:00
parent a6643968e2
commit 76121fd739
7 changed files with 272 additions and 21 deletions
+2 -2
View File
@@ -797,7 +797,7 @@ pub(crate) fn report_raw(target: &str, findings: &[harness::types::Finding], wor
harness::pipeline::stamp_attribution(&mut fs); // provenance travels with raw reports too
harness::attack_graph::enrich(&mut fs);
std::fs::write(workdir.join("findings.json"), serde_json::to_string_pretty(&fs).unwrap_or_default()).ok();
let _ = harness::report::typst_report(target, &fs, workdir);
let _ = harness::report::write_all(target, &fs, workdir); // md + json + html + pdf
write_status(workdir, "stopped-raw", &format!("\"findings\":{}", fs.len()));
}
@@ -808,7 +808,7 @@ pub(crate) fn finalize_run(mut out: RunOutput, workdir: &Path) -> RunOutput {
if out.target.is_empty() {
out.target = workdir.file_name().and_then(|s| s.to_str()).unwrap_or("").to_string();
}
let _ = harness::report::typst_report(&out.target, &out.findings, workdir);
let _ = harness::report::write_all(&out.target, &out.findings, workdir); // md + json + html + pdf
write_status(workdir, "complete", &format!("\"findings\":{},\"agents_ran\":{}", out.findings.len(), out.agents_ran.len()));
out
}