From 753ecefaaead1b302f8f881749a4b67f922ab55f Mon Sep 17 00:00:00 2001 From: Sharang Parnerkar <30073382+mighty840@users.noreply.github.com> Date: Tue, 21 Jul 2026 10:55:00 +0200 Subject: [PATCH] fix(semgrep): extract CWE from list-form metadata (was always null) semgrep emits metadata.cwe as a LIST (e.g. "CWE-798: Use of Hard-coded Credentials"), but the extractor called v.as_str() on it -> always None, so SAST findings never carried a CWE at all (silently breaking control mapping AND the CWE-based dedup). Handle list + bare-string forms and normalise to the CWE-NNN id. Found by the live control-triage end-to-end test. Co-Authored-By: Claude Fable 5 --- compliance-agent/src/pipeline/semgrep.rs | 29 ++++++++++++++++++++---- 1 file changed, 25 insertions(+), 4 deletions(-) diff --git a/compliance-agent/src/pipeline/semgrep.rs b/compliance-agent/src/pipeline/semgrep.rs index 7fc0747..6d28205 100644 --- a/compliance-agent/src/pipeline/semgrep.rs +++ b/compliance-agent/src/pipeline/semgrep.rs @@ -82,10 +82,7 @@ impl Scanner for SemgrepScanner { finding.file_path = Some(r.path); finding.line_number = Some(r.start.line); finding.code_snippet = Some(r.extra.lines); - finding.cwe = r - .extra - .metadata - .and_then(|m| m.get("cwe").and_then(|v| v.as_str()).map(|s| s.to_string())); + finding.cwe = r.extra.metadata.as_ref().and_then(extract_cwe); finding }) .collect(); @@ -124,10 +121,34 @@ struct SemgrepExtra { metadata: Option, } +/// semgrep emits `metadata.cwe` as a list of strings like +/// `"CWE-798: Use of Hard-coded Credentials"` (occasionally a bare string). Take +/// the first entry and normalise it to just the `CWE-NNN` id. +fn extract_cwe(metadata: &serde_json::Value) -> Option { + let raw = metadata.get("cwe")?; + let text = match raw { + serde_json::Value::Array(items) => items.first()?.as_str()?, + serde_json::Value::String(s) => s.as_str(), + _ => return None, + }; + let id = text.split(':').next().unwrap_or(text).trim(); + (!id.is_empty()).then(|| id.to_string()) +} + #[cfg(test)] mod tests { use super::*; + #[test] + fn extract_cwe_handles_list_and_normalises() { + let md = serde_json::json!({"cwe": ["CWE-798: Use of Hard-coded Credentials"]}); + assert_eq!(extract_cwe(&md).as_deref(), Some("CWE-798")); + let bare = serde_json::json!({"cwe": "CWE-89"}); + assert_eq!(extract_cwe(&bare).as_deref(), Some("CWE-89")); + let none = serde_json::json!({"severity": "ERROR"}); + assert_eq!(extract_cwe(&none), None); + } + #[test] fn deserialize_semgrep_output() { let json = r#"{