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