From e02266511aff9c9e55b8c7485bc0616afb07c4ad Mon Sep 17 00:00:00 2001 From: Sharang Parnerkar <30073382+mighty840@users.noreply.github.com> Date: Tue, 12 May 2026 11:49:46 +0200 Subject: [PATCH] fix: add timeouts to scanners, cap semgrep memory, remove syft remote lookups, fix Script error MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Semgrep was running unbounded with --config=auto (downloads all rules) and no memory cap, making it likely to get OOM-killed in resource-constrained Orca containers. Syft had remote license lookups enabled which adds network calls and memory overhead. Neither had timeouts, so a hung process would stall the entire scan indefinitely and silently produce 0 results. - semgrep: add --max-memory 500 --jobs 1 and a 10-minute timeout - syft: remove remote license lookup env vars, add 5-minute timeout - gitleaks: add 5-minute timeout - dashboard: fix Script dangerous_inner_html -> text child (Dioxus 0.7 Script element requires a single text node child, not dangerous_inner_html — was spamming error logs) Co-Authored-By: Claude Sonnet 4.6 --- compliance-agent/src/pipeline/gitleaks.rs | 47 +++++++++++-------- compliance-agent/src/pipeline/sbom/syft.rs | 30 ++++++------ compliance-agent/src/pipeline/semgrep.rs | 33 +++++++++---- .../src/components/app_shell.rs | 2 +- 4 files changed, 68 insertions(+), 44 deletions(-) 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..815f397 100644 --- a/compliance-agent/src/pipeline/sbom/syft.rs +++ b/compliance-agent/src/pipeline/sbom/syft.rs @@ -5,20 +5,22 @@ 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"]) + .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';" } } }