use chrono::{DateTime, Utc}; use serde::{Deserialize, Serialize}; use super::finding::Severity; /// Type of DAST target application #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] #[serde(rename_all = "snake_case")] pub enum DastTargetType { WebApp, RestApi, GraphQl, } impl std::fmt::Display for DastTargetType { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { Self::WebApp => write!(f, "webapp"), Self::RestApi => write!(f, "rest_api"), Self::GraphQl => write!(f, "graphql"), } } } /// Authentication configuration for DAST target #[derive(Debug, Clone, Serialize, Deserialize)] pub struct DastAuthConfig { /// Authentication method: "none", "basic", "bearer", "cookie", "form" pub method: String, /// Login URL for form-based auth pub login_url: Option, /// Username or token pub username: Option, /// Password (stored encrypted in practice) pub password: Option, /// Bearer token pub token: Option, /// Custom headers for auth pub headers: Option>, } /// A target for DAST scanning #[derive(Debug, Clone, Serialize, Deserialize)] pub struct DastTarget { #[serde(rename = "_id", skip_serializing_if = "Option::is_none")] pub id: Option, pub name: String, pub base_url: String, pub target_type: DastTargetType, pub auth_config: Option, /// Linked repository ID (for SAST correlation) pub repo_id: Option, /// URL paths to exclude from scanning pub excluded_paths: Vec, /// Maximum crawl depth pub max_crawl_depth: u32, /// Rate limit (requests per second) pub rate_limit: u32, /// Whether destructive tests (DELETE, PUT) are allowed pub allow_destructive: bool, pub created_at: DateTime, pub updated_at: DateTime, } impl DastTarget { pub fn new(name: String, base_url: String, target_type: DastTargetType) -> Self { let now = Utc::now(); Self { id: None, name, base_url, target_type, auth_config: None, repo_id: None, excluded_paths: Vec::new(), max_crawl_depth: 5, rate_limit: 10, allow_destructive: false, created_at: now, updated_at: now, } } } /// Phase of a DAST scan #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] #[serde(rename_all = "snake_case")] pub enum DastScanPhase { Reconnaissance, Crawling, VulnerabilityAnalysis, Exploitation, Reporting, Completed, } impl std::fmt::Display for DastScanPhase { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { Self::Reconnaissance => write!(f, "reconnaissance"), Self::Crawling => write!(f, "crawling"), Self::VulnerabilityAnalysis => write!(f, "vulnerability_analysis"), Self::Exploitation => write!(f, "exploitation"), Self::Reporting => write!(f, "reporting"), Self::Completed => write!(f, "completed"), } } } /// Status of a DAST scan run #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] #[serde(rename_all = "snake_case")] pub enum DastScanStatus { Running, Completed, Failed, Cancelled, } /// A DAST scan run #[derive(Debug, Clone, Serialize, Deserialize)] pub struct DastScanRun { #[serde(rename = "_id", skip_serializing_if = "Option::is_none")] pub id: Option, pub target_id: String, pub status: DastScanStatus, pub current_phase: DastScanPhase, pub phases_completed: Vec, /// Number of endpoints discovered during crawling pub endpoints_discovered: u32, /// Number of findings pub findings_count: u32, /// Number of confirmed exploitable findings pub exploitable_count: u32, pub error_message: Option, /// Linked SAST scan run ID (if triggered as part of pipeline) pub sast_scan_run_id: Option, pub started_at: DateTime, pub completed_at: Option>, } impl DastScanRun { pub fn new(target_id: String) -> Self { Self { id: None, target_id, status: DastScanStatus::Running, current_phase: DastScanPhase::Reconnaissance, phases_completed: Vec::new(), endpoints_discovered: 0, findings_count: 0, exploitable_count: 0, error_message: None, sast_scan_run_id: None, started_at: Utc::now(), completed_at: None, } } } /// Type of DAST vulnerability #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] #[serde(rename_all = "snake_case")] pub enum DastVulnType { SqlInjection, Xss, AuthBypass, Ssrf, ApiMisconfiguration, OpenRedirect, Idor, InformationDisclosure, SecurityMisconfiguration, BrokenAuth, Other, } impl std::fmt::Display for DastVulnType { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { Self::SqlInjection => write!(f, "sql_injection"), Self::Xss => write!(f, "xss"), Self::AuthBypass => write!(f, "auth_bypass"), Self::Ssrf => write!(f, "ssrf"), Self::ApiMisconfiguration => write!(f, "api_misconfiguration"), Self::OpenRedirect => write!(f, "open_redirect"), Self::Idor => write!(f, "idor"), Self::InformationDisclosure => write!(f, "information_disclosure"), Self::SecurityMisconfiguration => write!(f, "security_misconfiguration"), Self::BrokenAuth => write!(f, "broken_auth"), Self::Other => write!(f, "other"), } } } /// Evidence collected during DAST testing #[derive(Debug, Clone, Serialize, Deserialize)] pub struct DastEvidence { /// HTTP request that triggered the finding pub request_method: String, pub request_url: String, pub request_headers: Option>, pub request_body: Option, /// HTTP response pub response_status: u16, pub response_headers: Option>, /// Relevant snippet of response body pub response_snippet: Option, /// Path to screenshot file (if captured) pub screenshot_path: Option, /// The payload that triggered the vulnerability pub payload: Option, /// Timing information (for timing-based attacks) pub response_time_ms: Option, } /// A finding from DAST scanning #[derive(Debug, Clone, Serialize, Deserialize)] pub struct DastFinding { #[serde(rename = "_id", skip_serializing_if = "Option::is_none")] pub id: Option, pub scan_run_id: String, pub target_id: String, pub vuln_type: DastVulnType, pub title: String, pub description: String, pub severity: Severity, pub cwe: Option, /// The URL endpoint where the vulnerability was found pub endpoint: String, /// HTTP method pub method: String, /// Parameter that is vulnerable pub parameter: Option, /// Whether exploitability was confirmed with a working payload pub exploitable: bool, /// Evidence chain pub evidence: Vec, /// Remediation guidance pub remediation: Option, /// Linked SAST finding ID (if correlated) pub linked_sast_finding_id: Option, pub created_at: DateTime, } impl DastFinding { #[allow(clippy::too_many_arguments)] pub fn new( scan_run_id: String, target_id: String, vuln_type: DastVulnType, title: String, description: String, severity: Severity, endpoint: String, method: String, ) -> Self { Self { id: None, scan_run_id, target_id, vuln_type, title, description, severity, cwe: None, endpoint, method, parameter: None, exploitable: false, evidence: Vec::new(), remediation: None, linked_sast_finding_id: None, created_at: Utc::now(), } } }