fix(semgrep): extract CWE from list-form metadata (was always null)
CI / Check (push) Skipped
CI / Check (pull_request) Successful in 5m54s
CI / Detect Changes (pull_request) Skipped
CI / Deploy Agent (pull_request) Skipped
CI / Deploy Dashboard (pull_request) Skipped
CI / Deploy Docs (pull_request) Skipped
CI / Deploy MCP (pull_request) Skipped
CI / Check (push) Skipped
CI / Check (pull_request) Successful in 5m54s
CI / Detect Changes (pull_request) Skipped
CI / Deploy Agent (pull_request) Skipped
CI / Deploy Dashboard (pull_request) Skipped
CI / Deploy Docs (pull_request) Skipped
CI / Deploy MCP (pull_request) Skipped
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 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Fable 5
parent
075a4cb81b
commit
753ecefaae
@@ -82,10 +82,7 @@ impl Scanner for SemgrepScanner {
|
|||||||
finding.file_path = Some(r.path);
|
finding.file_path = Some(r.path);
|
||||||
finding.line_number = Some(r.start.line);
|
finding.line_number = Some(r.start.line);
|
||||||
finding.code_snippet = Some(r.extra.lines);
|
finding.code_snippet = Some(r.extra.lines);
|
||||||
finding.cwe = r
|
finding.cwe = r.extra.metadata.as_ref().and_then(extract_cwe);
|
||||||
.extra
|
|
||||||
.metadata
|
|
||||||
.and_then(|m| m.get("cwe").and_then(|v| v.as_str()).map(|s| s.to_string()));
|
|
||||||
finding
|
finding
|
||||||
})
|
})
|
||||||
.collect();
|
.collect();
|
||||||
@@ -124,10 +121,34 @@ struct SemgrepExtra {
|
|||||||
metadata: Option<serde_json::Value>,
|
metadata: Option<serde_json::Value>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// 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<String> {
|
||||||
|
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)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
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]
|
#[test]
|
||||||
fn deserialize_semgrep_output() {
|
fn deserialize_semgrep_output() {
|
||||||
let json = r#"{
|
let json = r#"{
|
||||||
|
|||||||
Reference in New Issue
Block a user