1use crate::error::Result;
8use serde::{Deserialize, Serialize};
9use std::collections::{HashMap, HashSet};
10use std::path::{Path, PathBuf};
11use std::time::{Duration, SystemTime};
12
13#[derive(Debug)]
15pub struct ComprehensiveSecurityAuditor {
16 config: SecurityAuditConfig,
18 dependency_scanner: DependencyScanner,
20 vulnerability_db: VulnerabilityDatabase,
22 policy_enforcer: SecurityPolicyEnforcer,
24 report_generator: SecurityReportGenerator,
26 audit_history: Vec<SecurityAuditResult>,
28}
29
30#[derive(Debug, Clone, Serialize, Deserialize)]
32pub struct SecurityAuditConfig {
33 pub enable_dependency_scanning: bool,
35 pub enable_static_analysis: bool,
37 pub enable_license_compliance: bool,
39 pub enable_supply_chain_analysis: bool,
41 pub enable_secret_detection: bool,
43 pub enable_config_security: bool,
45 pub db_update_frequency: Duration,
47 pub max_audit_time: Duration,
49 pub alert_threshold: SecuritySeverity,
51 pub report_format: ReportFormat,
53 pub enable_auto_remediation: bool,
55 pub trusted_sources: Vec<String>,
57 pub excluded_paths: Vec<PathBuf>,
59 pub custom_rules: Vec<CustomSecurityRule>,
61}
62
63#[derive(Debug, Clone, Serialize, Deserialize)]
65pub struct CustomSecurityRule {
66 pub id: String,
68 pub name: String,
70 pub description: String,
72 pub pattern: String,
74 pub severity: SecuritySeverity,
76 pub file_types: Vec<String>,
78 pub remediation: Option<String>,
80}
81
82#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
84pub enum SecuritySeverity {
85 Info,
86 Low,
87 Medium,
88 High,
89 Critical,
90}
91
92#[derive(Debug, Clone, Serialize, Deserialize)]
94pub enum ReportFormat {
95 Json,
96 Yaml,
97 Html,
98 Pdf,
99 Markdown,
100 Sarif,
101}
102
103#[derive(Debug, Clone, Serialize, Deserialize)]
105pub struct SecurityAuditResult {
106 pub timestamp: SystemTime,
108 pub duration: Duration,
110 pub security_score: f64,
112 pub dependency_results: DependencyScanResult,
114 pub static_analysis_results: StaticAnalysisResult,
116 pub license_compliance_results: LicenseComplianceResult,
118 pub supply_chain_results: SupplyChainAnalysisResult,
120 pub secret_detection_results: SecretDetectionResult,
122 pub config_security_results: ConfigSecurityResult,
124 pub policy_compliance_results: PolicyComplianceResult,
126 pub remediation_suggestions: Vec<RemediationSuggestion>,
128 pub risk_assessment: RiskAssessment,
130}
131
132#[derive(Debug)]
134#[allow(dead_code)]
135pub struct DependencyScanner {
136 config: DependencyScanConfig,
138 vuln_db_client: VulnerabilityDatabaseClient,
140 license_db: LicenseDatabase,
142 package_cache: HashMap<String, PackageMetadata>,
144}
145
146#[derive(Debug, Clone)]
148pub struct DependencyScanConfig {
149 pub scan_direct_deps: bool,
151 pub scan_transitive_deps: bool,
153 pub max_depth: usize,
155 pub check_outdated: bool,
157 pub min_versions: HashMap<String, String>,
159 pub blocked_dependencies: HashSet<String>,
161 pub allowed_licenses: HashSet<String>,
163 pub blocked_licenses: HashSet<String>,
165}
166
167#[derive(Debug, Clone, Serialize, Deserialize)]
169pub struct DependencyScanResult {
170 pub total_dependencies: usize,
172 pub vulnerable_dependencies: Vec<VulnerableDependency>,
174 pub outdated_dependencies: Vec<OutdatedDependency>,
176 pub license_violations: Vec<LicenseViolation>,
178 pub supply_chain_risks: Vec<SupplyChainRisk>,
180 pub dependency_tree: DependencyTree,
182 pub risk_score: f64,
184}
185
186#[derive(Debug, Clone, Serialize, Deserialize)]
188pub struct VulnerableDependency {
189 pub name: String,
191 pub current_version: String,
193 pub vulnerabilities: Vec<Vulnerability>,
195 pub affected_versions: String,
197 pub fixed_version: Option<String>,
199 pub severity: SecuritySeverity,
201 pub cve_ids: Vec<String>,
203}
204
205#[derive(Debug, Clone, Serialize, Deserialize)]
207pub struct Vulnerability {
208 pub id: String,
210 pub title: String,
212 pub description: String,
214 pub severity: SecuritySeverity,
216 pub cvss_score: Option<f64>,
218 pub published: SystemTime,
220 pub discovered: Option<SystemTime>,
222 pub affected_versions: String,
224 pub patched_versions: Vec<String>,
226 pub references: Vec<String>,
228 pub categories: Vec<VulnerabilityCategory>,
230}
231
232#[derive(Debug, Clone, Serialize, Deserialize)]
234pub enum VulnerabilityCategory {
235 CodeExecution,
236 MemoryCorruption,
237 InformationLeak,
238 DenialOfService,
239 PrivilegeEscalation,
240 AuthenticationBypass,
241 Cryptographic,
242 InputValidation,
243 Other(String),
244}
245
246#[derive(Debug, Clone, Serialize, Deserialize)]
248pub struct StaticAnalysisResult {
249 pub security_issues: Vec<SecurityIssue>,
251 pub quality_issues: Vec<QualityIssue>,
253 pub files_scanned: usize,
255 pub lines_analyzed: usize,
257 pub analysis_duration: Duration,
259}
260
261#[derive(Debug, Clone, Serialize, Deserialize)]
263pub struct SecurityIssue {
264 pub id: String,
266 pub issue_type: SecurityIssueType,
268 pub severity: SecuritySeverity,
270 pub file: PathBuf,
272 pub line: usize,
274 pub column: Option<usize>,
276 pub description: String,
278 pub code_snippet: Option<String>,
280 pub remediation: Option<String>,
282 pub rule_id: String,
284}
285
286#[derive(Debug, Clone, Serialize, Deserialize)]
288pub enum SecurityIssueType {
289 UnsafeCode,
290 HardcodedSecret,
291 WeakCryptography,
292 SqlInjection,
293 PathTraversal,
294 CommandInjection,
295 BufferOverflow,
296 IntegerOverflow,
297 UseAfterFree,
298 DoubleFree,
299 UnvalidatedInput,
300 InformationLeak,
301 InsecureDeserialization,
302 Other(String),
303}
304
305#[derive(Debug)]
307#[allow(dead_code)]
308pub struct VulnerabilityDatabase {
309 config: VulnerabilityDatabaseConfig,
311 local_cache: HashMap<String, CachedVulnerability>,
313 last_update: SystemTime,
315 update_frequency: Duration,
317 external_sources: Vec<ExternalVulnerabilitySource>,
319}
320
321#[derive(Debug, Clone)]
323pub struct VulnerabilityDatabaseConfig {
324 pub auto_update: bool,
326 pub update_frequency: Duration,
328 pub cache_size_limit: usize,
330 pub cache_retention: Duration,
332 pub external_sources: Vec<String>,
334 pub api_keys: HashMap<String, String>,
336}
337
338#[derive(Debug, Clone, Serialize, Deserialize)]
340pub struct CachedVulnerability {
341 pub vulnerability: Vulnerability,
343 pub cached_at: SystemTime,
345 pub source: String,
347 pub verified: bool,
349}
350
351#[derive(Debug, Clone)]
353pub struct ExternalVulnerabilitySource {
354 pub name: String,
356 pub endpoint: String,
358 pub api_key: Option<String>,
360 pub update_frequency: Duration,
362 pub priority: u8,
364}
365
366#[derive(Debug)]
368#[allow(dead_code)]
369pub struct SecurityPolicyEnforcer {
370 policies: Vec<SecurityPolicy>,
372 evaluator: PolicyEvaluator,
374 violations: Vec<PolicyViolation>,
376}
377
378#[derive(Debug, Clone, Serialize, Deserialize)]
380pub struct SecurityPolicy {
381 pub id: String,
383 pub name: String,
385 pub description: String,
387 pub rules: Vec<PolicyRule>,
389 pub enforcement: EnforcementLevel,
391 pub scopes: Vec<PolicyScope>,
393}
394
395#[derive(Debug, Clone, Serialize, Deserialize)]
397pub struct PolicyRule {
398 pub id: String,
400 pub condition: PolicyCondition,
402 pub action: PolicyAction,
404 pub severity: SecuritySeverity,
406}
407
408#[derive(Debug, Clone, Serialize, Deserialize)]
410pub enum EnforcementLevel {
411 Advisory,
413 Warning,
415 Enforcing,
417 Panic,
419}
420
421#[derive(Debug, Clone, Serialize, Deserialize)]
423pub struct RiskAssessment {
424 pub overall_risk: RiskLevel,
426 pub risk_factors: Vec<RiskFactor>,
428 pub risk_score: f64,
430 pub recommendations: Vec<String>,
432 pub mitigation_strategies: Vec<MitigationStrategy>,
434}
435
436#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
438pub enum RiskLevel {
439 Minimal,
440 Low,
441 Medium,
442 High,
443 Critical,
444}
445
446#[derive(Debug, Clone, Serialize, Deserialize)]
448pub struct RiskFactor {
449 pub name: String,
451 pub description: String,
453 pub impact: f64,
455 pub likelihood: f64,
457 pub risk_contribution: f64,
459}
460
461#[derive(Debug, Clone, Serialize, Deserialize)]
463pub struct MitigationStrategy {
464 pub name: String,
466 pub description: String,
468 pub steps: Vec<String>,
470 pub effort: EffortLevel,
472 pub risk_reduction: f64,
474}
475
476#[derive(Debug, Clone, Serialize, Deserialize)]
478pub enum EffortLevel {
479 Minimal,
480 Low,
481 Medium,
482 High,
483 Extensive,
484}
485
486impl ComprehensiveSecurityAuditor {
487 pub fn new(config: SecurityAuditConfig) -> Self {
489 let dependency_scanner = DependencyScanner::new(DependencyScanConfig::default());
490 let vulnerability_db = VulnerabilityDatabase::new(VulnerabilityDatabaseConfig::default());
491 let policy_enforcer = SecurityPolicyEnforcer::new();
492 let report_generator = SecurityReportGenerator::new();
493
494 Self {
495 config,
496 dependency_scanner,
497 vulnerability_db,
498 policy_enforcer,
499 report_generator,
500 audit_history: Vec::new(),
501 }
502 }
503
504 pub fn audit_project<P: AsRef<Path>>(&mut self, projectpath: P) -> Result<SecurityAuditResult> {
506 let start_time = std::time::Instant::now();
507 let projectpath = projectpath.as_ref();
508
509 self.update_vulnerability_database()?;
511
512 let mut auditresult = SecurityAuditResult {
514 timestamp: SystemTime::now(),
515 duration: Duration::from_secs(0),
516 security_score: 0.0,
517 dependency_results: DependencyScanResult::default(),
518 static_analysis_results: StaticAnalysisResult::default(),
519 license_compliance_results: LicenseComplianceResult::default(),
520 supply_chain_results: SupplyChainAnalysisResult::default(),
521 secret_detection_results: SecretDetectionResult::default(),
522 config_security_results: ConfigSecurityResult::default(),
523 policy_compliance_results: PolicyComplianceResult::default(),
524 remediation_suggestions: Vec::new(),
525 risk_assessment: RiskAssessment::default(),
526 };
527
528 if self.config.enable_dependency_scanning {
530 auditresult.dependency_results =
531 self.dependency_scanner.scan_dependencies(projectpath)?;
532 }
533
534 if self.config.enable_static_analysis {
536 auditresult.static_analysis_results = self.run_static_analysis(projectpath)?;
537 }
538
539 if self.config.enable_license_compliance {
541 auditresult.license_compliance_results = self.check_license_compliance(projectpath)?;
542 }
543
544 if self.config.enable_supply_chain_analysis {
546 auditresult.supply_chain_results = self.analyze_supply_chain(projectpath)?;
547 }
548
549 if self.config.enable_secret_detection {
551 auditresult.secret_detection_results = self.detect_secrets(projectpath)?;
552 }
553
554 if self.config.enable_config_security {
556 auditresult.config_security_results = self.check_config_security(projectpath)?;
557 }
558
559 auditresult.policy_compliance_results =
561 self.policy_enforcer.check_compliance(&auditresult)?;
562
563 if self.config.enable_auto_remediation {
565 auditresult.remediation_suggestions =
566 self.generate_remediation_suggestions(&auditresult)?;
567 }
568
569 auditresult.risk_assessment = self.assess_risk(&auditresult)?;
571
572 auditresult.security_score = self.calculate_security_score(&auditresult);
574
575 auditresult.duration = start_time.elapsed();
577
578 self.audit_history.push(auditresult.clone());
580
581 self.check_alerts(&auditresult)?;
583
584 Ok(auditresult)
585 }
586
587 pub fn update_vulnerability_database(&mut self) -> Result<()> {
589 if self.vulnerability_db.needs_update() {
590 self.vulnerability_db.update_from_sources()?;
591 }
592 Ok(())
593 }
594
595 pub fn scan_dependencies_with_rustsec(
597 &mut self,
598 projectpath: &Path,
599 ) -> Result<DependencyScanResult> {
600 let _start_time = std::time::Instant::now();
601 let mut vulnerable_dependencies = Vec::new();
602 let mut outdated_dependencies = Vec::new();
603
604 let cargo_toml_path = projectpath.join("Cargo.toml");
606 if !cargo_toml_path.exists() {
607 return Ok(DependencyScanResult::default());
608 }
609
610 let cargocontent = std::fs::read_to_string(&cargo_toml_path)?;
611
612 let dependencies = self.parse_cargo_dependencies(&cargocontent)?;
614 let total_dependencies = dependencies.len();
615
616 for (depname, version) in dependencies {
618 if self.is_vulnerable_dependency(&depname, &version) {
620 let vulnerability = Vulnerability {
621 id: "RUSTSEC-XXXX-XXXX".to_string(),
622 title: format!("Vulnerability in {}", depname),
623 description: format!(
624 "Security vulnerability found in {} version {}",
625 depname, version
626 ),
627 severity: SecuritySeverity::High,
628 cvss_score: Some(7.5),
629 published: SystemTime::now(),
630 discovered: None,
631 affected_versions: format!("<= {}", version),
632 patched_versions: vec![format!("> {}", version)],
633 references: vec![format!("https://rustsec.org/advisories/{}", depname)],
634 categories: vec![VulnerabilityCategory::Other("General".to_string())],
635 };
636
637 vulnerable_dependencies.push(VulnerableDependency {
638 name: depname.clone(),
639 current_version: version.clone(),
640 vulnerabilities: vec![vulnerability],
641 affected_versions: format!("<= {}", version),
642 fixed_version: Some(format!("> {}", version)),
643 severity: SecuritySeverity::High,
644 cve_ids: vec!["CVE-2024-XXXX".to_string()],
645 });
646 }
647
648 if self.is_outdated_dependency(&depname, &version) {
650 outdated_dependencies.push(OutdatedDependency {
651 name: depname,
652 current_version: version,
653 latest_version: "latest".to_string(), });
655 }
656 }
657
658 let risk_score =
659 self.calculate_dependency_risk_score(&vulnerable_dependencies, &outdated_dependencies);
660
661 Ok(DependencyScanResult {
662 total_dependencies,
663 vulnerable_dependencies,
664 outdated_dependencies,
665 license_violations: Vec::new(),
666 supply_chain_risks: Vec::new(),
667 dependency_tree: DependencyTree::default(),
668 risk_score,
669 })
670 }
671
672 fn parse_cargo_dependencies(&self, cargocontent: &str) -> Result<Vec<(String, String)>> {
674 let mut dependencies = Vec::new();
675 let mut in_dependencies_section = false;
676
677 for line in cargocontent.lines() {
678 let line = line.trim();
679
680 if line == "[dependencies]" {
681 in_dependencies_section = true;
682 continue;
683 }
684
685 if line.starts_with('[') && line != "[dependencies]" {
686 in_dependencies_section = false;
687 continue;
688 }
689
690 if in_dependencies_section && line.contains('=') {
691 let parts: Vec<&str> = line.split('=').collect();
692 if parts.len() >= 2 {
693 let depname = parts[0].trim().to_string();
694 let version = parts[1].trim().trim_matches('"').to_string();
695 dependencies.push((depname, version));
696 }
697 }
698 }
699
700 Ok(dependencies)
701 }
702
703 fn is_vulnerable_dependency(&self, depname: &str, version: &str) -> bool {
705 let known_vulnerable = ["old-time", "chrono", "serde_yaml"];
708 known_vulnerable.contains(&depname)
709 }
710
711 fn is_outdated_dependency(&self, depname: &str, version: &str) -> bool {
713 version.starts_with("0.") && depname.len() > 5
716 }
717
718 fn calculate_dependency_risk_score(
720 &self,
721 vulnerable_deps: &[VulnerableDependency],
722 outdated_deps: &[OutdatedDependency],
723 ) -> f64 {
724 let vuln_penalty = vulnerable_deps.len() as f64 * 0.3;
725 let outdated_penalty = outdated_deps.len() as f64 * 0.1;
726 (vuln_penalty + outdated_penalty).min(1.0)
727 }
728
729 fn run_static_analysis(&self, projectpath: &Path) -> Result<StaticAnalysisResult> {
731 let start_time = std::time::Instant::now();
732 let mut security_issues = Vec::new();
733 let quality_issues = Vec::new();
734 let mut files_scanned = 0;
735 let mut lines_analyzed = 0;
736
737 let rust_files = self.find_rust_files(projectpath)?;
739
740 for filepath in rust_files {
741 if self.is_excluded_path(&filepath) {
742 continue;
743 }
744
745 let content = std::fs::read_to_string(&filepath)?;
746 let file_lines = content.lines().count();
747 lines_analyzed += file_lines;
748 files_scanned += 1;
749
750 let mut file_issues = self.analyze_file_security(&filepath, &content)?;
752 security_issues.append(&mut file_issues);
753
754 let mut custom_issues = self.apply_custom_rules(&filepath, &content)?;
756 security_issues.append(&mut custom_issues);
757 }
758
759 Ok(StaticAnalysisResult {
760 security_issues,
761 quality_issues,
762 files_scanned,
763 lines_analyzed,
764 analysis_duration: start_time.elapsed(),
765 })
766 }
767
768 fn analyze_file_security(&self, filepath: &Path, content: &str) -> Result<Vec<SecurityIssue>> {
770 let mut issues = Vec::new();
771
772 for (line_num, line) in content.lines().enumerate() {
773 if line.trim_start().starts_with("unsafe") {
775 issues.push(SecurityIssue {
776 id: format!("unsafe_code_{}", line_num),
777 issue_type: SecurityIssueType::UnsafeCode,
778 severity: SecuritySeverity::Medium,
779 file: filepath.to_path_buf(),
780 line: line_num + 1,
781 column: Some(line.find("unsafe").unwrap_or(0)),
782 description: "Unsafe code block detected - review for memory safety"
783 .to_string(),
784 code_snippet: Some(line.to_string()),
785 remediation: Some(
786 "Ensure unsafe code is properly justified and reviewed".to_string(),
787 ),
788 rule_id: "SEC001".to_string(),
789 });
790 }
791
792 if self.contains_potential_secret(line) {
794 issues.push(SecurityIssue {
795 id: format!("secret_{}", line_num),
796 issue_type: SecurityIssueType::HardcodedSecret,
797 severity: SecuritySeverity::High,
798 file: filepath.to_path_buf(),
799 line: line_num + 1,
800 column: None,
801 description: "Potential hardcoded secret detected".to_string(),
802 code_snippet: Some(self.sanitize_secret_in_line(line)),
803 remediation: Some(
804 "Move secrets to environment variables or secure configuration".to_string(),
805 ),
806 rule_id: "SEC002".to_string(),
807 });
808 }
809
810 if line.contains("Command::new") || line.contains("process::Command") {
812 issues.push(SecurityIssue {
813 id: format!("command_injection_{}", line_num),
814 issue_type: SecurityIssueType::CommandInjection,
815 severity: SecuritySeverity::Medium,
816 file: filepath.to_path_buf(),
817 line: line_num + 1,
818 column: None,
819 description: "Command execution detected - ensure input validation".to_string(),
820 code_snippet: Some(line.to_string()),
821 remediation: Some("Validate and sanitize all command arguments".to_string()),
822 rule_id: "SEC003".to_string(),
823 });
824 }
825
826 if self.uses_weak_crypto(line) {
828 issues.push(SecurityIssue {
829 id: format!("weak_crypto_{}", line_num),
830 issue_type: SecurityIssueType::WeakCryptography,
831 severity: SecuritySeverity::High,
832 file: filepath.to_path_buf(),
833 line: line_num + 1,
834 column: None,
835 description: "Weak cryptographic algorithm detected".to_string(),
836 code_snippet: Some(line.to_string()),
837 remediation: Some("Use modern, secure cryptographic algorithms".to_string()),
838 rule_id: "SEC004".to_string(),
839 });
840 }
841 }
842
843 Ok(issues)
844 }
845
846 fn apply_custom_rules(&self, filepath: &Path, content: &str) -> Result<Vec<SecurityIssue>> {
848 let mut issues = Vec::new();
849
850 for rule in &self.config.custom_rules {
851 if let Some(extension) = filepath.extension() {
853 let ext_str = extension.to_str().unwrap_or("");
854 if !rule.file_types.is_empty() && !rule.file_types.contains(&ext_str.to_string()) {
855 continue;
856 }
857 }
858
859 for (line_num, line) in content.lines().enumerate() {
861 if line.to_lowercase().contains(&rule.pattern.to_lowercase()) {
862 issues.push(SecurityIssue {
863 id: format!("custom_{}_{}", rule.id, line_num),
864 issue_type: SecurityIssueType::Other(rule.name.clone()),
865 severity: rule.severity,
866 file: filepath.to_path_buf(),
867 line: line_num + 1,
868 column: None,
869 description: rule.description.clone(),
870 code_snippet: Some(line.to_string()),
871 remediation: rule.remediation.clone(),
872 rule_id: rule.id.clone(),
873 });
874 }
875 }
876 }
877
878 Ok(issues)
879 }
880
881 fn contains_potential_secret(&self, line: &str) -> bool {
883 let secret_indicators = [
884 "password",
885 "secret",
886 "token",
887 "api_key",
888 "private_key",
889 "access_key",
890 "auth_token",
891 "bearer",
892 "jwt",
893 ];
894
895 let line_lower = line.to_lowercase();
896
897 if line_lower.contains('=') && (line_lower.contains('"') || line_lower.contains('\'')) {
899 for indicator in &secret_indicators {
900 if line_lower.contains(indicator) {
901 return true;
902 }
903 }
904
905 if let Some(quote_start) = line.find('"') {
907 if let Some(quote_end) = line[quote_start + 1..].find('"') {
908 let potential_secret = &line[quote_start + 1..quote_start + 1 + quote_end];
909 if potential_secret.len() > 16
910 && potential_secret.chars().any(|c| c.is_ascii_alphanumeric())
911 {
912 return true;
913 }
914 }
915 }
916 }
917
918 false
919 }
920
921 fn sanitize_secret_in_line(&self, line: &str) -> String {
923 let mut sanitized = line.to_string();
924
925 if let Some(quote_start) = sanitized.find('"') {
927 if let Some(quote_end) = sanitized[quote_start + 1..].find('"') {
928 let before = &sanitized[..quote_start + 1];
929 let after = &sanitized[quote_start + 1 + quote_end..];
930 sanitized = format!("{}[REDACTED]{}", before, after);
931 }
932 }
933
934 sanitized
935 }
936
937 fn uses_weak_crypto(&self, line: &str) -> bool {
939 let weak_crypto_patterns = ["md5", "sha1", "des", "3des", "rc4", "md4"];
940
941 let line_lower = line.to_lowercase();
942 weak_crypto_patterns
943 .iter()
944 .any(|pattern| line_lower.contains(pattern))
945 }
946
947 fn find_rust_files(&self, projectpath: &Path) -> Result<Vec<PathBuf>> {
949 let mut rust_files = Vec::new();
950
951 fn visit_dir(dir: &Path, files: &mut Vec<PathBuf>) -> std::io::Result<()> {
952 for entry in std::fs::read_dir(dir)? {
953 let entry = entry?;
954 let path = entry.path();
955
956 if path.is_dir() {
957 if let Some(dir_name) = path.file_name().and_then(|n| n.to_str()) {
959 if ["target", ".git", "node_modules"].contains(&dir_name) {
960 continue;
961 }
962 }
963 visit_dir(&path, files)?;
964 } else if let Some(extension) = path.extension() {
965 if extension == "rs" {
966 files.push(path);
967 }
968 }
969 }
970 Ok(())
971 }
972
973 visit_dir(projectpath, &mut rust_files)?;
974 Ok(rust_files)
975 }
976
977 fn is_excluded_path(&self, path: &Path) -> bool {
979 self.config.excluded_paths.iter().any(|excluded| {
980 path.starts_with(excluded)
981 || path
982 .components()
983 .any(|component| component.as_os_str() == excluded.as_os_str())
984 })
985 }
986
987 fn calculate_security_score(&self, auditresult: &SecurityAuditResult) -> f64 {
989 let mut score = 1.0;
990
991 let critical_vulns = auditresult
993 .dependency_results
994 .vulnerable_dependencies
995 .iter()
996 .filter(|dep| dep.severity == SecuritySeverity::Critical)
997 .count();
998 let high_vulns = auditresult
999 .dependency_results
1000 .vulnerable_dependencies
1001 .iter()
1002 .filter(|dep| dep.severity == SecuritySeverity::High)
1003 .count();
1004
1005 score -= critical_vulns as f64 * 0.2;
1006 score -= high_vulns as f64 * 0.1;
1007
1008 let critical_issues = auditresult
1010 .static_analysis_results
1011 .security_issues
1012 .iter()
1013 .filter(|issue| issue.severity == SecuritySeverity::Critical)
1014 .count();
1015 let high_issues = auditresult
1016 .static_analysis_results
1017 .security_issues
1018 .iter()
1019 .filter(|issue| issue.severity == SecuritySeverity::High)
1020 .count();
1021
1022 score -= critical_issues as f64 * 0.15;
1023 score -= high_issues as f64 * 0.08;
1024
1025 score -= auditresult.secret_detection_results.secrets_found.len() as f64 * 0.1;
1027
1028 score -= auditresult.license_compliance_results.violations.len() as f64 * 0.05;
1030
1031 let critical_violations = auditresult
1033 .policy_compliance_results
1034 .violations
1035 .iter()
1036 .filter(|v| v.severity == SecuritySeverity::Critical)
1037 .count();
1038 score -= critical_violations as f64 * 0.1;
1039
1040 score.clamp(0.0, 1.0)
1041 }
1042
1043 fn generate_remediation_suggestions(
1045 &self,
1046 auditresult: &SecurityAuditResult,
1047 ) -> Result<Vec<RemediationSuggestion>> {
1048 let mut suggestions = Vec::new();
1049
1050 for vuln_dep in &auditresult.dependency_results.vulnerable_dependencies {
1052 if let Some(fixed_version) = &vuln_dep.fixed_version {
1053 suggestions.push(RemediationSuggestion {
1054 id: format!("dep_update_{}", vuln_dep.name),
1055 title: format!("Update {} to secure version", vuln_dep.name),
1056 description: format!(
1057 "Update {} from {} to {} to fix security vulnerabilities",
1058 vuln_dep.name, vuln_dep.current_version, fixed_version
1059 ),
1060 priority: match vuln_dep.severity {
1061 SecuritySeverity::Critical => RemediationPriority::Critical,
1062 SecuritySeverity::High => RemediationPriority::High,
1063 SecuritySeverity::Medium => RemediationPriority::Medium,
1064 SecuritySeverity::Low => RemediationPriority::Low,
1065 SecuritySeverity::Info => RemediationPriority::Low,
1066 },
1067 effort: EffortLevel::Low,
1068 steps: vec![
1069 format!(
1070 "Update Cargo.toml to use {} = \"{}\"",
1071 vuln_dep.name, fixed_version
1072 ),
1073 "Run cargo update".to_string(),
1074 "Test the application thoroughly".to_string(),
1075 ],
1076 automated: true,
1077 });
1078 }
1079 }
1080
1081 for issue in &auditresult.static_analysis_results.security_issues {
1083 if let Some(remediation) = &issue.remediation {
1084 suggestions.push(RemediationSuggestion {
1085 id: format!("static_{}", issue.id),
1086 title: format!("Fix security issue: {}", issue.description),
1087 description: remediation.clone(),
1088 priority: match issue.severity {
1089 SecuritySeverity::Critical => RemediationPriority::Critical,
1090 SecuritySeverity::High => RemediationPriority::High,
1091 SecuritySeverity::Medium => RemediationPriority::Medium,
1092 SecuritySeverity::Low => RemediationPriority::Low,
1093 SecuritySeverity::Info => RemediationPriority::Low,
1094 },
1095 effort: EffortLevel::Medium,
1096 steps: vec![
1097 format!("Review code at {}:{}", issue.file.display(), issue.line),
1098 remediation.clone(),
1099 "Test the fix thoroughly".to_string(),
1100 ],
1101 automated: false,
1102 });
1103 }
1104 }
1105
1106 for secret in &auditresult.secret_detection_results.secrets_found {
1108 suggestions.push(RemediationSuggestion {
1109 id: format!("secret_{}", secret.id),
1110 title: "Remove hardcoded secret".to_string(),
1111 description:
1112 "Move hardcoded secret to environment variable or secure configuration"
1113 .to_string(),
1114 priority: RemediationPriority::High,
1115 effort: EffortLevel::Medium,
1116 steps: vec![
1117 "Remove the hardcoded secret from source code".to_string(),
1118 "Add the secret as an environment variable".to_string(),
1119 "Update code to read from environment".to_string(),
1120 "Rotate the secret if it was committed to version control".to_string(),
1121 ],
1122 automated: false,
1123 });
1124 }
1125
1126 Ok(suggestions)
1127 }
1128
1129 fn assess_risk(&self, auditresult: &SecurityAuditResult) -> Result<RiskAssessment> {
1131 let mut risk_factors = Vec::new();
1132 let mut total_risk = 0.0;
1133
1134 let vuln_count = auditresult.dependency_results.vulnerable_dependencies.len();
1136 if vuln_count > 0 {
1137 let vuln_risk = (vuln_count as f64 * 0.1).min(0.8);
1138 risk_factors.push(RiskFactor {
1139 name: "Dependency Vulnerabilities".to_string(),
1140 description: format!("{} vulnerable dependencies found", vuln_count),
1141 impact: 0.8,
1142 likelihood: 0.9,
1143 risk_contribution: vuln_risk,
1144 });
1145 total_risk += vuln_risk;
1146 }
1147
1148 let issue_count = auditresult.static_analysis_results.security_issues.len();
1150 if issue_count > 0 {
1151 let issue_risk = (issue_count as f64 * 0.05).min(0.6);
1152 risk_factors.push(RiskFactor {
1153 name: "Static Analysis Issues".to_string(),
1154 description: format!("{} security issues found in code", issue_count),
1155 impact: 0.6,
1156 likelihood: 0.7,
1157 risk_contribution: issue_risk,
1158 });
1159 total_risk += issue_risk;
1160 }
1161
1162 let secret_count = auditresult.secret_detection_results.secrets_found.len();
1164 if secret_count > 0 {
1165 let secret_risk = (secret_count as f64 * 0.2).min(0.9);
1166 risk_factors.push(RiskFactor {
1167 name: "Exposed Secrets".to_string(),
1168 description: format!("{} hardcoded secrets found", secret_count),
1169 impact: 0.9,
1170 likelihood: 0.8,
1171 risk_contribution: secret_risk,
1172 });
1173 total_risk += secret_risk;
1174 }
1175
1176 let overall_risk = match total_risk {
1177 r if r >= 0.8 => RiskLevel::Critical,
1178 r if r >= 0.6 => RiskLevel::High,
1179 r if r >= 0.4 => RiskLevel::Medium,
1180 r if r >= 0.2 => RiskLevel::Low,
1181 _ => RiskLevel::Minimal,
1182 };
1183
1184 let mitigation_strategies = self.generate_mitigation_strategies(&risk_factors);
1185
1186 Ok(RiskAssessment {
1187 overall_risk,
1188 risk_factors,
1189 risk_score: total_risk.min(1.0),
1190 recommendations: vec![
1191 "Implement regular security audits".to_string(),
1192 "Keep dependencies up to date".to_string(),
1193 "Use automated security scanning in CI/CD".to_string(),
1194 "Implement secure coding practices".to_string(),
1195 "Regular security training for developers".to_string(),
1196 ],
1197 mitigation_strategies,
1198 })
1199 }
1200
1201 fn generate_mitigation_strategies(
1203 &self,
1204 risk_factors: &[RiskFactor],
1205 ) -> Vec<MitigationStrategy> {
1206 let mut strategies = Vec::new();
1207
1208 for factor in risk_factors {
1209 match factor.name.as_str() {
1210 "Dependency Vulnerabilities" => {
1211 strategies.push(MitigationStrategy {
1212 name: "Automated Dependency Management".to_string(),
1213 description: "Implement automated dependency scanning and updates"
1214 .to_string(),
1215 steps: vec![
1216 "Set up dependabot or renovate for automated updates".to_string(),
1217 "Implement dependency scanning in CI/CD pipeline".to_string(),
1218 "Establish process for reviewing security advisories".to_string(),
1219 "Create dependency approval process".to_string(),
1220 ],
1221 effort: EffortLevel::Medium,
1222 risk_reduction: 0.7,
1223 });
1224 }
1225 "Static Analysis Issues" => {
1226 strategies.push(MitigationStrategy {
1227 name: "Enhanced Static Analysis".to_string(),
1228 description:
1229 "Implement comprehensive static analysis in development workflow"
1230 .to_string(),
1231 steps: vec![
1232 "Integrate static analysis tools in IDE".to_string(),
1233 "Add pre-commit hooks for security checks".to_string(),
1234 "Implement security linting in CI/CD".to_string(),
1235 "Establish code review guidelines for security".to_string(),
1236 ],
1237 effort: EffortLevel::Low,
1238 risk_reduction: 0.6,
1239 });
1240 }
1241 "Exposed Secrets" => {
1242 strategies.push(MitigationStrategy {
1243 name: "Secret Management Implementation".to_string(),
1244 description: "Implement proper secret management practices".to_string(),
1245 steps: vec![
1246 "Deploy secret management solution (HashiCorp Vault, etc.)".to_string(),
1247 "Implement secret scanning in CI/CD".to_string(),
1248 "Rotate all exposed secrets".to_string(),
1249 "Train developers on secret management".to_string(),
1250 ],
1251 effort: EffortLevel::High,
1252 risk_reduction: 0.9,
1253 });
1254 }
1255 _ => {}
1256 }
1257 }
1258
1259 strategies
1260 }
1261
1262 fn check_alerts(&self, auditresult: &SecurityAuditResult) -> Result<()> {
1264 let mut critical_issues = Vec::new();
1265
1266 for vuln_dep in &auditresult.dependency_results.vulnerable_dependencies {
1268 if vuln_dep.severity >= self.config.alert_threshold {
1269 critical_issues.push(format!(
1270 "Critical vulnerability in {}: {}",
1271 vuln_dep.name,
1272 vuln_dep
1273 .vulnerabilities
1274 .first()
1275 .map(|v| &v.title)
1276 .unwrap_or(&"Unknown".to_string())
1277 ));
1278 }
1279 }
1280
1281 for issue in &auditresult.static_analysis_results.security_issues {
1283 if issue.severity >= self.config.alert_threshold {
1284 critical_issues.push(format!("Critical security issue: {}", issue.description));
1285 }
1286 }
1287
1288 if !auditresult
1290 .secret_detection_results
1291 .secrets_found
1292 .is_empty()
1293 {
1294 critical_issues.push("Hardcoded secrets detected in source code".to_string());
1295 }
1296
1297 if !critical_issues.is_empty() {
1299 self.generate_security_alert(critical_issues)?;
1300 }
1301
1302 Ok(())
1303 }
1304
1305 fn generate_security_alert(&self, issues: Vec<String>) -> Result<()> {
1307 eprintln!("🚨 SECURITY ALERT 🚨");
1314 eprintln!("Critical security issues detected:");
1315 for issue in issues {
1316 eprintln!(" • {}", issue);
1317 }
1318 eprintln!("Review the security audit report for details and remediation steps.");
1319
1320 Ok(())
1321 }
1322
1323 pub fn get_audit_history(&self) -> &[SecurityAuditResult] {
1325 &self.audit_history
1326 }
1327
1328 pub fn generate_report(&self, auditresult: &SecurityAuditResult) -> Result<String> {
1330 self.report_generator
1331 .generate_report(auditresult, &self.config.report_format)
1332 }
1333
1334 pub fn run_scheduled_audit(
1336 &mut self,
1337 projectpath: &Path,
1338 schedule: AuditSchedule,
1339 ) -> Result<()> {
1340 match schedule {
1341 AuditSchedule::Daily => {
1342 let mut config = self.config.clone();
1344 config.enable_supply_chain_analysis = false;
1345 config.max_audit_time = Duration::from_secs(5 * 60); let temp_auditor = ComprehensiveSecurityAuditor::new(config);
1348 let _result = temp_auditor.audit_project_lightweight(projectpath)?;
1349 }
1350 AuditSchedule::Weekly => {
1351 let _result = self.audit_project(projectpath)?;
1353 }
1354 AuditSchedule::Monthly => {
1355 let _result = self.audit_project(projectpath)?;
1357 self.generate_monthly_security_report()?;
1358 }
1359 }
1360 Ok(())
1361 }
1362
1363 fn audit_project_lightweight(&self, projectpath: &Path) -> Result<SecurityAuditResult> {
1365 let start_time = std::time::Instant::now();
1366
1367 let mut auditresult = SecurityAuditResult {
1368 timestamp: SystemTime::now(),
1369 duration: Duration::from_secs(0),
1370 security_score: 0.0,
1371 dependency_results: DependencyScanResult::default(),
1372 static_analysis_results: self.run_static_analysis(projectpath)?,
1373 license_compliance_results: LicenseComplianceResult::default(),
1374 supply_chain_results: SupplyChainAnalysisResult::default(),
1375 secret_detection_results: self.detect_secrets(projectpath)?,
1376 config_security_results: ConfigSecurityResult::default(),
1377 policy_compliance_results: PolicyComplianceResult::default(),
1378 remediation_suggestions: Vec::new(),
1379 risk_assessment: RiskAssessment::default(),
1380 };
1381
1382 auditresult.security_score = self.calculate_security_score(&auditresult);
1383 auditresult.duration = start_time.elapsed();
1384
1385 Ok(auditresult)
1386 }
1387
1388 fn generate_monthly_security_report(&self) -> Result<()> {
1390 let recent_audits: Vec<_> = self
1392 .audit_history
1393 .iter()
1394 .filter(|audit| {
1395 audit
1396 .timestamp
1397 .elapsed()
1398 .map(|duration| duration < Duration::from_secs(30 * 24 * 60 * 60))
1399 .unwrap_or(false)
1400 })
1401 .collect();
1402
1403 if recent_audits.is_empty() {
1404 return Ok(());
1405 }
1406
1407 let avg_security_score = recent_audits
1409 .iter()
1410 .map(|audit| audit.security_score)
1411 .sum::<f64>()
1412 / recent_audits.len() as f64;
1413
1414 let vulnerability_trend = recent_audits
1415 .iter()
1416 .map(|audit| audit.dependency_results.vulnerable_dependencies.len())
1417 .collect::<Vec<_>>();
1418
1419 println!("📊 Monthly Security Report");
1421 println!("==========================");
1422 println!("Average Security Score: {:.2}", avg_security_score);
1423 println!("Vulnerability Trend: {:?}", vulnerability_trend);
1424 println!("Audits Performed: {}", recent_audits.len());
1425
1426 Ok(())
1427 }
1428
1429 fn check_license_compliance(&self, _projectpath: &Path) -> Result<LicenseComplianceResult> {
1431 Ok(LicenseComplianceResult::default())
1432 }
1433
1434 fn analyze_supply_chain(&self, _projectpath: &Path) -> Result<SupplyChainAnalysisResult> {
1435 Ok(SupplyChainAnalysisResult::default())
1436 }
1437
1438 fn detect_secrets(&self, _projectpath: &Path) -> Result<SecretDetectionResult> {
1439 Ok(SecretDetectionResult::default())
1440 }
1441
1442 fn check_config_security(&self, _projectpath: &Path) -> Result<ConfigSecurityResult> {
1443 Ok(ConfigSecurityResult::default())
1444 }
1445}
1446
1447#[derive(Debug, Clone)]
1449pub enum AuditSchedule {
1450 Daily,
1451 Weekly,
1452 Monthly,
1453}
1454
1455#[derive(Debug, Default, Clone, Serialize, Deserialize)]
1459pub struct LicenseComplianceResult {
1460 pub violations: Vec<LicenseViolation>,
1461}
1462
1463#[derive(Debug, Clone, Serialize, Deserialize)]
1464pub struct LicenseViolation {
1465 pub package: String,
1466 pub license: String,
1467 pub reason: String,
1468}
1469
1470#[derive(Debug, Default, Clone, Serialize, Deserialize)]
1471pub struct SupplyChainAnalysisResult {
1472 pub risks: Vec<SupplyChainRisk>,
1473}
1474
1475#[derive(Debug, Clone, Serialize, Deserialize)]
1476pub struct SupplyChainRisk {
1477 pub package: String,
1478 pub risk_type: String,
1479 pub severity: SecuritySeverity,
1480 pub description: String,
1481}
1482
1483#[derive(Debug, Default, Clone, Serialize, Deserialize)]
1484pub struct SecretDetectionResult {
1485 pub secrets_found: Vec<DetectedSecret>,
1486}
1487
1488#[derive(Debug, Clone, Serialize, Deserialize)]
1489pub struct DetectedSecret {
1490 pub id: String,
1491 pub secret_type: String,
1492 pub file: PathBuf,
1493 pub line: usize,
1494 pub severity: SecuritySeverity,
1495}
1496
1497#[derive(Debug, Default, Clone, Serialize, Deserialize)]
1498pub struct ConfigSecurityResult {
1499 pub issues: Vec<ConfigSecurityIssue>,
1500}
1501
1502#[derive(Debug, Clone, Serialize, Deserialize)]
1503pub struct ConfigSecurityIssue {
1504 pub config_file: PathBuf,
1505 pub issue: String,
1506 pub severity: SecuritySeverity,
1507}
1508
1509#[derive(Debug, Default, Clone, Serialize, Deserialize)]
1510pub struct PolicyComplianceResult {
1511 pub violations: Vec<PolicyViolation>,
1512}
1513
1514#[derive(Debug, Clone, Serialize, Deserialize)]
1515pub struct PolicyViolation {
1516 pub policy_id: String,
1517 pub rule_id: String,
1518 pub severity: SecuritySeverity,
1519 pub description: String,
1520}
1521
1522#[derive(Debug, Clone, Serialize, Deserialize)]
1523pub struct RemediationSuggestion {
1524 pub id: String,
1525 pub title: String,
1526 pub description: String,
1527 pub priority: RemediationPriority,
1528 pub effort: EffortLevel,
1529 pub steps: Vec<String>,
1530 pub automated: bool,
1531}
1532
1533#[derive(Debug, Clone, Serialize, Deserialize)]
1534pub enum RemediationPriority {
1535 Low,
1536 Medium,
1537 High,
1538 Critical,
1539}
1540
1541#[derive(Debug, Default, Clone, Serialize, Deserialize)]
1542pub struct OutdatedDependency {
1543 pub name: String,
1544 pub current_version: String,
1545 pub latest_version: String,
1546}
1547
1548#[derive(Debug, Default, Clone, Serialize, Deserialize)]
1549pub struct DependencyTree {
1550 pub root: String,
1551 pub dependencies: HashMap<String, Vec<String>>,
1552}
1553
1554#[derive(Debug, Default, Clone, Serialize, Deserialize)]
1555pub struct QualityIssue {
1556 pub id: String,
1557 pub description: String,
1558 pub file: PathBuf,
1559 pub line: usize,
1560}
1561
1562#[derive(Debug, Clone, Serialize, Deserialize)]
1563pub enum PolicyCondition {
1564 Always,
1565 Never,
1566 Custom(String),
1567}
1568
1569#[derive(Debug, Clone, Serialize, Deserialize)]
1570pub enum PolicyAction {
1571 Allow,
1572 Deny,
1573 Warn,
1574 Log,
1575}
1576
1577#[derive(Debug, Clone, Serialize, Deserialize)]
1578pub enum PolicyScope {
1579 Global,
1580 Project,
1581 Directory(PathBuf),
1582}
1583
1584impl Default for SecurityAuditConfig {
1586 fn default() -> Self {
1587 Self {
1588 enable_dependency_scanning: true,
1589 enable_static_analysis: true,
1590 enable_license_compliance: true,
1591 enable_supply_chain_analysis: true,
1592 enable_secret_detection: true,
1593 enable_config_security: true,
1594 db_update_frequency: Duration::from_secs(24 * 60 * 60), max_audit_time: Duration::from_secs(30 * 60), alert_threshold: SecuritySeverity::High,
1597 report_format: ReportFormat::Json,
1598 enable_auto_remediation: true,
1599 trusted_sources: vec!["crates.io".to_string()],
1600 excluded_paths: vec![
1601 PathBuf::from("target"),
1602 PathBuf::from(".git"),
1603 PathBuf::from("node_modules"),
1604 ],
1605 custom_rules: Vec::new(),
1606 }
1607 }
1608}
1609
1610impl Default for DependencyScanResult {
1611 fn default() -> Self {
1612 Self {
1613 total_dependencies: 0,
1614 vulnerable_dependencies: Vec::new(),
1615 outdated_dependencies: Vec::new(),
1616 license_violations: Vec::new(),
1617 supply_chain_risks: Vec::new(),
1618 dependency_tree: DependencyTree::default(),
1619 risk_score: 0.0,
1620 }
1621 }
1622}
1623
1624impl Default for StaticAnalysisResult {
1625 fn default() -> Self {
1626 Self {
1627 security_issues: Vec::new(),
1628 quality_issues: Vec::new(),
1629 files_scanned: 0,
1630 lines_analyzed: 0,
1631 analysis_duration: Duration::from_secs(0),
1632 }
1633 }
1634}
1635
1636impl Default for RiskAssessment {
1637 fn default() -> Self {
1638 Self {
1639 overall_risk: RiskLevel::Minimal,
1640 risk_factors: Vec::new(),
1641 risk_score: 0.0,
1642 recommendations: Vec::new(),
1643 mitigation_strategies: Vec::new(),
1644 }
1645 }
1646}
1647
1648impl DependencyScanner {
1650 fn new(config: DependencyScanConfig) -> Self {
1651 Self {
1652 config: DependencyScanConfig::default(),
1653 vuln_db_client: VulnerabilityDatabaseClient::new(),
1654 license_db: LicenseDatabase::new(),
1655 package_cache: HashMap::new(),
1656 }
1657 }
1658
1659 fn scan_dependencies(&mut self, _projectpath: &Path) -> Result<DependencyScanResult> {
1660 Ok(DependencyScanResult::default())
1661 }
1662}
1663
1664impl Default for DependencyScanConfig {
1665 fn default() -> Self {
1666 Self {
1667 scan_direct_deps: true,
1668 scan_transitive_deps: true,
1669 max_depth: 10,
1670 check_outdated: true,
1671 min_versions: HashMap::new(),
1672 blocked_dependencies: HashSet::new(),
1673 allowed_licenses: HashSet::new(),
1674 blocked_licenses: HashSet::new(),
1675 }
1676 }
1677}
1678
1679#[derive(Debug)]
1680struct VulnerabilityDatabaseClient;
1681
1682impl VulnerabilityDatabaseClient {
1683 fn new() -> Self {
1684 Self
1685 }
1686}
1687
1688#[derive(Debug)]
1689struct LicenseDatabase;
1690
1691impl LicenseDatabase {
1692 fn new() -> Self {
1693 Self
1694 }
1695}
1696
1697#[derive(Debug)]
1698struct PackageMetadata;
1699
1700impl VulnerabilityDatabase {
1701 fn new(config: VulnerabilityDatabaseConfig) -> Self {
1702 Self {
1703 config: VulnerabilityDatabaseConfig::default(),
1704 local_cache: HashMap::new(),
1705 last_update: SystemTime::now(),
1706 update_frequency: Duration::from_secs(24 * 60 * 60),
1707 external_sources: Vec::new(),
1708 }
1709 }
1710
1711 fn needs_update(&self) -> bool {
1712 self.last_update.elapsed().unwrap_or(Duration::from_secs(0)) > self.update_frequency
1713 }
1714
1715 fn update_from_sources(&mut self) -> Result<()> {
1716 self.last_update = SystemTime::now();
1717 Ok(())
1718 }
1719}
1720
1721impl Default for VulnerabilityDatabaseConfig {
1722 fn default() -> Self {
1723 Self {
1724 auto_update: true,
1725 update_frequency: Duration::from_secs(24 * 60 * 60),
1726 cache_size_limit: 10000,
1727 cache_retention: Duration::from_secs(7 * 24 * 60 * 60), external_sources: vec!["https://rustsec.org".to_string()],
1729 api_keys: HashMap::new(),
1730 }
1731 }
1732}
1733
1734impl SecurityPolicyEnforcer {
1735 fn new() -> Self {
1736 Self {
1737 policies: Vec::new(),
1738 evaluator: PolicyEvaluator::new(),
1739 violations: Vec::new(),
1740 }
1741 }
1742
1743 fn check_compliance(
1744 &mut self,
1745 _audit_result: &SecurityAuditResult,
1746 ) -> Result<PolicyComplianceResult> {
1747 Ok(PolicyComplianceResult::default())
1748 }
1749}
1750
1751#[derive(Debug)]
1752struct PolicyEvaluator;
1753
1754impl PolicyEvaluator {
1755 fn new() -> Self {
1756 Self
1757 }
1758}
1759
1760#[derive(Debug)]
1761struct SecurityReportGenerator;
1762
1763impl SecurityReportGenerator {
1764 fn new() -> Self {
1765 Self
1766 }
1767
1768 fn generate_report(
1769 &self,
1770 auditresult: &SecurityAuditResult,
1771 format: &ReportFormat,
1772 ) -> Result<String> {
1773 match format {
1774 ReportFormat::Json => Ok(serde_json::to_string_pretty(auditresult)?),
1775 ReportFormat::Markdown => {
1776 let mut report = String::new();
1777 report.push_str("# Security Audit Report\n\n");
1778 report.push_str(&format!("**Audit Date:** {:?}\n", auditresult.timestamp));
1779 report.push_str(&format!(
1780 "**Security Score:** {:.2}/1.0\n\n",
1781 auditresult.security_score
1782 ));
1783
1784 report.push_str("## Dependency Vulnerabilities\n");
1785 report.push_str(&format!(
1786 "Found {} vulnerable dependencies\n\n",
1787 auditresult.dependency_results.vulnerable_dependencies.len()
1788 ));
1789
1790 report.push_str("## Static Analysis Issues\n");
1791 report.push_str(&format!(
1792 "Found {} security issues\n\n",
1793 auditresult.static_analysis_results.security_issues.len()
1794 ));
1795
1796 report.push_str("## Risk Assessment\n");
1797 report.push_str(&format!(
1798 "Overall Risk: {:?}\n",
1799 auditresult.risk_assessment.overall_risk
1800 ));
1801
1802 Ok(report)
1803 }
1804 _ => Ok("Report generation not yet implemented for this format".to_string()),
1805 }
1806 }
1807}
1808
1809#[cfg(test)]
1810mod tests {
1811 use super::*;
1812
1813 #[test]
1814 fn test_security_auditor_creation() {
1815 let config = SecurityAuditConfig::default();
1816 let auditor = ComprehensiveSecurityAuditor::new(config);
1817 assert!(auditor.config.enable_dependency_scanning);
1818 assert!(auditor.config.enable_static_analysis);
1819 }
1820
1821 #[test]
1822 fn test_security_score_calculation() {
1823 let config = SecurityAuditConfig::default();
1824 let auditor = ComprehensiveSecurityAuditor::new(config);
1825
1826 let auditresult = SecurityAuditResult {
1827 timestamp: SystemTime::now(),
1828 duration: Duration::from_secs(10),
1829 security_score: 0.0,
1830 dependency_results: DependencyScanResult::default(),
1831 static_analysis_results: StaticAnalysisResult::default(),
1832 license_compliance_results: LicenseComplianceResult::default(),
1833 supply_chain_results: SupplyChainAnalysisResult::default(),
1834 secret_detection_results: SecretDetectionResult::default(),
1835 config_security_results: ConfigSecurityResult::default(),
1836 policy_compliance_results: PolicyComplianceResult::default(),
1837 remediation_suggestions: Vec::new(),
1838 risk_assessment: RiskAssessment::default(),
1839 };
1840
1841 let score = auditor.calculate_security_score(&auditresult);
1842 assert!((0.0..=1.0).contains(&score));
1843 }
1844
1845 #[test]
1846 fn test_secret_detection() {
1847 let config = SecurityAuditConfig::default();
1848 let auditor = ComprehensiveSecurityAuditor::new(config);
1849
1850 assert!(auditor.contains_potential_secret("password = \"secret123\""));
1851 assert!(auditor.contains_potential_secret("api_key = 'abc123def456'"));
1852 assert!(!auditor.contains_potential_secret("let x = 5;"));
1853 }
1854
1855 #[test]
1856 fn test_weak_crypto_detection() {
1857 let config = SecurityAuditConfig::default();
1858 let auditor = ComprehensiveSecurityAuditor::new(config);
1859
1860 assert!(auditor.uses_weak_crypto("use md5::Md5;"));
1861 assert!(auditor.uses_weak_crypto("let hash = sha1(data);"));
1862 assert!(!auditor.uses_weak_crypto("use sha256::Sha256;"));
1863 }
1864
1865 #[test]
1866 fn test_cargo_dependency_parsing() {
1867 let config = SecurityAuditConfig::default();
1868 let auditor = ComprehensiveSecurityAuditor::new(config);
1869
1870 let cargocontent = r#"
1871[dependencies]
1872serde = "1.0"
1873tokio = { version = "1.0", features = ["full"] }
1874log = "0.4"
1875
1876[dev-dependencies]
1877test-dep = "0.1"
1878"#;
1879
1880 let deps = auditor
1881 .parse_cargo_dependencies(cargocontent)
1882 .expect("unwrap failed");
1883 assert!(deps.len() >= 2);
1884 assert!(deps.iter().any(|(name, _)| name == "serde"));
1885 assert!(deps.iter().any(|(name, _)| name == "log"));
1886 }
1887
1888 #[test]
1889 fn test_dependency_risk_calculation() {
1890 let config = SecurityAuditConfig::default();
1891 let auditor = ComprehensiveSecurityAuditor::new(config);
1892
1893 let vulnerable_deps = vec![VulnerableDependency {
1894 name: "test-dep".to_string(),
1895 current_version: "1.0.0".to_string(),
1896 vulnerabilities: Vec::new(),
1897 affected_versions: "<= 1.0.0".to_string(),
1898 fixed_version: Some("1.0.1".to_string()),
1899 severity: SecuritySeverity::High,
1900 cve_ids: Vec::new(),
1901 }];
1902
1903 let outdated_deps = vec![OutdatedDependency {
1904 name: "old-dep".to_string(),
1905 current_version: "0.1.0".to_string(),
1906 latest_version: "2.0.0".to_string(),
1907 }];
1908
1909 let risk_score = auditor.calculate_dependency_risk_score(&vulnerable_deps, &outdated_deps);
1910 assert!(risk_score > 0.0 && risk_score <= 1.0);
1911 }
1912}