mirror of
https://github.com/deps-rs/deps.rs.git
synced 2024-11-22 10:26:30 +00:00
Only query advisory database on latest matching version (#98)
* Add methods to check always insecure dependencies Unlike checks for `_insecure`, `always_insecure_ only accounts for vulnerabilities not patched in the latest version in the range * Update status renders to show "maybe insecure" - show always insecure dependencies as insecure, and remaining ones as "possibly insecure" - show warning sign on all dependencies with possible vulnerability - tweak security banner in case all insecure dependencies are "possibly insecure" * Update badge renderer to show "maybe insecure" - only show the red "inscure" if >=1 dependency is always insecure - show "possibly insecure" if all are up to date but might be vulnerable * Update status renderer - more complete counts per project * Format code * Extend banner to explain what "maybe insecure" means
This commit is contained in:
parent
50d81a7a79
commit
6cd7256ee8
4 changed files with 88 additions and 14 deletions
|
@ -108,6 +108,13 @@ impl AnalyzeDependenciesOutcome {
|
||||||
.any(|&(_, ref deps)| deps.count_insecure() > 0)
|
.any(|&(_, ref deps)| deps.count_insecure() > 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Checks if any always insecure main or build dependencies exist in the scanned crates
|
||||||
|
pub fn any_always_insecure(&self) -> bool {
|
||||||
|
self.crates
|
||||||
|
.iter()
|
||||||
|
.any(|&(_, ref deps)| deps.count_always_insecure() > 0)
|
||||||
|
}
|
||||||
|
|
||||||
/// Checks if any dev-dependencies in the scanned crates are either outdated or insecure
|
/// Checks if any dev-dependencies in the scanned crates are either outdated or insecure
|
||||||
pub fn any_dev_issues(&self) -> bool {
|
pub fn any_dev_issues(&self) -> bool {
|
||||||
self.crates
|
self.crates
|
||||||
|
|
|
@ -103,10 +103,27 @@ impl AnalyzedDependency {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Check whether this dependency has at least one known vulnerability
|
||||||
|
/// in any version in the required version range.
|
||||||
|
///
|
||||||
|
/// Note that the vulnerability may (or not) already be patched
|
||||||
|
/// in the latest version(s) in the range.
|
||||||
pub fn is_insecure(&self) -> bool {
|
pub fn is_insecure(&self) -> bool {
|
||||||
!self.vulnerabilities.is_empty()
|
!self.vulnerabilities.is_empty()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Check whether this dependency has at laest one known vulnerability
|
||||||
|
/// even when the latest version in the required range is used.
|
||||||
|
pub fn is_always_insecure(&self) -> bool {
|
||||||
|
if let Some(latest) = &self.latest {
|
||||||
|
self.vulnerabilities
|
||||||
|
.iter()
|
||||||
|
.any(|a| a.versions.is_vulnerable(latest))
|
||||||
|
} else {
|
||||||
|
self.is_insecure()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn is_outdated(&self) -> bool {
|
pub fn is_outdated(&self) -> bool {
|
||||||
self.latest > self.latest_that_matches
|
self.latest > self.latest_that_matches
|
||||||
}
|
}
|
||||||
|
@ -199,6 +216,23 @@ impl AnalyzedDependencies {
|
||||||
main_insecure + build_insecure
|
main_insecure + build_insecure
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns the number of main and build dependencies
|
||||||
|
/// which are vulnerable to security issues,
|
||||||
|
/// even they are updated to the latest version in the required range.
|
||||||
|
pub fn count_always_insecure(&self) -> usize {
|
||||||
|
let main_insecure = self
|
||||||
|
.main
|
||||||
|
.iter()
|
||||||
|
.filter(|&(_, dep)| dep.is_always_insecure())
|
||||||
|
.count();
|
||||||
|
let build_insecure = self
|
||||||
|
.build
|
||||||
|
.iter()
|
||||||
|
.filter(|&(_, dep)| dep.is_always_insecure())
|
||||||
|
.count();
|
||||||
|
main_insecure + build_insecure
|
||||||
|
}
|
||||||
|
|
||||||
/// Checks if any outdated main or build dependencies exist
|
/// Checks if any outdated main or build dependencies exist
|
||||||
pub fn any_outdated(&self) -> bool {
|
pub fn any_outdated(&self) -> bool {
|
||||||
let main_any_outdated = self.main.iter().any(|(_, dep)| dep.is_outdated());
|
let main_any_outdated = self.main.iter().any(|(_, dep)| dep.is_outdated());
|
||||||
|
|
|
@ -7,7 +7,7 @@ use crate::engine::AnalyzeDependenciesOutcome;
|
||||||
pub fn badge(analysis_outcome: Option<&AnalyzeDependenciesOutcome>) -> Badge {
|
pub fn badge(analysis_outcome: Option<&AnalyzeDependenciesOutcome>) -> Badge {
|
||||||
let opts = match analysis_outcome {
|
let opts = match analysis_outcome {
|
||||||
Some(outcome) => {
|
Some(outcome) => {
|
||||||
if outcome.any_insecure() {
|
if outcome.any_always_insecure() {
|
||||||
BadgeOptions {
|
BadgeOptions {
|
||||||
subject: "dependencies".into(),
|
subject: "dependencies".into(),
|
||||||
status: "insecure".into(),
|
status: "insecure".into(),
|
||||||
|
@ -23,11 +23,19 @@ pub fn badge(analysis_outcome: Option<&AnalyzeDependenciesOutcome>) -> Badge {
|
||||||
color: "#dfb317".into(),
|
color: "#dfb317".into(),
|
||||||
}
|
}
|
||||||
} else if total > 0 {
|
} else if total > 0 {
|
||||||
|
if outcome.any_insecure() {
|
||||||
|
BadgeOptions {
|
||||||
|
subject: "dependencies".into(),
|
||||||
|
status: "maybe insecure".into(),
|
||||||
|
color: "#8b1".into(),
|
||||||
|
}
|
||||||
|
} else {
|
||||||
BadgeOptions {
|
BadgeOptions {
|
||||||
subject: "dependencies".into(),
|
subject: "dependencies".into(),
|
||||||
status: "up to date".into(),
|
status: "up to date".into(),
|
||||||
color: "#4c1".into(),
|
color: "#4c1".into(),
|
||||||
}
|
}
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
BadgeOptions {
|
BadgeOptions {
|
||||||
subject: "dependencies".into(),
|
subject: "dependencies".into(),
|
||||||
|
|
|
@ -47,6 +47,10 @@ fn dependency_tables(crate_name: &CrateName, deps: &AnalyzedDependencies) -> Mar
|
||||||
|
|
||||||
fn dependency_table(title: &str, deps: &IndexMap<CrateName, AnalyzedDependency>) -> Markup {
|
fn dependency_table(title: &str, deps: &IndexMap<CrateName, AnalyzedDependency>) -> Markup {
|
||||||
let count_total = deps.len();
|
let count_total = deps.len();
|
||||||
|
let count_always_insecure = deps
|
||||||
|
.iter()
|
||||||
|
.filter(|&(_, dep)| dep.is_always_insecure())
|
||||||
|
.count();
|
||||||
let count_insecure = deps.iter().filter(|&(_, dep)| dep.is_insecure()).count();
|
let count_insecure = deps.iter().filter(|&(_, dep)| dep.is_insecure()).count();
|
||||||
let count_outdated = deps.iter().filter(|&(_, dep)| dep.is_outdated()).count();
|
let count_outdated = deps.iter().filter(|&(_, dep)| dep.is_outdated()).count();
|
||||||
|
|
||||||
|
@ -55,11 +59,15 @@ fn dependency_table(title: &str, deps: &IndexMap<CrateName, AnalyzedDependency>)
|
||||||
html! {
|
html! {
|
||||||
h3 class="title is-4" { (title) }
|
h3 class="title is-4" { (title) }
|
||||||
p class="subtitle is-5" {
|
p class="subtitle is-5" {
|
||||||
(match (count_outdated, count_insecure) {
|
(match (count_outdated, count_always_insecure, count_insecure - count_always_insecure) {
|
||||||
(0, 0) => format!("({} total, all up-to-date)", count_total),
|
(0, 0, 0) => format!("({} total, all up-to-date)", count_total),
|
||||||
(0, _) => format!("({} total, {} insecure)", count_total, count_insecure),
|
(0, 0, c) => format!("({} total, {} possibly insecure)", count_total, c),
|
||||||
(_, 0) => format!("({} total, {} outdated)", count_total, count_outdated),
|
(_, 0, 0) => format!("({} total, {} outdated)", count_total, count_outdated),
|
||||||
(_, _) => format!("({} total, {} outdated, {} insecure)", count_total, count_outdated, count_insecure),
|
(0, _, 0) => format!("({} total, {} insecure)", count_total, count_always_insecure),
|
||||||
|
(0, _, c) => format!("({} total, {} insecure, {} possibly insecure)", count_total, count_always_insecure, c),
|
||||||
|
(_, 0, c) => format!("({} total, {} outdated, {} possibly insecure)", count_total, count_outdated, c),
|
||||||
|
(_, _, 0) => format!("({} total, {} outdated, {} insecure)", count_total, count_outdated, count_always_insecure),
|
||||||
|
(_, _, c) => format!("({} total, {} outdated, {} insecure, {} possibly insecure)", count_total, count_outdated, count_always_insecure, c),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -81,6 +89,11 @@ fn dependency_table(title: &str, deps: &IndexMap<CrateName, AnalyzedDependency>)
|
||||||
}
|
}
|
||||||
{ "\u{00A0}" } // non-breaking space
|
{ "\u{00A0}" } // non-breaking space
|
||||||
a href=(dep.deps_rs_path(name.as_ref())) { (name.as_ref()) }
|
a href=(dep.deps_rs_path(name.as_ref())) { (name.as_ref()) }
|
||||||
|
|
||||||
|
@if dep.is_insecure() {
|
||||||
|
{ "\u{00A0}" } // non-breaking space
|
||||||
|
a href="#vulnerabilities" title="has known vulnerabilities" { "⚠️" }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
td class="has-text-right" { code { (dep.required.to_string()) } }
|
td class="has-text-right" { code { (dep.required.to_string()) } }
|
||||||
td class="has-text-right" {
|
td class="has-text-right" {
|
||||||
|
@ -91,10 +104,12 @@ fn dependency_table(title: &str, deps: &IndexMap<CrateName, AnalyzedDependency>)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
td class="has-text-right" {
|
td class="has-text-right" {
|
||||||
@if dep.is_insecure() {
|
@if dep.is_always_insecure() {
|
||||||
span class="tag is-danger" { "insecure" }
|
span class="tag is-danger" { "insecure" }
|
||||||
} @else if dep.is_outdated() {
|
} @else if dep.is_outdated() {
|
||||||
span class="tag is-warning" { "out of date" }
|
span class="tag is-warning" { "out of date" }
|
||||||
|
} @else if dep.is_insecure() {
|
||||||
|
span class="tag is-warning" { "maybe insecure" }
|
||||||
} @else {
|
} @else {
|
||||||
span class="tag is-success" { "up to date" }
|
span class="tag is-success" { "up to date" }
|
||||||
}
|
}
|
||||||
|
@ -288,9 +303,9 @@ fn render_success(
|
||||||
|
|
||||||
let status_data_uri = badge::badge(Some(&analysis_outcome)).to_svg_data_uri();
|
let status_data_uri = badge::badge(Some(&analysis_outcome)).to_svg_data_uri();
|
||||||
|
|
||||||
let hero_class = if analysis_outcome.any_insecure() {
|
let hero_class = if analysis_outcome.any_always_insecure() {
|
||||||
"is-danger"
|
"is-danger"
|
||||||
} else if analysis_outcome.any_outdated() {
|
} else if analysis_outcome.any_insecure() || analysis_outcome.any_outdated() {
|
||||||
"is-warning"
|
"is-warning"
|
||||||
} else {
|
} else {
|
||||||
"is-success"
|
"is-success"
|
||||||
|
@ -318,7 +333,7 @@ fn render_success(
|
||||||
}
|
}
|
||||||
section class="section" {
|
section class="section" {
|
||||||
div class="container" {
|
div class="container" {
|
||||||
@if analysis_outcome.any_insecure() {
|
@if analysis_outcome.any_always_insecure() {
|
||||||
div class="notification is-warning" {
|
div class="notification is-warning" {
|
||||||
p { "This project contains "
|
p { "This project contains "
|
||||||
b { "known security vulnerabilities" }
|
b { "known security vulnerabilities" }
|
||||||
|
@ -326,6 +341,16 @@ fn render_success(
|
||||||
a href="#vulnerabilities" { "bottom"} "."
|
a href="#vulnerabilities" { "bottom"} "."
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
} @else if analysis_outcome.any_insecure() {
|
||||||
|
div class="notification is-warning" {
|
||||||
|
p { "This project might be open to "
|
||||||
|
b { "known security vulnerabilities" }
|
||||||
|
", which can be prevented by tightening "
|
||||||
|
"the version range of affected dependencies. "
|
||||||
|
"Find detailed information at the "
|
||||||
|
a href="#vulnerabilities" { "bottom"} "."
|
||||||
|
}
|
||||||
|
}
|
||||||
} @else if analysis_outcome.any_dev_issues() {
|
} @else if analysis_outcome.any_dev_issues() {
|
||||||
(render_dev_dependency_box(&analysis_outcome))
|
(render_dev_dependency_box(&analysis_outcome))
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue