style: fix cargo fmt formatting
Some checks failed
CI / Format (pull_request) Successful in 4s
CI / Clippy (push) Failing after 2m48s
CI / Detect Changes (pull_request) Has been skipped
CI / Detect Changes (push) Has been skipped
CI / Deploy Agent (pull_request) Has been skipped
CI / Deploy Dashboard (pull_request) Has been skipped
CI / Deploy Agent (push) Has been skipped
CI / Deploy Dashboard (push) Has been skipped
CI / Deploy Docs (push) Has been skipped
CI / Deploy MCP (push) Has been skipped
CI / Clippy (pull_request) Failing after 2m34s
CI / Security Audit (pull_request) Has been skipped
CI / Tests (pull_request) Has been skipped
CI / Format (push) Successful in 3s
CI / Security Audit (push) Has been skipped
CI / Tests (push) Has been skipped
CI / Deploy Docs (pull_request) Has been skipped
CI / Deploy MCP (pull_request) Has been skipped

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Sharang Parnerkar
2026-03-09 12:08:55 +01:00
parent d9b21d3410
commit 3958c1a036
17 changed files with 99 additions and 85 deletions

View File

@@ -192,7 +192,8 @@ pub async fn build_embeddings(
auth_token: repo.auth_token.clone(), auth_token: repo.auth_token.clone(),
auth_username: repo.auth_username.clone(), auth_username: repo.auth_username.clone(),
}; };
let git_ops = crate::pipeline::git::GitOps::new(&agent_clone.config.git_clone_base_path, creds); let git_ops =
crate::pipeline::git::GitOps::new(&agent_clone.config.git_clone_base_path, creds);
let repo_path = match git_ops.clone_or_fetch(&repo.git_url, &repo.name) { let repo_path = match git_ops.clone_or_fetch(&repo.git_url, &repo.name) {
Ok(p) => p, Ok(p) => p,
Err(e) => { Err(e) => {

View File

@@ -296,7 +296,8 @@ pub async fn trigger_build(
auth_token: repo.auth_token.clone(), auth_token: repo.auth_token.clone(),
auth_username: repo.auth_username.clone(), auth_username: repo.auth_username.clone(),
}; };
let git_ops = crate::pipeline::git::GitOps::new(&agent_clone.config.git_clone_base_path, creds); let git_ops =
crate::pipeline::git::GitOps::new(&agent_clone.config.git_clone_base_path, creds);
let repo_path = match git_ops.clone_or_fetch(&repo.git_url, &repo.name) { let repo_path = match git_ops.clone_or_fetch(&repo.git_url, &repo.name) {
Ok(p) => p, Ok(p) => p,
Err(e) => { Err(e) => {

View File

@@ -315,7 +315,12 @@ pub async fn add_repository(
.repositories() .repositories()
.insert_one(&repo) .insert_one(&repo)
.await .await
.map_err(|_| (StatusCode::CONFLICT, "Repository already exists".to_string()))?; .map_err(|_| {
(
StatusCode::CONFLICT,
"Repository already exists".to_string(),
)
})?;
Ok(Json(ApiResponse { Ok(Json(ApiResponse {
data: repo, data: repo,

View File

@@ -7,7 +7,10 @@ pub fn build_router() -> Router {
Router::new() Router::new()
.route("/api/v1/health", get(handlers::health)) .route("/api/v1/health", get(handlers::health))
.route("/api/v1/stats/overview", get(handlers::stats_overview)) .route("/api/v1/stats/overview", get(handlers::stats_overview))
.route("/api/v1/settings/ssh-public-key", get(handlers::get_ssh_public_key)) .route(
"/api/v1/settings/ssh-public-key",
get(handlers::get_ssh_public_key),
)
.route("/api/v1/repositories", get(handlers::list_repositories)) .route("/api/v1/repositories", get(handlers::list_repositories))
.route("/api/v1/repositories", post(handlers::add_repository)) .route("/api/v1/repositories", post(handlers::add_repository))
.route( .route(

View File

@@ -47,7 +47,9 @@ pub async fn triage_findings(
// Enrich with surrounding code context if possible // Enrich with surrounding code context if possible
if let Some(context) = read_surrounding_context(finding) { if let Some(context) = read_surrounding_context(finding) {
user_prompt.push_str(&format!("\n\n--- Surrounding Code (50 lines) ---\n{context}")); user_prompt.push_str(&format!(
"\n\n--- Surrounding Code (50 lines) ---\n{context}"
));
} }
// Enrich with graph context if available // Enrich with graph context if available
@@ -98,7 +100,8 @@ pub async fn triage_findings(
}; };
if let Ok(result) = serde_json::from_str::<TriageResult>(cleaned) { if let Ok(result) = serde_json::from_str::<TriageResult>(cleaned) {
// Apply file-path confidence adjustment // Apply file-path confidence adjustment
let adjusted_confidence = adjust_confidence(result.confidence, &file_classification); let adjusted_confidence =
adjust_confidence(result.confidence, &file_classification);
finding.confidence = Some(adjusted_confidence); finding.confidence = Some(adjusted_confidence);
finding.triage_action = Some(result.action.clone()); finding.triage_action = Some(result.action.clone());
finding.triage_rationale = Some(result.rationale); finding.triage_rationale = Some(result.rationale);
@@ -235,7 +238,9 @@ fn adjust_confidence(raw_confidence: f64, classification: &str) -> f64 {
raw_confidence * multiplier raw_confidence * multiplier
} }
fn downgrade_severity(severity: &compliance_core::models::Severity) -> compliance_core::models::Severity { fn downgrade_severity(
severity: &compliance_core::models::Severity,
) -> compliance_core::models::Severity {
use compliance_core::models::Severity; use compliance_core::models::Severity;
match severity { match severity {
Severity::Critical => Severity::High, Severity::Critical => Severity::High,
@@ -246,7 +251,9 @@ fn downgrade_severity(severity: &compliance_core::models::Severity) -> complianc
} }
} }
fn upgrade_severity(severity: &compliance_core::models::Severity) -> compliance_core::models::Severity { fn upgrade_severity(
severity: &compliance_core::models::Severity,
) -> compliance_core::models::Severity {
use compliance_core::models::Severity; use compliance_core::models::Severity;
match severity { match severity {
Severity::Info => Severity::Low, Severity::Info => Severity::Low,

View File

@@ -108,22 +108,19 @@ impl CveScanner {
.await .await
.map_err(|e| CoreError::Http(format!("Failed to parse OSV.dev response: {e}")))?; .map_err(|e| CoreError::Http(format!("Failed to parse OSV.dev response: {e}")))?;
let chunk_vulns = result let chunk_vulns = result.results.into_iter().map(|r| {
.results r.vulns
.into_iter() .unwrap_or_default()
.map(|r| { .into_iter()
r.vulns .map(|v| OsvVuln {
.unwrap_or_default() id: v.id,
.into_iter() summary: v.summary,
.map(|v| OsvVuln { severity: v.database_specific.and_then(|d| {
id: v.id, d.get("severity").and_then(|s| s.as_str()).map(String::from)
summary: v.summary, }),
severity: v.database_specific.and_then(|d| { })
d.get("severity").and_then(|s| s.as_str()).map(String::from) .collect()
}), });
})
.collect()
});
all_vulns.extend(chunk_vulns); all_vulns.extend(chunk_vulns);
} }

View File

@@ -37,9 +37,7 @@ impl RepoCredentials {
// HTTPS userpass authentication // HTTPS userpass authentication
if allowed_types.contains(git2::CredentialType::USER_PASS_PLAINTEXT) { if allowed_types.contains(git2::CredentialType::USER_PASS_PLAINTEXT) {
if let Some(ref tok) = token { if let Some(ref tok) = token {
let user = username let user = username.as_deref().unwrap_or("x-access-token");
.as_deref()
.unwrap_or("x-access-token");
return Cred::userpass_plaintext(user, tok); return Cred::userpass_plaintext(user, tok);
} }
} }

View File

@@ -19,7 +19,18 @@ impl Scanner for GitleaksScanner {
async fn scan(&self, repo_path: &Path, repo_id: &str) -> Result<ScanOutput, CoreError> { async fn scan(&self, repo_path: &Path, repo_id: &str) -> Result<ScanOutput, CoreError> {
let output = tokio::process::Command::new("gitleaks") let output = tokio::process::Command::new("gitleaks")
.args(["detect", "--source", ".", "--report-format", "json", "--report-path", "/dev/stdout", "--no-banner", "--exit-code", "0"]) .args([
"detect",
"--source",
".",
"--report-format",
"json",
"--report-path",
"/dev/stdout",
"--no-banner",
"--exit-code",
"0",
])
.current_dir(repo_path) .current_dir(repo_path)
.output() .output()
.await .await
@@ -32,8 +43,8 @@ impl Scanner for GitleaksScanner {
return Ok(ScanOutput::default()); return Ok(ScanOutput::default());
} }
let results: Vec<GitleaksResult> = serde_json::from_slice(&output.stdout) let results: Vec<GitleaksResult> =
.unwrap_or_default(); serde_json::from_slice(&output.stdout).unwrap_or_default();
let findings = results let findings = results
.into_iter() .into_iter()
@@ -41,7 +52,9 @@ impl Scanner for GitleaksScanner {
.map(|r| { .map(|r| {
let severity = match r.rule_id.as_str() { let severity = match r.rule_id.as_str() {
s if s.contains("private-key") => Severity::Critical, s if s.contains("private-key") => Severity::Critical,
s if s.contains("token") || s.contains("password") || s.contains("secret") => Severity::High, s if s.contains("token") || s.contains("password") || s.contains("secret") => {
Severity::High
}
s if s.contains("api-key") => Severity::High, s if s.contains("api-key") => Severity::High,
_ => Severity::Medium, _ => Severity::Medium,
}; };

View File

@@ -60,8 +60,7 @@ fn has_rust_project(repo_path: &Path) -> bool {
fn has_js_project(repo_path: &Path) -> bool { fn has_js_project(repo_path: &Path) -> bool {
// Only run if eslint is actually installed in the project // Only run if eslint is actually installed in the project
repo_path.join("package.json").exists() repo_path.join("package.json").exists() && repo_path.join("node_modules/.bin/eslint").exists()
&& repo_path.join("node_modules/.bin/eslint").exists()
} }
fn has_python_project(repo_path: &Path) -> bool { fn has_python_project(repo_path: &Path) -> bool {
@@ -99,7 +98,14 @@ async fn run_with_timeout(
async fn run_clippy(repo_path: &Path, repo_id: &str) -> Result<Vec<Finding>, CoreError> { async fn run_clippy(repo_path: &Path, repo_id: &str) -> Result<Vec<Finding>, CoreError> {
let child = Command::new("cargo") let child = Command::new("cargo")
.args(["clippy", "--message-format=json", "--quiet", "--", "-W", "clippy::all"]) .args([
"clippy",
"--message-format=json",
"--quiet",
"--",
"-W",
"clippy::all",
])
.current_dir(repo_path) .current_dir(repo_path)
.stdout(std::process::Stdio::piped()) .stdout(std::process::Stdio::piped())
.stderr(std::process::Stdio::piped()) .stderr(std::process::Stdio::piped())
@@ -128,10 +134,7 @@ async fn run_clippy(repo_path: &Path, repo_id: &str) -> Result<Vec<Finding>, Cor
None => continue, None => continue,
}; };
let level = message let level = message.get("level").and_then(|v| v.as_str()).unwrap_or("");
.get("level")
.and_then(|v| v.as_str())
.unwrap_or("");
if level != "warning" && level != "error" { if level != "warning" && level != "error" {
continue; continue;
@@ -162,8 +165,13 @@ async fn run_clippy(repo_path: &Path, repo_id: &str) -> Result<Vec<Finding>, Cor
Severity::Low Severity::Low
}; };
let fingerprint = let fingerprint = dedup::compute_fingerprint(&[
dedup::compute_fingerprint(&[repo_id, "clippy", &code, &file_path, &line_number.to_string()]); repo_id,
"clippy",
&code,
&file_path,
&line_number.to_string(),
]);
let mut finding = Finding::new( let mut finding = Finding::new(
repo_id.to_string(), repo_id.to_string(),
@@ -200,10 +208,7 @@ fn extract_primary_span(message: &serde_json::Value) -> (String, u32) {
.and_then(|v| v.as_str()) .and_then(|v| v.as_str())
.unwrap_or("") .unwrap_or("")
.to_string(); .to_string();
let line = span let line = span.get("line_start").and_then(|v| v.as_u64()).unwrap_or(0) as u32;
.get("line_start")
.and_then(|v| v.as_u64())
.unwrap_or(0) as u32;
return (file, line); return (file, line);
} }
} }
@@ -233,8 +238,7 @@ async fn run_eslint(repo_path: &Path, repo_id: &str) -> Result<Vec<Finding>, Cor
return Ok(Vec::new()); return Ok(Vec::new());
} }
let results: Vec<EslintFileResult> = let results: Vec<EslintFileResult> = serde_json::from_slice(&output.stdout).unwrap_or_default();
serde_json::from_slice(&output.stdout).unwrap_or_default();
let mut findings = Vec::new(); let mut findings = Vec::new();
for file_result in results { for file_result in results {
@@ -308,8 +312,7 @@ async fn run_ruff(repo_path: &Path, repo_id: &str) -> Result<Vec<Finding>, CoreE
return Ok(Vec::new()); return Ok(Vec::new());
} }
let results: Vec<RuffResult> = let results: Vec<RuffResult> = serde_json::from_slice(&output.stdout).unwrap_or_default();
serde_json::from_slice(&output.stdout).unwrap_or_default();
let findings = results let findings = results
.into_iter() .into_iter()

View File

@@ -9,9 +9,8 @@ pub fn ensure_ssh_key(key_path: &str) -> Result<String, AgentError> {
let public_path = private_path.with_extension("pub"); let public_path = private_path.with_extension("pub");
if private_path.exists() && public_path.exists() { if private_path.exists() && public_path.exists() {
return std::fs::read_to_string(&public_path).map_err(|e| { return std::fs::read_to_string(&public_path)
AgentError::Config(format!("Failed to read SSH public key: {e}")) .map_err(|e| AgentError::Config(format!("Failed to read SSH public key: {e}")));
});
} }
// Create parent directory // Create parent directory
@@ -36,9 +35,7 @@ pub fn ensure_ssh_key(key_path: &str) -> Result<String, AgentError> {
if !output.status.success() { if !output.status.success() {
let stderr = String::from_utf8_lossy(&output.stderr); let stderr = String::from_utf8_lossy(&output.stderr);
return Err(AgentError::Config(format!( return Err(AgentError::Config(format!("ssh-keygen failed: {stderr}")));
"ssh-keygen failed: {stderr}"
)));
} }
// Set correct permissions // Set correct permissions
@@ -48,9 +45,8 @@ pub fn ensure_ssh_key(key_path: &str) -> Result<String, AgentError> {
std::fs::set_permissions(private_path, std::fs::Permissions::from_mode(0o600))?; std::fs::set_permissions(private_path, std::fs::Permissions::from_mode(0o600))?;
} }
let public_key = std::fs::read_to_string(&public_path).map_err(|e| { let public_key = std::fs::read_to_string(&public_path)
AgentError::Config(format!("Failed to read generated SSH public key: {e}")) .map_err(|e| AgentError::Config(format!("Failed to read generated SSH public key: {e}")))?;
})?;
tracing::info!("Generated new SSH key pair at {key_path}"); tracing::info!("Generated new SSH key pair at {key_path}");
Ok(public_key) Ok(public_key)

View File

@@ -1,5 +1,4 @@
pub mod auth; pub mod auth;
pub(crate) mod serde_helpers;
pub mod chat; pub mod chat;
pub mod cve; pub mod cve;
pub mod dast; pub mod dast;
@@ -11,6 +10,7 @@ pub mod mcp;
pub mod repository; pub mod repository;
pub mod sbom; pub mod sbom;
pub mod scan; pub mod scan;
pub(crate) mod serde_helpers;
pub use auth::AuthInfo; pub use auth::AuthInfo;
pub use chat::{ChatMessage, ChatRequest, ChatResponse, SourceReference}; pub use chat::{ChatMessage, ChatRequest, ChatResponse, SourceReference};

View File

@@ -37,9 +37,15 @@ pub struct TrackedRepository {
pub last_scanned_commit: Option<String>, pub last_scanned_commit: Option<String>,
#[serde(default, deserialize_with = "deserialize_findings_count")] #[serde(default, deserialize_with = "deserialize_findings_count")]
pub findings_count: u32, pub findings_count: u32,
#[serde(default = "chrono::Utc::now", with = "super::serde_helpers::bson_datetime")] #[serde(
default = "chrono::Utc::now",
with = "super::serde_helpers::bson_datetime"
)]
pub created_at: DateTime<Utc>, pub created_at: DateTime<Utc>,
#[serde(default = "chrono::Utc::now", with = "super::serde_helpers::bson_datetime")] #[serde(
default = "chrono::Utc::now",
with = "super::serde_helpers::bson_datetime"
)]
pub updated_at: DateTime<Utc>, pub updated_at: DateTime<Utc>,
} }

View File

@@ -22,9 +22,7 @@ pub mod bson_datetime {
let bson_val = bson::Bson::deserialize(deserializer)?; let bson_val = bson::Bson::deserialize(deserializer)?;
match bson_val { match bson_val {
bson::Bson::DateTime(dt) => Ok(dt.into()), bson::Bson::DateTime(dt) => Ok(dt.into()),
bson::Bson::String(s) => { bson::Bson::String(s) => s.parse::<DateTime<Utc>>().map_err(serde::de::Error::custom),
s.parse::<DateTime<Utc>>().map_err(serde::de::Error::custom)
}
other => Err(serde::de::Error::custom(format!( other => Err(serde::de::Error::custom(format!(
"expected DateTime or string, got: {other:?}" "expected DateTime or string, got: {other:?}"
))), ))),

View File

@@ -14,6 +14,8 @@ pub fn load_config() -> Result<DashboardConfig, DashboardError> {
.ok() .ok()
.and_then(|p| p.parse().ok()) .and_then(|p| p.parse().ok())
.unwrap_or(8080), .unwrap_or(8080),
mcp_endpoint_url: std::env::var("MCP_ENDPOINT_URL").ok().filter(|v| !v.is_empty()), mcp_endpoint_url: std::env::var("MCP_ENDPOINT_URL")
.ok()
.filter(|v| !v.is_empty()),
}) })
} }

View File

@@ -120,10 +120,7 @@ pub async fn bulk_update_finding_status(
} }
#[server] #[server]
pub async fn update_finding_feedback( pub async fn update_finding_feedback(id: String, feedback: String) -> Result<(), ServerFnError> {
id: String,
feedback: String,
) -> Result<(), ServerFnError> {
let state: super::server_state::ServerState = let state: super::server_state::ServerState =
dioxus_fullstack::FullstackContext::extract().await?; dioxus_fullstack::FullstackContext::extract().await?;
let url = format!("{}/api/v1/findings/{id}/feedback", state.agent_api_url); let url = format!("{}/api/v1/findings/{id}/feedback", state.agent_api_url);

View File

@@ -141,10 +141,7 @@ pub async fn trigger_repo_scan(repo_id: String) -> Result<(), ServerFnError> {
pub async fn check_repo_scanning(repo_id: String) -> Result<bool, ServerFnError> { pub async fn check_repo_scanning(repo_id: String) -> Result<bool, ServerFnError> {
let state: super::server_state::ServerState = let state: super::server_state::ServerState =
dioxus_fullstack::FullstackContext::extract().await?; dioxus_fullstack::FullstackContext::extract().await?;
let url = format!( let url = format!("{}/api/v1/scan-runs?page=1&limit=1", state.agent_api_url);
"{}/api/v1/scan-runs?page=1&limit=1",
state.agent_api_url
);
let resp = reqwest::get(&url) let resp = reqwest::get(&url)
.await .await

View File

@@ -85,27 +85,17 @@ async fn seed_default_mcp_servers(db: &Database, mcp_endpoint_url: Option<&str>)
( (
"Findings MCP", "Findings MCP",
"Exposes security findings, triage data, and finding summaries to LLM agents", "Exposes security findings, triage data, and finding summaries to LLM agents",
vec![ vec!["list_findings", "get_finding", "findings_summary"],
"list_findings",
"get_finding",
"findings_summary",
],
), ),
( (
"SBOM MCP", "SBOM MCP",
"Exposes software bill of materials and vulnerability reports to LLM agents", "Exposes software bill of materials and vulnerability reports to LLM agents",
vec![ vec!["list_sbom_packages", "sbom_vuln_report"],
"list_sbom_packages",
"sbom_vuln_report",
],
), ),
( (
"DAST MCP", "DAST MCP",
"Exposes DAST scan findings and scan summaries to LLM agents", "Exposes DAST scan findings and scan summaries to LLM agents",
vec![ vec!["list_dast_findings", "dast_scan_summary"],
"list_dast_findings",
"dast_scan_summary",
],
), ),
]; ];