Files
compliance-scanner-agent/compliance-agent/src/pipeline/semgrep.rs
Sharang Parnerkar 03ee69834d
All checks were successful
CI / Format (push) Successful in 3s
CI / Clippy (push) Successful in 2m15s
CI / Security Audit (push) Successful in 1m34s
CI / Tests (push) Successful in 3m4s
Fix formatting and clippy warnings across workspace
- Run cargo fmt on all crates
- Fix regex patterns using unsupported lookahead in patterns.rs
- Replace unwrap() calls with compile_regex() helper
- Fix never type fallback in GitHub tracker
- Fix redundant field name in findings page
- Allow enum_variant_names for Dioxus Route enum
- Fix &mut Vec -> &mut [T] clippy lint in sbom.rs
- Mark unused-but-intended APIs with #[allow(dead_code)]

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-02 17:41:03 +01:00

110 lines
3.1 KiB
Rust

use std::path::Path;
use compliance_core::models::{Finding, ScanType, Severity};
use compliance_core::traits::{ScanOutput, Scanner};
use compliance_core::CoreError;
use crate::pipeline::dedup;
pub struct SemgrepScanner;
impl Scanner for SemgrepScanner {
fn name(&self) -> &str {
"semgrep"
}
fn scan_type(&self) -> ScanType {
ScanType::Sast
}
async fn scan(&self, repo_path: &Path, repo_id: &str) -> Result<ScanOutput, CoreError> {
let output = tokio::process::Command::new("semgrep")
.args(["--config=auto", "--json", "--quiet"])
.arg(repo_path)
.output()
.await
.map_err(|e| CoreError::Scanner {
scanner: "semgrep".to_string(),
source: Box::new(e),
})?;
if !output.status.success() && output.stdout.is_empty() {
let stderr = String::from_utf8_lossy(&output.stderr);
tracing::warn!("Semgrep exited with {}: {stderr}", output.status);
return Ok(ScanOutput::default());
}
let result: SemgrepOutput = serde_json::from_slice(&output.stdout)?;
let findings = result
.results
.into_iter()
.map(|r| {
let severity = match r.extra.severity.as_str() {
"ERROR" => Severity::High,
"WARNING" => Severity::Medium,
"INFO" => Severity::Low,
_ => Severity::Info,
};
let fingerprint = dedup::compute_fingerprint(&[
repo_id,
&r.check_id,
&r.path,
&r.start.line.to_string(),
]);
let mut finding = Finding::new(
repo_id.to_string(),
fingerprint,
"semgrep".to_string(),
ScanType::Sast,
r.extra.message.clone(),
r.extra.message,
severity,
);
finding.rule_id = Some(r.check_id);
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
})
.collect();
Ok(ScanOutput {
findings,
sbom_entries: Vec::new(),
})
}
}
#[derive(serde::Deserialize)]
struct SemgrepOutput {
results: Vec<SemgrepResult>,
}
#[derive(serde::Deserialize)]
struct SemgrepResult {
check_id: String,
path: String,
start: SemgrepPosition,
extra: SemgrepExtra,
}
#[derive(serde::Deserialize)]
struct SemgrepPosition {
line: u32,
}
#[derive(serde::Deserialize)]
struct SemgrepExtra {
message: String,
severity: String,
lines: String,
#[serde(default)]
metadata: Option<serde_json::Value>,
}