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#"{