forge-guard 0.3.5

Pre-deployment smart contract auditing framework for Foundry
Documentation
//! `forge-guard scan` — scan dependencies for vulnerabilities.

use crate::dependencies::DependencyScanner;

use super::ScanArgs;
use anyhow::Result;
use colored::*;

/// Scan dependencies for known vulnerabilities.
pub fn run(args: &ScanArgs) -> Result<()> {
    eprintln!("{}", "🔎 Forge Guard — Dependency Scanner".bold());
    eprintln!("   Depth:       {}", args.depth);
    eprintln!(
        "   Fail fast:   {}",
        if args.fail_fast { "yes" } else { "no" }
    );

    let mut scanner = DependencyScanner::new(args.depth);

    // Update vulnerability database if requested
    if args.update {
        eprintln!("\n📥 Updating vulnerability database...");
        scanner.update_database()?;
    }

    // Scan dependencies
    eprintln!("\n🔍 Scanning dependencies...");
    let vulnerabilities = scanner.scan()?;

    // Report results
    if vulnerabilities.is_empty() {
        println!("\n{} No vulnerabilities found!", "".green().bold());
    } else {
        println!(
            "\n{} {} vulnerabilities found:",
            "⚠️".yellow().bold(),
            vulnerabilities.len()
        );

        for vuln in &vulnerabilities {
            let severity_color = match vuln.severity.to_lowercase().as_str() {
                "critical" | "high" => "red",
                "medium" => "yellow",
                _ => "blue",
            };

            println!(
                "\n  [{}] {}",
                vuln.severity.color(severity_color).bold(),
                vuln.name.bold()
            );
            println!("       Package:  {}", vuln.package);
            println!("       Version:  {}", vuln.version);
            println!("       Detail:   {}", vuln.description);
            println!("       Fix:      {}", vuln.recommended_fix);

            if args.fail_fast && vuln.severity.to_lowercase() == "critical" {
                anyhow::bail!("Critical vulnerability found: {}", vuln.name);
            }
        }
    }

    // Output JSON if requested
    if args.shared.json {
        println!("{}", serde_json::to_string_pretty(&vulnerabilities)?);
    }

    Ok(())
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_scan_arg_defaults() {
        let args = ScanArgs {
            shared: super::super::SharedFlags {
                chain: "ethereum".into(),
                project: std::path::PathBuf::from("."),
                json: false,
                markdown: false,
                html: false,
                strict: false,
                offline: false,
                production: false,
                report: false,
                parallelism: 4,
            },
            depth: 1,
            update: false,
            vulnerable_only: false,
            fail_fast: false,
        };
        assert_eq!(args.depth, 1);
        assert!(!args.update);
        assert!(!args.vulnerable_only);
        assert!(!args.fail_fast);
    }

    #[test]
    fn test_scan_with_all_options() {
        let args = ScanArgs {
            shared: super::super::SharedFlags::default(),
            depth: 2,
            update: true,
            vulnerable_only: true,
            fail_fast: true,
        };
        assert_eq!(args.depth, 2);
        assert!(args.update);
        assert!(args.vulnerable_only);
        assert!(args.fail_fast);
    }

    #[test]
    fn test_scan_fail_fast_display() {
        assert_eq!(if true { "yes" } else { "no" }, "yes");
        assert_eq!(if false { "yes" } else { "no" }, "no");
    }

    #[test]
    fn test_dependency_scanner_creation() {
        let scanner = DependencyScanner::new(1);
        // Constructor should succeed
        assert!(scanner.scan().is_ok());
    }

    #[test]
    fn test_dependency_scanner_depth_0() {
        let scanner = DependencyScanner::new(0);
        let results = scanner.scan().unwrap();
        // Depth 0 should return results or empty
        assert!(results.len() <= 50);
    }
}