1use crate::pkg::{config, db, local, types};
2use anyhow::Result;
3use colored::*;
4use comfy_table::{Attribute, Cell, ContentArrangement, Table, presets::UTF8_FULL};
5use semver::{Version, VersionReq};
6
7pub fn run(all: bool, registry_filter: Option<String>, repo_filter: Option<String>) -> Result<()> {
8 if !all {
9 println!(
10 "{} Auditing installed packages for vulnerabilities...",
11 "::".bold().blue()
12 );
13 } else {
14 println!(
15 "{} Listing all known vulnerabilities...",
16 "::".bold().blue()
17 );
18 }
19
20 let config = config::read_config()?;
21 let mut registries = Vec::new();
22 if let Some(reg) = registry_filter {
23 registries.push(reg);
24 } else {
25 if let Some(default) = &config.default_registry {
26 registries.push(default.handle.clone());
27 }
28 for reg in &config.added_registries {
29 registries.push(reg.handle.clone());
30 }
31 }
32
33 let mut all_advisories = Vec::new();
34 for handle in registries {
35 if let Ok(advisories) = db::list_all_advisories(&handle) {
36 for (adv, repo) in advisories {
37 all_advisories.push((adv, repo, handle.clone()));
38 }
39 }
40 }
41
42 if let Some(rf) = &repo_filter {
43 all_advisories.retain(|(_, repo, _)| {
44 if rf.contains('/') {
45 repo == rf
46 } else {
47 repo.split('/').any(|part| part == rf)
48 }
49 });
50 }
51
52 if all_advisories.is_empty() {
53 println!(
54 "\n{}",
55 "No vulnerabilities found matching your criteria.".green()
56 );
57 return Ok(());
58 }
59
60 if all {
61 print_advisories_table(all_advisories)?;
62 } else {
63 let installed = local::get_installed_packages()?;
64 let mut vulnerable_installed = Vec::new();
65
66 for manifest in installed {
67 for (adv, repo, reg) in &all_advisories {
68 let package_match = adv.package == manifest.name
69 && *repo == manifest.repo
70 && *reg == manifest.registry_handle;
71
72 let sub_package_match = match (&adv.sub_package, &manifest.sub_package) {
73 (Some(adv_sub), Some(man_sub)) => adv_sub == man_sub,
74 (None, _) => true,
75 (Some(_), None) => false,
76 };
77
78 if package_match
79 && sub_package_match
80 && let Ok(version) = Version::parse(&manifest.version)
81 && let Ok(req) = VersionReq::parse(&adv.affected_range)
82 && req.matches(&version)
83 {
84 vulnerable_installed.push((adv.clone(), manifest.clone()));
85 }
86 }
87 }
88
89 if vulnerable_installed.is_empty() {
90 println!(
91 "\n{}",
92 "No vulnerabilities found in installed packages.".green()
93 );
94 } else {
95 println!(
96 "\n{} Found {} vulnerabilities in installed packages:",
97 "Warning".red().bold(),
98 vulnerable_installed.len()
99 );
100 print_vulnerable_table(vulnerable_installed)?;
101 }
102 }
103
104 Ok(())
105}
106
107fn print_advisories_table(advisories: Vec<(types::Advisory, String, String)>) -> Result<()> {
108 let mut table = Table::new();
109 table
110 .load_preset(UTF8_FULL)
111 .set_content_arrangement(ContentArrangement::Dynamic)
112 .set_header(vec![
113 Cell::new("ID").add_attribute(Attribute::Bold),
114 Cell::new("Package").add_attribute(Attribute::Bold),
115 Cell::new("Severity").add_attribute(Attribute::Bold),
116 Cell::new("Affected").add_attribute(Attribute::Bold),
117 Cell::new("Fixed In").add_attribute(Attribute::Bold),
118 Cell::new("Summary").add_attribute(Attribute::Bold),
119 ]);
120
121 for (adv, _, _) in advisories {
122 let severity_cell = match adv.severity {
123 types::Severity::Low => Cell::new("Low").fg(comfy_table::Color::Blue),
124 types::Severity::Medium => Cell::new("Medium").fg(comfy_table::Color::Yellow),
125 types::Severity::High => Cell::new("High").fg(comfy_table::Color::Red),
126 types::Severity::Critical => Cell::new("Critical")
127 .fg(comfy_table::Color::Magenta)
128 .add_attribute(Attribute::Bold),
129 };
130
131 let package_display = if let Some(sub) = &adv.sub_package {
132 format!("{}:{}", adv.package, sub)
133 } else {
134 adv.package.clone()
135 };
136
137 table.add_row(vec![
138 Cell::new(adv.id).fg(comfy_table::Color::Cyan),
139 Cell::new(package_display),
140 severity_cell,
141 Cell::new(adv.affected_range),
142 Cell::new(adv.fixed_in.unwrap_or_else(|| "N/A".to_string()))
143 .fg(comfy_table::Color::Green),
144 Cell::new(adv.summary),
145 ]);
146 }
147
148 println!("{table}");
149 Ok(())
150}
151
152fn print_vulnerable_table(
153 vulnerable: Vec<(types::Advisory, types::InstallManifest)>,
154) -> Result<()> {
155 let mut table = Table::new();
156 table
157 .load_preset(UTF8_FULL)
158 .set_content_arrangement(ContentArrangement::Dynamic)
159 .set_header(vec![
160 Cell::new("Package").add_attribute(Attribute::Bold),
161 Cell::new("Installed").add_attribute(Attribute::Bold),
162 Cell::new("ID").add_attribute(Attribute::Bold),
163 Cell::new("Severity").add_attribute(Attribute::Bold),
164 Cell::new("Fixed In").add_attribute(Attribute::Bold),
165 Cell::new("Summary").add_attribute(Attribute::Bold),
166 ]);
167
168 for (adv, manifest) in vulnerable {
169 let severity_cell = match adv.severity {
170 types::Severity::Low => Cell::new("Low").fg(comfy_table::Color::Blue),
171 types::Severity::Medium => Cell::new("Medium").fg(comfy_table::Color::Yellow),
172 types::Severity::High => Cell::new("High").fg(comfy_table::Color::Red),
173 types::Severity::Critical => Cell::new("Critical")
174 .fg(comfy_table::Color::Magenta)
175 .add_attribute(Attribute::Bold),
176 };
177
178 let package_display = if let Some(sub) = &manifest.sub_package {
179 format!("{}:{}", manifest.name, sub)
180 } else {
181 manifest.name.clone()
182 };
183
184 table.add_row(vec![
185 Cell::new(package_display).fg(comfy_table::Color::Cyan),
186 Cell::new(manifest.version).fg(comfy_table::Color::Red),
187 Cell::new(adv.id).fg(comfy_table::Color::DarkGrey),
188 severity_cell,
189 Cell::new(adv.fixed_in.unwrap_or_else(|| "N/A".to_string()))
190 .fg(comfy_table::Color::Green),
191 Cell::new(adv.summary),
192 ]);
193 }
194
195 println!("{table}");
196 Ok(())
197}