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
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
pub mod cfg;
pub(crate) mod diags;
mod helpers;

use crate::{diag, LintLevel};
pub use diags::Code;
pub use helpers::{
    db::{AdvisoryDb, DbSet, Fetch, Id, Report},
    index::{Entry, Indices},
};

pub trait AuditReporter {
    fn report(&mut self, report: serde_json::Value);
}

/// For when you just want to satisfy `AuditReporter` without doing anything
pub struct NoneReporter;
impl AuditReporter for NoneReporter {
    fn report(&mut self, _report: serde_json::Value) {}
}

impl<F> AuditReporter for F
where
    F: FnMut(serde_json::Value),
{
    fn report(&mut self, report: serde_json::Value) {
        self(report);
    }
}

/// Check crates against the advisory database to detect vulnerabilities or
/// unmaintained crates
pub fn check<R, S>(
    ctx: crate::CheckCtx<'_, cfg::ValidConfig>,
    advisory_dbs: &DbSet,
    audit_compatible_reporter: Option<R>,
    indices: Option<Indices<'_>>,
    sink: S,
) where
    R: AuditReporter,
    S: Into<diag::ErrorSink>,
{
    let mut sink = sink.into();
    let emit_audit_compatible_reports = audit_compatible_reporter.is_some();

    let (report, yanked) = rayon::join(
        || Report::generate(advisory_dbs, ctx.krates, emit_audit_compatible_reports),
        || {
            if let Some(indices) = indices {
                let yanked: Vec<_> = ctx
                    .krates
                    .krates()
                    .filter_map(|package| match indices.is_yanked(package) {
                        Ok(is_yanked) => {
                            if is_yanked {
                                Some((package, None))
                            } else {
                                None
                            }
                        }
                        Err(err) => Some((package, Some(err))),
                    })
                    .collect();

                yanked
            } else {
                Vec::new()
            }
        },
    );

    use bitvec::prelude::*;
    let mut ignore_hits: BitVec = BitVec::repeat(false, ctx.cfg.ignore.len());
    let mut ignore_yanked_hits: BitVec = BitVec::repeat(false, ctx.cfg.ignore_yanked.len());

    // Emit diagnostics for any advisories found that matched crates in the graph
    for (krate, krate_index, advisory) in &report.advisories {
        let diag = ctx.diag_for_advisory(
            krate,
            *krate_index,
            &advisory.metadata,
            Some(&advisory.versions),
            |index| {
                ignore_hits.as_mut_bitslice().set(index, true);
            },
        );

        sink.push(diag);
    }

    for (krate, status) in yanked {
        let Some(ind) = ctx.krates.nid_for_kid(&krate.id) else {
            log::warn!("failed to locate node id for '{krate}'");
            continue;
        };

        if let Some(e) = status {
            if ctx.cfg.yanked.value != LintLevel::Allow {
                sink.push(ctx.diag_for_index_failure(krate, ind, e));
            }
        } else {
            // Check to see if the user has added an ignore for the yanked
            // crate, eg. see https://github.com/EmbarkStudios/cargo-deny/issues/579
            // this should be extremely rare and very temporary as in most cases
            // a new semver compatible version of the yanked version is published
            // around the same time as a yank occurs
            if let Some(i) = ctx
                .cfg
                .ignore_yanked
                .iter()
                .position(|iy| crate::match_krate(krate, &iy.spec))
            {
                sink.push(ctx.diag_for_yanked_ignore(krate, i));
                ignore_yanked_hits.as_mut_bitslice().set(i, true);
            } else {
                sink.push(ctx.diag_for_yanked(krate, ind));
            }
        }
    }

    // Check for advisory identifiers that were set to be ignored, but
    // are not actually in any database.
    for ignored in &ctx.cfg.ignore {
        if !advisory_dbs.has_advisory(&ignored.id.value) {
            sink.push(ctx.diag_for_unknown_advisory(ignored));
        }
    }

    // Check for advisory identifiers that were set to be ignored, but
    // were not actually encountered, for cases where a crate, or specific
    // version of that crate, has been removed or replaced and the advisory
    // no longer applies to it, so that users can cleanup their configuration
    for ignore in ignore_hits
        .into_iter()
        .zip(ctx.cfg.ignore.iter())
        .filter_map(|(hit, ignore)| if !hit { Some(ignore) } else { None })
    {
        sink.push(ctx.diag_for_advisory_not_encountered(ignore));
    }

    for ignore in ignore_yanked_hits
        .into_iter()
        .zip(ctx.cfg.ignore_yanked.iter())
        .filter_map(|(hit, ignore)| if !hit { Some(ignore) } else { None })
    {
        sink.push(ctx.diag_for_ignored_yanked_not_encountered(ignore));
    }

    if let Some(mut reporter) = audit_compatible_reporter {
        for ser_report in report.serialized_reports {
            reporter.report(ser_report);
        }
    }
}