use futures_util::StreamExt; use mongodb::bson::doc; use compliance_core::models::dast::DastTarget; use compliance_core::models::finding::Finding; use compliance_core::models::pentest::CodeContextHint; use compliance_core::models::sbom::SbomEntry; use super::orchestrator::PentestOrchestrator; impl PentestOrchestrator { /// Fetch SAST findings, SBOM entries (with CVEs), and code graph entry points /// for the repo linked to this DAST target. pub(crate) async fn gather_repo_context( &self, target: &DastTarget, ) -> (Vec, Vec, Vec) { let Some(repo_id) = &target.repo_id else { return (Vec::new(), Vec::new(), Vec::new()); }; let sast_findings = self.fetch_sast_findings(repo_id).await; let sbom_entries = self.fetch_vulnerable_sbom(repo_id).await; let code_context = self.fetch_code_context(repo_id, &sast_findings).await; tracing::info!( repo_id, sast_findings = sast_findings.len(), vulnerable_deps = sbom_entries.len(), code_hints = code_context.len(), "Gathered code-awareness context for pentest" ); (sast_findings, sbom_entries, code_context) } /// Fetch open/triaged SAST findings for the repo (not false positives or resolved) async fn fetch_sast_findings(&self, repo_id: &str) -> Vec { let cursor = self .db .findings() .find(doc! { "repo_id": repo_id, "status": { "$in": ["open", "triaged"] }, }) .sort(doc! { "severity": -1 }) .limit(100) .await; match cursor { Ok(mut c) => { let mut results = Vec::new(); while let Some(Ok(f)) = c.next().await { results.push(f); } results } Err(e) => { tracing::warn!("Failed to fetch SAST findings for pentest: {e}"); Vec::new() } } } /// Fetch SBOM entries that have known vulnerabilities async fn fetch_vulnerable_sbom(&self, repo_id: &str) -> Vec { let cursor = self .db .sbom_entries() .find(doc! { "repo_id": repo_id, "known_vulnerabilities": { "$exists": true, "$ne": [] }, }) .limit(50) .await; match cursor { Ok(mut c) => { let mut results = Vec::new(); while let Some(Ok(e)) = c.next().await { results.push(e); } results } Err(e) => { tracing::warn!("Failed to fetch vulnerable SBOM entries: {e}"); Vec::new() } } } /// Build CodeContextHint objects from the code knowledge graph. /// Maps entry points to their source files and links SAST findings. async fn fetch_code_context( &self, repo_id: &str, sast_findings: &[Finding], ) -> Vec { // Get entry point nodes from the code graph let cursor = self .db .graph_nodes() .find(doc! { "repo_id": repo_id, "is_entry_point": true, }) .limit(50) .await; let nodes = match cursor { Ok(mut c) => { let mut results = Vec::new(); while let Some(Ok(n)) = c.next().await { results.push(n); } results } Err(_) => return Vec::new(), }; // Build hints by matching graph nodes to SAST findings by file path nodes .into_iter() .map(|node| { // Find SAST findings in the same file let linked_vulns: Vec = sast_findings .iter() .filter(|f| f.file_path.as_deref() == Some(&node.file_path)) .map(|f| { format!( "[{}] {}: {} (line {})", f.severity, f.scanner, f.title, f.line_number.unwrap_or(0) ) }) .collect(); CodeContextHint { endpoint_pattern: node.qualified_name.clone(), handler_function: node.name.clone(), file_path: node.file_path.clone(), code_snippet: String::new(), // Could fetch from embeddings known_vulnerabilities: linked_vulns, } }) .collect() } }