feat(controls): B1 — custom semgrep detectors for 4 CRA controls (#218)
This commit was merged in pull request #218.
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
use std::path::Path;
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
use compliance_core::models::{Finding, ScanType, Severity};
|
||||
use compliance_core::traits::{ScanOutput, Scanner};
|
||||
@@ -6,6 +6,30 @@ use compliance_core::CoreError;
|
||||
|
||||
use crate::pipeline::dedup;
|
||||
|
||||
/// Custom CRA-control detectors bundled into the binary and staged to a temp file
|
||||
/// at scan time so semgrep can `--config` them alongside the auto ruleset. These
|
||||
/// cover controls no off-the-shelf rule digs out (secure defaults, weak password
|
||||
/// hashing, insecure session cookies, weak data-at-rest ciphers); each rule id is
|
||||
/// keyed back to its control by the `control-map` LUT.
|
||||
const CRA_RULES: &str = include_str!("../../rules/cra_semgrep.yaml");
|
||||
|
||||
/// Write the bundled CRA rules to a stable temp path (atomic: unique tmp +
|
||||
/// rename). Returns `None` on failure — the scan then runs with auto rules only.
|
||||
async fn stage_cra_rules() -> Option<PathBuf> {
|
||||
let dir = std::env::temp_dir();
|
||||
let path = dir.join("compliance-cra-semgrep.yaml");
|
||||
let tmp = dir.join(format!("compliance-cra-semgrep.{}.tmp", std::process::id()));
|
||||
if let Err(e) = tokio::fs::write(&tmp, CRA_RULES).await {
|
||||
tracing::warn!(error = %e, "failed to stage custom CRA semgrep rules; using auto rules only");
|
||||
return None;
|
||||
}
|
||||
if let Err(e) = tokio::fs::rename(&tmp, &path).await {
|
||||
tracing::warn!(error = %e, "failed to stage custom CRA semgrep rules; using auto rules only");
|
||||
return None;
|
||||
}
|
||||
Some(path)
|
||||
}
|
||||
|
||||
pub struct SemgrepScanner;
|
||||
|
||||
impl Scanner for SemgrepScanner {
|
||||
@@ -19,30 +43,26 @@ impl Scanner for SemgrepScanner {
|
||||
|
||||
#[tracing::instrument(skip_all)]
|
||||
async fn scan(&self, repo_path: &Path, repo_id: &str) -> Result<ScanOutput, CoreError> {
|
||||
let output = tokio::time::timeout(
|
||||
std::time::Duration::from_secs(600),
|
||||
tokio::process::Command::new("semgrep")
|
||||
.args([
|
||||
"--config=auto",
|
||||
"--json",
|
||||
"--quiet",
|
||||
"--max-memory",
|
||||
"500",
|
||||
"--jobs",
|
||||
"1",
|
||||
])
|
||||
.arg(repo_path)
|
||||
.output(),
|
||||
)
|
||||
.await
|
||||
.map_err(|_| CoreError::Scanner {
|
||||
scanner: "semgrep".to_string(),
|
||||
source: "timed out after 10 minutes".into(),
|
||||
})?
|
||||
.map_err(|e| CoreError::Scanner {
|
||||
scanner: "semgrep".to_string(),
|
||||
source: Box::new(e),
|
||||
})?;
|
||||
let cra_rules = stage_cra_rules().await;
|
||||
let mut command = tokio::process::Command::new("semgrep");
|
||||
command.arg("--config=auto");
|
||||
if let Some(path) = &cra_rules {
|
||||
command.arg(format!("--config={}", path.display()));
|
||||
}
|
||||
command
|
||||
.args(["--json", "--quiet", "--max-memory", "500", "--jobs", "1"])
|
||||
.arg(repo_path);
|
||||
|
||||
let output = tokio::time::timeout(std::time::Duration::from_secs(600), command.output())
|
||||
.await
|
||||
.map_err(|_| CoreError::Scanner {
|
||||
scanner: "semgrep".to_string(),
|
||||
source: "timed out after 10 minutes".into(),
|
||||
})?
|
||||
.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);
|
||||
|
||||
Reference in New Issue
Block a user