feat: add new scanners, enhanced triage, findings refinement, and deployment tooling
Some checks failed
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 / Deploy Agent (pull_request) Has been skipped
CI / Deploy Dashboard (pull_request) Has been skipped
CI / Deploy Docs (pull_request) Has been skipped
CI / Format (push) Failing after 3s
CI / Clippy (push) Failing after 2m44s
CI / Security Audit (push) Has been skipped
CI / Tests (push) Has been skipped
CI / Format (pull_request) Failing after 3s
CI / Clippy (pull_request) Failing after 2m51s
CI / Security Audit (pull_request) Has been skipped
CI / Tests (pull_request) Has been skipped
CI / Detect Changes (push) Has been skipped
CI / Detect Changes (pull_request) Has been skipped
CI / Deploy MCP (pull_request) Has been skipped

- Add gitleaks secret detection, lint scanning (clippy/eslint/ruff), and LLM code review scanners
- Enhance LLM triage with multi-action support (confirm/downgrade/upgrade/dismiss),
  surrounding code context, and file-path classification confidence adjustment
- Add text search, column sorting, and bulk status update to findings dashboard
- Fix finding detail page status refresh and add developer feedback field
- Fix BSON DateTime deserialization across all models with shared serde helpers
- Add scan progress spinner with polling to repositories page
- Batch OSV.dev queries to avoid "Too many queries" errors
- Add gitleaks, semgrep, and ruff to Dockerfile.agent for deployment

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Sharang Parnerkar
2026-03-09 11:05:31 +01:00
parent 32e5fc21e7
commit 23ba52276b
31 changed files with 1602 additions and 95 deletions

View File

@@ -23,6 +23,7 @@ pub struct CveAlert {
pub summary: Option<String>,
pub llm_impact_summary: Option<String>,
pub references: Vec<String>,
#[serde(with = "super::serde_helpers::bson_datetime")]
pub created_at: DateTime<Utc>,
}

View File

@@ -58,7 +58,9 @@ pub struct DastTarget {
pub rate_limit: u32,
/// Whether destructive tests (DELETE, PUT) are allowed
pub allow_destructive: bool,
#[serde(with = "super::serde_helpers::bson_datetime")]
pub created_at: DateTime<Utc>,
#[serde(with = "super::serde_helpers::bson_datetime")]
pub updated_at: DateTime<Utc>,
}
@@ -135,7 +137,9 @@ pub struct DastScanRun {
pub error_message: Option<String>,
/// Linked SAST scan run ID (if triggered as part of pipeline)
pub sast_scan_run_id: Option<String>,
#[serde(with = "super::serde_helpers::bson_datetime")]
pub started_at: DateTime<Utc>,
#[serde(default, with = "super::serde_helpers::opt_bson_datetime")]
pub completed_at: Option<DateTime<Utc>>,
}
@@ -240,6 +244,7 @@ pub struct DastFinding {
pub remediation: Option<String>,
/// Linked SAST finding ID (if correlated)
pub linked_sast_finding_id: Option<String>,
#[serde(with = "super::serde_helpers::bson_datetime")]
pub created_at: DateTime<Utc>,
}

View File

@@ -71,7 +71,14 @@ pub struct Finding {
pub status: FindingStatus,
pub tracker_issue_url: Option<String>,
pub scan_run_id: Option<String>,
/// LLM triage action and reasoning
pub triage_action: Option<String>,
pub triage_rationale: Option<String>,
/// Developer feedback on finding quality
pub developer_feedback: Option<String>,
#[serde(with = "super::serde_helpers::bson_datetime")]
pub created_at: DateTime<Utc>,
#[serde(with = "super::serde_helpers::bson_datetime")]
pub updated_at: DateTime<Utc>,
}
@@ -108,6 +115,9 @@ impl Finding {
status: FindingStatus::Open,
tracker_issue_url: None,
scan_run_id: None,
triage_action: None,
triage_rationale: None,
developer_feedback: None,
created_at: now,
updated_at: now,
}

View File

@@ -122,7 +122,9 @@ pub struct GraphBuildRun {
pub community_count: u32,
pub languages_parsed: Vec<String>,
pub error_message: Option<String>,
#[serde(with = "super::serde_helpers::bson_datetime")]
pub started_at: DateTime<Utc>,
#[serde(default, with = "super::serde_helpers::opt_bson_datetime")]
pub completed_at: Option<DateTime<Utc>>,
}
@@ -164,6 +166,7 @@ pub struct ImpactAnalysis {
pub direct_callers: Vec<String>,
/// Direct callees of the affected function
pub direct_callees: Vec<String>,
#[serde(with = "super::serde_helpers::bson_datetime")]
pub created_at: DateTime<Utc>,
}

View File

@@ -49,7 +49,9 @@ pub struct TrackerIssue {
pub external_url: String,
pub title: String,
pub status: IssueStatus,
#[serde(with = "super::serde_helpers::bson_datetime")]
pub created_at: DateTime<Utc>,
#[serde(with = "super::serde_helpers::bson_datetime")]
pub updated_at: DateTime<Utc>,
}

View File

@@ -62,6 +62,8 @@ pub struct McpServerConfig {
pub mongodb_uri: Option<String>,
/// Database name
pub mongodb_database: Option<String>,
#[serde(with = "super::serde_helpers::bson_datetime")]
pub created_at: DateTime<Utc>,
#[serde(with = "super::serde_helpers::bson_datetime")]
pub updated_at: DateTime<Utc>,
}

View File

@@ -1,4 +1,5 @@
pub mod auth;
pub(crate) mod serde_helpers;
pub mod chat;
pub mod cve;
pub mod dast;

