feat(report): SARIF 2.1.0 export + stronger cross-object reference mining (v4.2.1)

- new sarif module: projects findings to SARIF 2.1.0 (rules deduped by CWE,
  security-severity from graded CVSS, endpoint locations, OWASP/MITRE tags)
- report::write_all/rebuild now emit report.sarif alongside md/json/html/pdf
- `neurosploit sarif <run> [--out]` re-emits on demand; exposed over MCP
- assurance: report.sarif added to the known-artifacts manifest
- chaining doctrine: harvest every object identifier (ids/UUIDs/tokens/emails)
  into a reference pool and substitute across identities/endpoints — the core
  of reliable BOLA/IDOR/mass-assignment discovery
- version 4.2.1; 389 tests passing

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
CyberSecurityUPandClaude Opus 4.8 committed 2026-09-24 12:14:49 -03:00
1 parent e49595b8bf
commit 5ab6451c15
10 files changed
+304 -8

No files matched your search

+25
View File
@@ -203,6 +203,15 @@ enum Cmd {
/// Run id or path.
run: String,
},
/// Emit SARIF 2.1.0 for a finished run so CI code-scanning (GitHub, Azure
/// DevOps) can ingest the findings as annotated, severity-coloured alerts.
Sarif {
/// Run id (`ns-…`) or a path to the run directory.
run: String,
/// Write to this path instead of the run's `report.sarif`.
#[arg(long = "out")]
out: Option<String>,
},
/// Verify a finished run's audit trail — the hash chain and, with --anchor,
/// the signed anchors that catch truncation and silent rebuilds.
Audit {
@@ -808,6 +817,7 @@ async fn main() -> anyhow::Result<()> {
}
Cmd::Audit { run, anchor } => handle_audit(&base, &run, anchor)?,
Cmd::Traffic { run } => handle_traffic(&base, &run)?,
Cmd::Sarif { run, out } => handle_sarif(&base, &run, out.as_deref())?,
Cmd::Assurance { run, verify } => handle_assurance(&base, &run, verify)?,
Cmd::Compliance { run, framework, include_leads } => handle_compliance(&base, &run, &framework, include_leads)?,
Cmd::Poc { run, repeats, apply } => handle_poc(&base, &run, repeats, apply).await?,
@@ -1657,6 +1667,21 @@ fn handle_traffic(base: &std::path::Path, run: &str) -> anyhow::Result<()> {
Ok(())
}
fn handle_sarif(base: &std::path::Path, run: &str, out: Option<&str>) -> anyhow::Result<()> {
let dir = resolve_run(base, run)?;
let findings = load_findings(&dir)?;
let target = std::fs::read_to_string(dir.join("meta.json"))
.ok()
.and_then(|t| serde_json::from_str::<serde_json::Value>(&t).ok())
.and_then(|v| v.get("target").and_then(|x| x.as_str()).map(String::from))
.unwrap_or_else(|| run.to_string());
let doc = harness::sarif::to_string(&target, &findings);
let dest = out.map(std::path::PathBuf::from).unwrap_or_else(|| dir.join("report.sarif"));
std::fs::write(&dest, doc)?;
println!(" {} finding(s) -> SARIF 2.1.0 at {}", findings.len(), dest.display());
Ok(())
}
fn handle_audit(base: &std::path::Path, run: &str, anchor: bool) -> anyhow::Result<()> {
let dir = resolve_run(base, run)?;
let log = harness::audit::AuditLog::open(dir.join("audit.jsonl"));
+2
View File
@@ -99,6 +99,7 @@ fn tool_list() -> Value {
{ "name": "neurosploit_findings", "description": "Read a finished run's findings as JSON.", "inputSchema": { "type": "object", "properties": { "run": { "type": "string", "description": "Run id or path" } }, "required": ["run"] } },
{ "name": "neurosploit_report", "description": "Read a finished run's Markdown report.", "inputSchema": { "type": "object", "properties": { "run": { "type": "string" } }, "required": ["run"] } },
{ "name": "neurosploit_rebuild", "description": "Rebuild a run's report artifacts from its findings (no model calls).", "inputSchema": { "type": "object", "properties": { "run": { "type": "string" } }, "required": ["run"] } },
{ "name": "neurosploit_sarif", "description": "Emit SARIF 2.1.0 for a finished run (report.sarif) so CI code-scanning can ingest the findings.", "inputSchema": { "type": "object", "properties": { "run": { "type": "string" } }, "required": ["run"] } },
{ "name": "neurosploit_internal", "description": "Internal-network / Active Directory attack-graph analysis: paths to crown jewels and the choke point to fix first.", "inputSchema": { "type": "object", "properties": { "graph": { "type": "string", "description": "Path to a graph JSON" }, "scaffold": { "type": "string", "description": "Domain to scaffold, e.g. corp.local" }, "from": { "type": "string", "description": "Foothold node id" } } } },
{ "name": "neurosploit_compliance", "description": "Map a finished run's findings onto PCI-DSS, HIPAA or SOC 2 controls.", "inputSchema": { "type": "object", "properties": { "run": { "type": "string" }, "framework": { "type": "string", "enum": ["pci-dss","hipaa","soc2"] } }, "required": ["run"] } },
{ "name": "neurosploit_container", "description": "Scan an OCI container image (repo:tag / tar / Dockerfile) for vulnerable packages, secrets, misconfig and emit an SBOM.", "inputSchema": { "type": "object", "properties": { "image": { "type": "string" }, "model": { "type": "string" }, "subscription": { "type": "boolean" } }, "required": ["image"] } }
@@ -138,6 +139,7 @@ fn handle_call(id: Option<Value>, req: &Value, exe: &std::path::Path) -> Value {
return read_run_file(id, &run, "report.md");
}
"neurosploit_rebuild" => { let Some(run) = s("run") else { return tool_err(id, "run is required") }; argv.push("rebuild".into()); argv.push(run); }
"neurosploit_sarif" => { let Some(run) = s("run") else { return tool_err(id, "run is required") }; argv.push("sarif".into()); argv.push(run); }
"neurosploit_internal" => {
argv.push("internal".into());
if let Some(g) = s("graph") { argv.push("--graph".into()); argv.push(g); }