Skip to content

Commit 258cfb7

Browse files
Copilot0xrinegade
andcommitted
Implement comprehensive audit system improvements addressing all requested enhancements
Co-authored-by: 0xrinegade <[email protected]>
1 parent cb4cc4d commit 258cfb7

File tree

6 files changed

+793
-48
lines changed

6 files changed

+793
-48
lines changed

src/services/audit_service.rs

Lines changed: 14 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -61,30 +61,34 @@ impl AuditService {
6161
}
6262
}
6363

64+
pub fn with_optional_ai(api_key: Option<String>) -> Self {
65+
Self {
66+
coordinator: AuditCoordinator::with_optional_ai(api_key),
67+
}
68+
}
69+
6470
pub fn validate_environment(request: &AuditRequest) -> Result<(), AuditError> {
65-
// Check for OpenAI API key if AI analysis is enabled
71+
// Check for OpenAI API key if AI analysis is explicitly requested
6672
if request.ai_analysis {
6773
match std::env::var("OPENAI_API_KEY") {
68-
Ok(key) if !key.is_empty() => {}
74+
Ok(key) if !key.trim().is_empty() => {
75+
println!("🤖 AI analysis will be enabled with provided API key");
76+
}
6977
Ok(_) => {
70-
return Err(AuditError::EnvironmentError(
71-
"OPENAI_API_KEY environment variable is empty".to_string(),
72-
))
78+
println!("⚠️ OPENAI_API_KEY is empty, AI analysis will be disabled");
7379
}
7480
Err(_) => {
75-
return Err(AuditError::EnvironmentError(
76-
"OPENAI_API_KEY environment variable not found".to_string(),
77-
))
81+
println!("⚠️ OPENAI_API_KEY not found, AI analysis will be disabled");
7882
}
7983
}
8084
}
8185

8286
// Validate output format
8387
match request.format.as_str() {
84-
"typst" | "pdf" | "both" => {}
88+
"typst" | "pdf" | "both" | "json" | "html" | "markdown" => {}
8589
_ => {
8690
return Err(AuditError::ConfigurationError(format!(
87-
"Invalid format '{}'. Valid formats: typst, pdf, both",
91+
"Invalid format '{}'. Valid formats: typst, pdf, both, json, html, markdown",
8892
request.format
8993
)))
9094
}

src/utils/audit.rs

Lines changed: 174 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -477,6 +477,94 @@ impl AuditCoordinator {
477477
}
478478
}
479479

480+
/// Create a new audit coordinator with optional AI capabilities
481+
/// If api_key is None or empty, AI analysis will be disabled
482+
pub fn with_optional_ai(api_key: Option<String>) -> Self {
483+
match api_key {
484+
Some(key) if !key.trim().is_empty() => {
485+
println!("🤖 AI analysis enabled with provided API key");
486+
Self::with_ai(key)
487+
}
488+
_ => {
489+
println!("🔧 Running audit without AI analysis (no API key provided)");
490+
Self::new()
491+
}
492+
}
493+
}
494+
495+
/// Detect if the current directory is a Rust workspace
496+
pub fn is_workspace(&self) -> bool {
497+
std::path::Path::new("Cargo.toml").exists() &&
498+
std::fs::read_to_string("Cargo.toml")
499+
.map(|content| content.contains("[workspace]"))
500+
.unwrap_or(false)
501+
}
502+
503+
/// Get all crate directories in a workspace
504+
pub fn get_workspace_crates(&self) -> Result<Vec<std::path::PathBuf>> {
505+
let mut crates = Vec::new();
506+
507+
if self.is_workspace() {
508+
let cargo_toml = std::fs::read_to_string("Cargo.toml")?;
509+
510+
// Simple parsing to find workspace members
511+
let mut in_workspace = false;
512+
let mut in_members = false;
513+
514+
for line in cargo_toml.lines() {
515+
let line = line.trim();
516+
517+
if line == "[workspace]" {
518+
in_workspace = true;
519+
continue;
520+
}
521+
522+
if in_workspace && line.starts_with("members") {
523+
in_members = true;
524+
continue;
525+
}
526+
527+
if in_members && line.starts_with('[') && !line.starts_with("members") {
528+
break;
529+
}
530+
531+
if in_members && line.contains("\"") {
532+
if let Some(member) = line.split('"').nth(1) {
533+
let crate_path = std::path::PathBuf::from(member);
534+
if crate_path.join("Cargo.toml").exists() {
535+
crates.push(crate_path);
536+
}
537+
}
538+
}
539+
}
540+
} else {
541+
// Single crate
542+
crates.push(std::path::PathBuf::from("."));
543+
}
544+
545+
if crates.is_empty() {
546+
crates.push(std::path::PathBuf::from("."));
547+
}
548+
549+
Ok(crates)
550+
}
551+
552+
/// Get AI client for testing purposes
553+
#[cfg(test)]
554+
pub fn ai_client(&self) -> &Option<OpenAIClient> {
555+
&self.ai_client
556+
}
557+
558+
/// Enhance existing findings with AI analysis (exposed for testing)
559+
#[cfg(test)]
560+
pub async fn enhance_findings_with_ai(
561+
&self,
562+
ai_client: &OpenAIClient,
563+
findings: Vec<AuditFinding>,
564+
) -> Vec<AuditFinding> {
565+
self.enhance_findings_with_ai_internal(ai_client, findings).await
566+
}
567+
480568
/// Run comprehensive security audit with enhanced modular architecture
481569
pub async fn run_security_audit(&self) -> Result<AuditReport> {
482570
println!("🔍 Starting comprehensive security audit with enhanced modular system...");
@@ -485,6 +573,9 @@ impl AuditCoordinator {
485573
println!("🤖 AI-powered analysis enabled");
486574
} else if self.ai_disabled {
487575
println!("🤖 AI analysis disabled due to previous errors");
576+
} else {
577+
println!("🔧 Running audit without AI analysis (no API key provided)");
578+
println!("💡 Consider setting OPENAI_API_KEY environment variable for enhanced analysis");
488579
}
489580

490581
// Create diagnostic coordinator only when needed
@@ -515,7 +606,7 @@ impl AuditCoordinator {
515606
findings.extend(ai_findings);
516607

517608
// Enhance existing findings with AI analysis (only critical/high)
518-
findings = self.enhance_findings_with_ai(ai_client, findings).await;
609+
findings = self.enhance_findings_with_ai_internal(ai_client, findings).await;
519610
}
520611
}
521612

@@ -546,7 +637,7 @@ impl AuditCoordinator {
546637
}
547638

548639
/// Run audit using only the modular system (fallback when diagnostics fail)
549-
async fn run_modular_audit_only(&self) -> Result<AuditReport> {
640+
pub async fn run_modular_audit_only(&self) -> Result<AuditReport> {
550641
println!("🔧 Running modular security audit system...");
551642

552643
let mut findings = Vec::new();
@@ -591,38 +682,94 @@ impl AuditCoordinator {
591682

592683
println!("🔍 Running modular security checks...");
593684

594-
// Analyze Rust source files using the modular system
595-
if let Ok(entries) = std::fs::read_dir("src") {
596-
for entry in entries.flatten() {
597-
if let Some(ext) = entry.path().extension() {
598-
if ext == "rs" {
599-
if let Ok(content) = std::fs::read_to_string(&entry.path()) {
600-
let file_path = entry.path().display().to_string();
601-
602-
match self.modular_coordinator.audit_file(&content, &file_path) {
603-
Ok(findings) => {
604-
println!(" 📄 Analyzed {} - {} findings", file_path, findings.len());
605-
all_findings.extend(findings);
606-
}
607-
Err(e) => {
608-
log::warn!("Failed to analyze file {}: {}", file_path, e);
609-
// Continue with other files even if one fails
610-
}
611-
}
612-
}
613-
}
614-
}
685+
// Get all crates in the workspace
686+
let crates = self.get_workspace_crates()?;
687+
688+
if crates.len() > 1 {
689+
println!("📦 Detected workspace with {} crates", crates.len());
690+
}
691+
692+
for crate_path in &crates {
693+
let src_dir = crate_path.join("src");
694+
let crate_name = crate_path.file_name()
695+
.and_then(|n| n.to_str())
696+
.unwrap_or("root");
697+
698+
if src_dir.exists() {
699+
println!("🔍 Scanning crate: {}", crate_name);
700+
701+
// Recursively scan all Rust files in the crate
702+
self.scan_rust_files_recursive(&src_dir, &mut all_findings, crate_name).await?;
615703
}
616704
}
617705

618-
// Add configuration and dependency checks
619-
all_findings.extend(self.check_dependency_security_enhanced()?);
620-
all_findings.extend(self.check_configuration_security_enhanced()?);
706+
// Add configuration and dependency checks for each crate
707+
for crate_path in &crates {
708+
let current_dir = std::env::current_dir()?;
709+
if let Err(e) = std::env::set_current_dir(crate_path) {
710+
log::warn!("Failed to change directory to {}: {}", crate_path.display(), e);
711+
continue;
712+
}
713+
714+
all_findings.extend(self.check_dependency_security_enhanced()?);
715+
all_findings.extend(self.check_configuration_security_enhanced()?);
716+
717+
// Restore original directory
718+
if let Err(e) = std::env::set_current_dir(&current_dir) {
719+
log::warn!("Failed to restore directory: {}", e);
720+
}
721+
}
621722

622723
println!("✅ Modular security checks completed - {} findings", all_findings.len());
623724
Ok(all_findings)
624725
}
625726

727+
/// Recursively scan Rust files in a directory
728+
fn scan_rust_files_recursive<'a>(
729+
&'a self,
730+
dir: &'a std::path::Path,
731+
findings: &'a mut Vec<AuditFinding>,
732+
crate_name: &'a str
733+
) -> std::pin::Pin<Box<dyn std::future::Future<Output = Result<()>> + 'a>> {
734+
Box::pin(async move {
735+
if let Ok(entries) = std::fs::read_dir(dir) {
736+
for entry in entries.flatten() {
737+
let path = entry.path();
738+
739+
if path.is_dir() {
740+
// Recursively scan subdirectories
741+
self.scan_rust_files_recursive(&path, findings, crate_name).await?;
742+
} else if let Some(ext) = path.extension() {
743+
if ext == "rs" {
744+
if let Ok(content) = std::fs::read_to_string(&path) {
745+
let file_path = format!("{}/{}", crate_name, path.display());
746+
747+
match self.modular_coordinator.audit_file(&content, &file_path) {
748+
Ok(mut file_findings) => {
749+
// Tag findings with the crate name
750+
for finding in &mut file_findings {
751+
finding.code_location = Some(format!("{}:{}", crate_name,
752+
finding.code_location.as_ref().unwrap_or(&"unknown".to_string())));
753+
}
754+
755+
println!(" 📄 Analyzed {} - {} findings", file_path, file_findings.len());
756+
findings.extend(file_findings);
757+
}
758+
Err(e) => {
759+
log::warn!("Failed to analyze file {}: {}", file_path, e);
760+
// Continue with other files even if one fails
761+
}
762+
}
763+
}
764+
}
765+
}
766+
}
767+
}
768+
769+
Ok(())
770+
})
771+
}
772+
626773
/// Create a fallback audit report when diagnostics fail
627774
async fn create_fallback_audit_report(&self) -> Result<AuditReport> {
628775
let mut findings = Vec::new();
@@ -819,7 +966,7 @@ impl AuditCoordinator {
819966
}
820967

821968
/// Enhance existing findings with AI analysis with improved error handling
822-
async fn enhance_findings_with_ai(
969+
async fn enhance_findings_with_ai_internal(
823970
&self,
824971
ai_client: &OpenAIClient,
825972
findings: Vec<AuditFinding>,

0 commit comments

Comments
 (0)