View File

@@ -31,15 +31,9 @@ pub struct TrackedRepository {
pub last_scanned_commit: Option<String>,
#[serde(default, deserialize_with = "deserialize_findings_count")]
pub findings_count: u32,
#[serde(
default = "chrono::Utc::now",
deserialize_with = "deserialize_datetime"
)]
#[serde(default = "chrono::Utc::now", with = "super::serde_helpers::bson_datetime")]
pub created_at: DateTime<Utc>,
#[serde(
default = "chrono::Utc::now",
deserialize_with = "deserialize_datetime"
)]
#[serde(default = "chrono::Utc::now", with = "super::serde_helpers::bson_datetime")]
pub updated_at: DateTime<Utc>,
}
@@ -47,23 +41,6 @@ fn default_branch() -> String {
"main".to_string()
}
/// Handles findings_count stored as either a plain integer or a BSON Int64
/// which the driver may present as a map `{"low": N, "high": N, "unsigned": bool}`.
/// Handles datetime stored as either a BSON DateTime or an RFC 3339 string.
fn deserialize_datetime<'de, D>(deserializer: D) -> Result<DateTime<Utc>, D::Error>
where
D: Deserializer<'de>,
{
let bson = bson::Bson::deserialize(deserializer)?;
match bson {
bson::Bson::DateTime(dt) => Ok(dt.into()),
bson::Bson::String(s) => s.parse::<DateTime<Utc>>().map_err(serde::de::Error::custom),
other => Err(serde::de::Error::custom(format!(
"expected DateTime or string, got: {other:?}"
))),
}
}
fn deserialize_findings_count<'de, D>(deserializer: D) -> Result<u32, D::Error>
where
D: Deserializer<'de>,

View File

@@ -20,7 +20,9 @@ pub struct SbomEntry {
pub license: Option<String>,
pub purl: Option<String>,
pub known_vulnerabilities: Vec<VulnRef>,
#[serde(with = "super::serde_helpers::bson_datetime")]
pub created_at: DateTime<Utc>,
#[serde(with = "super::serde_helpers::bson_datetime")]
pub updated_at: DateTime<Utc>,
}

View File

@@ -13,6 +13,9 @@ pub enum ScanType {
OAuth,
Graph,
Dast,
SecretDetection,
Lint,
CodeReview,
}
impl std::fmt::Display for ScanType {
@@ -25,6 +28,9 @@ impl std::fmt::Display for ScanType {
Self::OAuth => write!(f, "oauth"),
Self::Graph => write!(f, "graph"),
Self::Dast => write!(f, "dast"),
Self::SecretDetection => write!(f, "secret_detection"),
Self::Lint => write!(f, "lint"),
Self::CodeReview => write!(f, "code_review"),
}
}
}
@@ -45,6 +51,9 @@ pub enum ScanPhase {
SbomGeneration,
CveScanning,
PatternScanning,
SecretDetection,
LintScanning,
CodeReview,
GraphBuilding,
LlmTriage,
IssueCreation,
@@ -64,7 +73,9 @@ pub struct ScanRun {
pub phases_completed: Vec<ScanPhase>,
pub new_findings_count: u32,
pub error_message: Option<String>,
#[serde(with = "super::serde_helpers::bson_datetime")]
pub started_at: DateTime<Utc>,
#[serde(default, with = "super::serde_helpers::opt_bson_datetime")]
pub completed_at: Option<DateTime<Utc>>,
}

View File

@@ -0,0 +1,70 @@
use chrono::{DateTime, Utc};
use serde::{Deserialize, Deserializer, Serializer};
/// Serialize/deserialize `DateTime<Utc>` as BSON DateTime.
/// Handles both BSON DateTime objects and RFC 3339 strings on deserialization.
pub mod bson_datetime {
use super::*;
use serde::Serialize as _;
pub fn serialize<S>(dt: &DateTime<Utc>, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
let bson_dt: bson::DateTime = (*dt).into();
bson_dt.serialize(serializer)
}
pub fn deserialize<'de, D>(deserializer: D) -> Result<DateTime<Utc>, D::Error>
where
D: Deserializer<'de>,
{
let bson_val = bson::Bson::deserialize(deserializer)?;
match bson_val {
bson::Bson::DateTime(dt) => Ok(dt.into()),
bson::Bson::String(s) => {
s.parse::<DateTime<Utc>>().map_err(serde::de::Error::custom)
}
other => Err(serde::de::Error::custom(format!(
"expected DateTime or string, got: {other:?}"
))),
}
}
}
/// Serialize/deserialize `Option<DateTime<Utc>>` as BSON DateTime.
pub mod opt_bson_datetime {
use super::*;
use serde::Serialize as _;
pub fn serialize<S>(dt: &Option<DateTime<Utc>>, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
match dt {
Some(dt) => {
let bson_dt: bson::DateTime = (*dt).into();
bson_dt.serialize(serializer)
}
None => serializer.serialize_none(),
}
}
pub fn deserialize<'de, D>(deserializer: D) -> Result<Option<DateTime<Utc>>, D::Error>
where
D: Deserializer<'de>,
{
let bson_val = Option::<bson::Bson>::deserialize(deserializer)?;
match bson_val {
Some(bson::Bson::DateTime(dt)) => Ok(Some(dt.into())),
Some(bson::Bson::String(s)) => s
.parse::<DateTime<Utc>>()
.map(Some)
.map_err(serde::de::Error::custom),
Some(bson::Bson::Null) | None => Ok(None),
Some(other) => Err(serde::de::Error::custom(format!(
"expected DateTime, string, or null, got: {other:?}"
))),
}
}
}