aboutsummaryrefslogtreecommitdiff
blob: dbccfa30b7620e6ef95ccf299b110cd715e3711a (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
use std::env;
use std::path::Path;
use std::process::Command;

use anyhow::{format_err, Context, Result};
use rustsec::Lockfile;
use rustsec::report::{Settings, VulnerabilityInfo};
use rustsec::{Database, Report, Vulnerability};

fn generate_lockfile(workspace_root: &Path, manifest_path: Option<&Path>) -> Result<Lockfile> {
    let lockfile = workspace_root.join("Cargo.lock");
    let mut command = Command::new(env::var("CARGO").unwrap_or_else(|_| "cargo".into()));

    if lockfile.exists() {
        return Lockfile::load(lockfile).context("Failed to load lockfile");
    }

    command.arg("generate-lockfile");

    if let Some(path) = manifest_path {
        command.arg("--manifest-path");
        command.arg(path.as_os_str());
    }

    let status = command
        .status()
        .context("Failed to run `cargo generate-lockfile`")?;

    match status.code() {
        Some(0) => Lockfile::load(lockfile).context("Failed to load lockfile"),
        Some(code) => Err(format_err!(
            "Non-zero status ({}) on `cargo generate-lockfile`",
            code,
        )),
        None => Err(format_err!(
            "Unexpected termination on `cargo generate-lockfile`",
        )),
    }
}

pub fn audit_package(workspace_root: &Path, manifest_path: Option<&Path>) -> Result<()> {
    let database = Database::fetch().context("Failed to fetch security advisory database")?;
    let lockfile = generate_lockfile(workspace_root, manifest_path)?;
    let settings = Settings::default();
    let report = Report::generate(&database, &lockfile, &settings);

    if report.vulnerabilities.found {
        let VulnerabilityInfo { count, list, .. } = report.vulnerabilities;

        let mut message = match count {
            1 => format!("Found {} vulnerability:\n", count),
            _ => format!("Found {} vulnerabilities:\n", count),
        };

        for Vulnerability {
            package,
            versions,
            advisory,
            ..
        } in list
        {
            message.push('\n');
            message.push_str(&format!("Crate:    {}\n", package.name));
            message.push_str(&format!("Version:  {}\n", package.version.to_string()));
            message.push_str(&format!("Title:    {}\n", advisory.title));
            message.push_str(&format!("Date:     {}\n", advisory.date.as_str()));
            message.push_str(&format!("ID:       {}\n", advisory.id));

            if let Some(url) = advisory.id.url() {
                message.push_str(&format!("URL:      {}\n", url));
            } else if let Some(url) = &advisory.url {
                message.push_str(&format!("URL:      {}\n", url));
            }

            if versions.patched().is_empty() {
                message.push_str("Solution: No solution available\n");
            } else {
                let patched = versions
                    .patched()
                    .iter()
                    .map(ToString::to_string)
                    .collect::<Vec<_>>()
                    .as_slice()
                    .join(" or ");

                message.push_str(&format!("Solution: Upgrade to {}\n", patched));
            }
        }

        message.push_str("\nPlease fix the issues or use \"--noaudit\" flag.\n");

        return Err(format_err!(message));
    }

    Ok(())
}