aur_scanner_plugin/
lib.rs

1//! AUR helper plugin library
2//!
3//! Provides integration capabilities for AUR helpers like yay and paru.
4
5use aur_scanner_core::{ScanConfig, ScanResult, Scanner, Severity};
6use colored::Colorize;
7use std::io::{self, Write};
8use std::path::Path;
9
10/// Plugin for AUR helper integration
11pub struct AurScannerPlugin {
12    scanner: Scanner,
13    interactive: bool,
14}
15
16impl AurScannerPlugin {
17    /// Create a new plugin instance
18    pub fn new(config: ScanConfig) -> Result<Self, aur_scanner_core::ScanError> {
19        Ok(Self {
20            scanner: Scanner::new(config)?,
21            interactive: true,
22        })
23    }
24
25    /// Create a plugin with default configuration
26    pub fn with_defaults() -> Result<Self, aur_scanner_core::ScanError> {
27        Self::new(ScanConfig::default())
28    }
29
30    /// Set whether to prompt user interactively
31    pub fn set_interactive(&mut self, interactive: bool) {
32        self.interactive = interactive;
33    }
34
35    /// Scan a package directory before building
36    pub async fn pre_build_scan(
37        &self,
38        package_dir: &Path,
39    ) -> Result<ScanResult, aur_scanner_core::ScanError> {
40        self.scanner.scan_directory(package_dir).await
41    }
42
43    /// Display scan results and optionally prompt user
44    ///
45    /// Returns true if installation should proceed, false to abort
46    pub fn handle_results(&self, result: &ScanResult) -> bool {
47        if result.findings.is_empty() {
48            println!(
49                "{} No security issues found in {}",
50                "OK:".green().bold(),
51                result.package_name
52            );
53            return true;
54        }
55
56        println!();
57        println!(
58            "{} Security Scan Results for {}",
59            "SCAN:".cyan().bold(),
60            result.package_name.bold()
61        );
62        println!("{}", "=".repeat(60));
63
64        for finding in &result.findings {
65            let severity_str = match finding.severity {
66                Severity::Critical => "[CRITICAL]".red().bold().to_string(),
67                Severity::High => "[HIGH]".yellow().bold().to_string(),
68                Severity::Medium => "[MEDIUM]".cyan().to_string(),
69                Severity::Low => "[LOW]".to_string(),
70                Severity::Info => "[INFO]".dimmed().to_string(),
71            };
72
73            println!();
74            println!("{} {} {}", severity_str, finding.id.bold(), finding.title);
75            println!("    {}", finding.description);
76            println!("    {}", finding.recommendation.green());
77        }
78
79        println!();
80        println!("{}", "=".repeat(60));
81
82        // Check for critical issues
83        if result.has_critical() {
84            println!(
85                "{} Critical security issues detected!",
86                "ERROR:".red().bold()
87            );
88
89            if self.interactive {
90                print!("Continue anyway? (type 'yes' to confirm): ");
91                io::stdout().flush().unwrap();
92
93                let mut response = String::new();
94                io::stdin().read_line(&mut response).unwrap();
95
96                if response.trim().to_lowercase() != "yes" {
97                    println!("Installation aborted.");
98                    return false;
99                }
100            } else {
101                println!("Aborting due to critical issues (non-interactive mode).");
102                return false;
103            }
104        } else if result.has_severity_or_above(Severity::High) {
105            println!(
106                "{} High severity issues detected.",
107                "WARNING:".yellow().bold()
108            );
109
110            if self.interactive {
111                print!("Continue with installation? [y/N]: ");
112                io::stdout().flush().unwrap();
113
114                let mut response = String::new();
115                io::stdin().read_line(&mut response).unwrap();
116
117                if !matches!(
118                    response.trim().to_lowercase().as_str(),
119                    "y" | "yes"
120                ) {
121                    println!("Installation aborted.");
122                    return false;
123                }
124            }
125        } else if self.interactive {
126            print!("Continue with installation? [Y/n]: ");
127            io::stdout().flush().unwrap();
128
129            let mut response = String::new();
130            io::stdin().read_line(&mut response).unwrap();
131
132            if matches!(response.trim().to_lowercase().as_str(), "n" | "no") {
133                println!("Installation aborted.");
134                return false;
135            }
136        }
137
138        true
139    }
140}