Initial commit: Compliance Scanner Agent

Autonomous security and compliance scanning agent for git repositories.
Features: SAST (Semgrep), SBOM (Syft), CVE monitoring (OSV.dev/NVD),
GDPR/OAuth pattern detection, LLM triage, issue creation (GitHub/GitLab/Jira),
PR reviews, and Dioxus fullstack dashboard.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Sharang Parnerkar
2026-03-02 13:30:17 +01:00
commit 0867e401bc
97 changed files with 11750 additions and 0 deletions

View File

@@ -0,0 +1,46 @@
use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "lowercase")]
pub enum CveSource {
Osv,
Nvd,
SearXNG,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CveAlert {
#[serde(rename = "_id", skip_serializing_if = "Option::is_none")]
pub id: Option<mongodb::bson::oid::ObjectId>,
pub cve_id: String,
pub repo_id: String,
pub affected_package: String,
pub affected_version: String,
pub source: CveSource,
pub severity: Option<String>,
pub cvss_score: Option<f64>,
pub summary: Option<String>,
pub llm_impact_summary: Option<String>,
pub references: Vec<String>,
pub created_at: DateTime<Utc>,
}
impl CveAlert {
pub fn new(cve_id: String, repo_id: String, affected_package: String, affected_version: String, source: CveSource) -> Self {
Self {
id: None,
cve_id,
repo_id,
affected_package,
affected_version,
source,
severity: None,
cvss_score: None,
summary: None,
llm_impact_summary: None,
references: Vec::new(),
created_at: Utc::now(),
}
}
}

View File

@@ -0,0 +1,115 @@
use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
use super::scan::ScanType;
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord)]
#[serde(rename_all = "lowercase")]
pub enum Severity {
Info,
Low,
Medium,
High,
Critical,
}
impl std::fmt::Display for Severity {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Info => write!(f, "info"),
Self::Low => write!(f, "low"),
Self::Medium => write!(f, "medium"),
Self::High => write!(f, "high"),
Self::Critical => write!(f, "critical"),
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "snake_case")]
pub enum FindingStatus {
Open,
Triaged,
FalsePositive,
Resolved,
Ignored,
}
impl std::fmt::Display for FindingStatus {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Open => write!(f, "open"),
Self::Triaged => write!(f, "triaged"),
Self::FalsePositive => write!(f, "false_positive"),
Self::Resolved => write!(f, "resolved"),
Self::Ignored => write!(f, "ignored"),
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Finding {
#[serde(rename = "_id", skip_serializing_if = "Option::is_none")]
pub id: Option<mongodb::bson::oid::ObjectId>,
pub repo_id: String,
pub fingerprint: String,
pub scanner: String,
pub scan_type: ScanType,
pub rule_id: Option<String>,
pub title: String,
pub description: String,
pub severity: Severity,
pub confidence: Option<f64>,
pub cwe: Option<String>,
pub cve: Option<String>,
pub cvss_score: Option<f64>,
pub file_path: Option<String>,
pub line_number: Option<u32>,
pub code_snippet: Option<String>,
pub remediation: Option<String>,
pub suggested_fix: Option<String>,
pub status: FindingStatus,
pub tracker_issue_url: Option<String>,
pub scan_run_id: Option<String>,
pub created_at: DateTime<Utc>,
pub updated_at: DateTime<Utc>,
}
impl Finding {
pub fn new(
repo_id: String,
fingerprint: String,
scanner: String,
scan_type: ScanType,
title: String,
description: String,
severity: Severity,
) -> Self {
let now = Utc::now();
Self {
id: None,
repo_id,
fingerprint,
scanner,
scan_type,
rule_id: None,
title,
description,
severity,
confidence: None,
cwe: None,
cve: None,
cvss_score: None,
file_path: None,
line_number: None,
code_snippet: None,
remediation: None,
suggested_fix: None,
status: FindingStatus::Open,
tracker_issue_url: None,
scan_run_id: None,
created_at: now,
updated_at: now,
}
}
}

View File

@@ -0,0 +1,77 @@
use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "lowercase")]
pub enum TrackerType {
GitHub,
GitLab,
Jira,
}
impl std::fmt::Display for TrackerType {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::GitHub => write!(f, "github"),
Self::GitLab => write!(f, "gitlab"),
Self::Jira => write!(f, "jira"),
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "snake_case")]
pub enum IssueStatus {
Open,
InProgress,
Closed,
Resolved,
}
impl std::fmt::Display for IssueStatus {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Open => write!(f, "open"),
Self::InProgress => write!(f, "in_progress"),
Self::Closed => write!(f, "closed"),
Self::Resolved => write!(f, "resolved"),
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TrackerIssue {
#[serde(rename = "_id", skip_serializing_if = "Option::is_none")]
pub id: Option<mongodb::bson::oid::ObjectId>,
pub finding_id: String,
pub tracker_type: TrackerType,
pub external_id: String,
pub external_url: String,
pub title: String,
pub status: IssueStatus,
pub created_at: DateTime<Utc>,
pub updated_at: DateTime<Utc>,
}
impl TrackerIssue {
pub fn new(
finding_id: String,
tracker_type: TrackerType,
external_id: String,
external_url: String,
title: String,
) -> Self {
let now = Utc::now();
Self {
id: None,
finding_id,
tracker_type,
external_id,
external_url,
title,
status: IssueStatus::Open,
created_at: now,
updated_at: now,
}
}
}

View File

@@ -0,0 +1,13 @@
pub mod cve;
pub mod finding;
pub mod issue;
pub mod repository;
pub mod sbom;
pub mod scan;
pub use cve::{CveAlert, CveSource};
pub use finding::{Finding, FindingStatus, Severity};
pub use issue::{IssueStatus, TrackerIssue, TrackerType};
pub use repository::{ScanTrigger, TrackedRepository};
pub use sbom::{SbomEntry, VulnRef};
pub use scan::{ScanPhase, ScanRun, ScanRunStatus, ScanType};

View File

@@ -0,0 +1,53 @@
use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
use super::issue::TrackerType;
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "snake_case")]
pub enum ScanTrigger {
Scheduled,
Webhook,
Manual,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TrackedRepository {
#[serde(rename = "_id", skip_serializing_if = "Option::is_none")]
pub id: Option<mongodb::bson::oid::ObjectId>,
pub name: String,
pub git_url: String,
pub default_branch: String,
pub local_path: Option<String>,
pub scan_schedule: Option<String>,
pub webhook_enabled: bool,
pub tracker_type: Option<TrackerType>,
pub tracker_owner: Option<String>,
pub tracker_repo: Option<String>,
pub last_scanned_commit: Option<String>,
pub findings_count: u32,
pub created_at: DateTime<Utc>,
pub updated_at: DateTime<Utc>,
}
impl TrackedRepository {
pub fn new(name: String, git_url: String) -> Self {
let now = Utc::now();
Self {
id: None,
name,
git_url,
default_branch: "main".to_string(),
local_path: None,
scan_schedule: None,
webhook_enabled: false,
tracker_type: None,
tracker_owner: None,
tracker_repo: None,
last_scanned_commit: None,
findings_count: 0,
created_at: now,
updated_at: now,
}
}
}

View File

@@ -0,0 +1,43 @@
use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct VulnRef {
pub id: String,
pub source: String,
pub severity: Option<String>,
pub url: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SbomEntry {
#[serde(rename = "_id", skip_serializing_if = "Option::is_none")]
pub id: Option<mongodb::bson::oid::ObjectId>,
pub repo_id: String,
pub name: String,
pub version: String,
pub package_manager: String,
pub license: Option<String>,
pub purl: Option<String>,
pub known_vulnerabilities: Vec<VulnRef>,
pub created_at: DateTime<Utc>,
pub updated_at: DateTime<Utc>,
}
impl SbomEntry {
pub fn new(repo_id: String, name: String, version: String, package_manager: String) -> Self {
let now = Utc::now();
Self {
id: None,
repo_id,
name,
version,
package_manager,
license: None,
purl: None,
known_vulnerabilities: Vec::new(),
created_at: now,
updated_at: now,
}
}
}

View File

@@ -0,0 +1,81 @@
use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
use super::repository::ScanTrigger;
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "lowercase")]
pub enum ScanType {
Sast,
Sbom,
Cve,
Gdpr,
OAuth,
}
impl std::fmt::Display for ScanType {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Sast => write!(f, "sast"),
Self::Sbom => write!(f, "sbom"),
Self::Cve => write!(f, "cve"),
Self::Gdpr => write!(f, "gdpr"),
Self::OAuth => write!(f, "oauth"),
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "snake_case")]
pub enum ScanRunStatus {
Running,
Completed,
Failed,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "snake_case")]
pub enum ScanPhase {
ChangeDetection,
Sast,
SbomGeneration,
CveScanning,
PatternScanning,
LlmTriage,
IssueCreation,
Completed,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ScanRun {
#[serde(rename = "_id", skip_serializing_if = "Option::is_none")]
pub id: Option<mongodb::bson::oid::ObjectId>,
pub repo_id: String,
pub trigger: ScanTrigger,
pub commit_sha: Option<String>,
pub status: ScanRunStatus,
pub current_phase: ScanPhase,
pub phases_completed: Vec<ScanPhase>,
pub new_findings_count: u32,
pub error_message: Option<String>,
pub started_at: DateTime<Utc>,
pub completed_at: Option<DateTime<Utc>>,
}
impl ScanRun {
pub fn new(repo_id: String, trigger: ScanTrigger) -> Self {
Self {
id: None,
repo_id,
trigger,
commit_sha: None,
status: ScanRunStatus::Running,
current_phase: ScanPhase::ChangeDetection,
phases_completed: Vec::new(),
new_findings_count: 0,
error_message: None,
started_at: Utc::now(),
completed_at: None,
}
}
}