diff --git a/.cargo/audit.toml b/.cargo/audit.toml new file mode 100644 index 0000000..e183aab --- /dev/null +++ b/.cargo/audit.toml @@ -0,0 +1,10 @@ +[advisories] +ignore = [ + # hickory-proto 0.25.x pulled in transitively via mongodb → hickory-resolver. + # MongoDB 3.x has not yet released with hickory-resolver 0.26.x, so we cannot + # upgrade past this without a mongodb release. Both are DNS-layer DoS vectors + # requiring a MITM/controlled DNS server against MongoDB's hostname resolution — + # not a realistic attack surface here. Revisit when mongodb bumps hickory. + "RUSTSEC-2026-0118", # NSEC3 loop, no fix available upstream + "RUSTSEC-2026-0119", # O(n²) name compression, fixed in hickory-proto >=0.26.1 +] diff --git a/Cargo.lock b/Cargo.lock index 378dada..0714ef6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3524,9 +3524,9 @@ checksum = "224484c5d09285a7b8cb0a0c117e847ebd14cb6e4470ecf68cdb89c503b0edb9" [[package]] name = "mongodb" -version = "3.5.1" +version = "3.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "803dd859e8afa084c255a8effd8000ff86f7c8076a50cd6d8c99e8f3496f75c2" +checksum = "1ef2c933617431ad0246fb5b43c425ebdae18c7f7259c87de0726d93b0e7e91b" dependencies = [ "base64", "bitflags", @@ -3570,9 +3570,9 @@ dependencies = [ [[package]] name = "mongodb-internal-macros" -version = "3.5.1" +version = "3.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a973ef3dd3dbc6f6e65bbdecfd9ec5e781b9e7493b0f369a7c62e35d8e5ae2c8" +checksum = "9e5758dc828eb2d02ec30563cba365609d56ddd833190b192beaee2b475a7bb3" dependencies = [ "macro_magic", "proc-macro2", @@ -4699,9 +4699,9 @@ dependencies = [ [[package]] name = "rustls-webpki" -version = "0.103.10" +version = "0.103.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df33b2b81ac578cabaf06b89b0631153a3f416b0a886e8a7a1707fb51abbd1ef" +checksum = "61c429a8649f110dddef65e2a5ad240f747e85f7758a6bccc7e5777bd33f756e" dependencies = [ "ring", "rustls-pki-types", diff --git a/compliance-agent/src/pipeline/gitleaks.rs b/compliance-agent/src/pipeline/gitleaks.rs index dd1c221..e830f22 100644 --- a/compliance-agent/src/pipeline/gitleaks.rs +++ b/compliance-agent/src/pipeline/gitleaks.rs @@ -19,26 +19,33 @@ impl Scanner for GitleaksScanner { #[tracing::instrument(skip_all)] async fn scan(&self, repo_path: &Path, repo_id: &str) -> Result { - let output = tokio::process::Command::new("gitleaks") - .args([ - "detect", - "--source", - ".", - "--report-format", - "json", - "--report-path", - "/dev/stdout", - "--no-banner", - "--exit-code", - "0", - ]) - .current_dir(repo_path) - .output() - .await - .map_err(|e| CoreError::Scanner { - scanner: "gitleaks".to_string(), - source: Box::new(e), - })?; + let output = tokio::time::timeout( + std::time::Duration::from_secs(300), + tokio::process::Command::new("gitleaks") + .args([ + "detect", + "--source", + ".", + "--report-format", + "json", + "--report-path", + "/dev/stdout", + "--no-banner", + "--exit-code", + "0", + ]) + .current_dir(repo_path) + .output(), + ) + .await + .map_err(|_| CoreError::Scanner { + scanner: "gitleaks".to_string(), + source: "timed out after 5 minutes".into(), + })? + .map_err(|e| CoreError::Scanner { + scanner: "gitleaks".to_string(), + source: Box::new(e), + })?; if output.stdout.is_empty() { return Ok(ScanOutput::default()); diff --git a/compliance-agent/src/pipeline/sbom/syft.rs b/compliance-agent/src/pipeline/sbom/syft.rs index 93b1502..8b686c8 100644 --- a/compliance-agent/src/pipeline/sbom/syft.rs +++ b/compliance-agent/src/pipeline/sbom/syft.rs @@ -5,20 +5,26 @@ use compliance_core::CoreError; #[tracing::instrument(skip_all, fields(repo_id = %repo_id))] pub(super) async fn run_syft(repo_path: &Path, repo_id: &str) -> Result, CoreError> { - let output = tokio::process::Command::new("syft") - .arg(repo_path) - .args(["-o", "cyclonedx-json"]) - // Enable remote license lookups for all ecosystems - .env("SYFT_GOLANG_SEARCH_REMOTE_LICENSES", "true") - .env("SYFT_JAVASCRIPT_SEARCH_REMOTE_LICENSES", "true") - .env("SYFT_PYTHON_SEARCH_REMOTE_LICENSES", "true") - .env("SYFT_JAVA_USE_NETWORK", "true") - .output() - .await - .map_err(|e| CoreError::Scanner { - scanner: "syft".to_string(), - source: Box::new(e), - })?; + let output = tokio::time::timeout( + std::time::Duration::from_secs(300), + tokio::process::Command::new("syft") + .arg(repo_path) + .args(["-o", "cyclonedx-json"]) + .env("SYFT_GOLANG_SEARCH_REMOTE_LICENSES", "true") + .env("SYFT_JAVASCRIPT_SEARCH_REMOTE_LICENSES", "true") + .env("SYFT_PYTHON_SEARCH_REMOTE_LICENSES", "true") + .env("SYFT_JAVA_USE_NETWORK", "true") + .output(), + ) + .await + .map_err(|_| CoreError::Scanner { + scanner: "syft".to_string(), + source: "timed out after 5 minutes".into(), + })? + .map_err(|e| CoreError::Scanner { + scanner: "syft".to_string(), + source: Box::new(e), + })?; if !output.status.success() { let stderr = String::from_utf8_lossy(&output.stderr); diff --git a/compliance-agent/src/pipeline/semgrep.rs b/compliance-agent/src/pipeline/semgrep.rs index 5151351..7fc0747 100644 --- a/compliance-agent/src/pipeline/semgrep.rs +++ b/compliance-agent/src/pipeline/semgrep.rs @@ -19,15 +19,30 @@ impl Scanner for SemgrepScanner { #[tracing::instrument(skip_all)] async fn scan(&self, repo_path: &Path, repo_id: &str) -> Result { - 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), - })?; + 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), + })?; if !output.status.success() && output.stdout.is_empty() { let stderr = String::from_utf8_lossy(&output.stderr); diff --git a/compliance-dashboard/src/components/app_shell.rs b/compliance-dashboard/src/components/app_shell.rs index 8c4ae2f..b0816cf 100644 --- a/compliance-dashboard/src/components/app_shell.rs +++ b/compliance-dashboard/src/components/app_shell.rs @@ -32,7 +32,7 @@ pub fn AppShell() -> Element { // Not authenticated — redirect to Keycloak login rsx! { document::Script { - dangerous_inner_html: "window.location.href = '/auth';" + "window.location.href = '/auth';" } } }