mirror of
https://github.com/deps-rs/deps.rs.git
synced 2024-11-21 18:06:30 +00:00
Display rustsec information on page (#96)
* chore: Bump rustsec version * feat: display RustSec CVEs at the bottom This closes #75. * fix: Reduce complexity and remove duplicate advisories
This commit is contained in:
parent
3e77c30ada
commit
7ebffe019f
5 changed files with 201 additions and 28 deletions
67
Cargo.lock
generated
67
Cargo.lock
generated
|
@ -142,7 +142,6 @@ dependencies = [
|
|||
"libc",
|
||||
"num-integer",
|
||||
"num-traits",
|
||||
"serde",
|
||||
"time",
|
||||
"winapi",
|
||||
]
|
||||
|
@ -417,6 +416,15 @@ dependencies = [
|
|||
"slab",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "getopts"
|
||||
version = "0.2.21"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "14dbbfd5c71d70241ecf9e6f13737f7b5ce823821063188d7e46c41d371eebd5"
|
||||
dependencies = [
|
||||
"unicode-width",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "getrandom"
|
||||
version = "0.1.15"
|
||||
|
@ -535,6 +543,22 @@ version = "0.3.2"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "494b4d60369511e7dea41cf646832512a94e542f68bb9c49e54518e0f468eb47"
|
||||
|
||||
[[package]]
|
||||
name = "humantime"
|
||||
version = "2.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4"
|
||||
|
||||
[[package]]
|
||||
name = "humantime-serde"
|
||||
version = "1.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ac34a56cfd4acddb469cc7fff187ed5ac36f498ba085caf8bbc725e3ff474058"
|
||||
dependencies = [
|
||||
"humantime",
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hyper"
|
||||
version = "0.14.2"
|
||||
|
@ -1047,6 +1071,18 @@ dependencies = [
|
|||
"unicode-xid",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pulldown-cmark"
|
||||
version = "0.8.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ffade02495f22453cd593159ea2f59827aae7f53fa8323f756799b670881dcf8"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"getopts",
|
||||
"memchr",
|
||||
"unicase",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "quote"
|
||||
version = "1.0.7"
|
||||
|
@ -1187,23 +1223,25 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "rustsec"
|
||||
version = "0.22.2"
|
||||
version = "0.23.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e5982d0d4f57176e3e8d62452a5d6dab98906ee5ab4ad1cb63c0877f0a16ab0e"
|
||||
checksum = "7989ff58001a2be1c17945e53d32fcad5cf33c638a4b1239d6e1c9aff7a5d2f8"
|
||||
dependencies = [
|
||||
"cargo-lock",
|
||||
"chrono",
|
||||
"crates-index 0.16.2",
|
||||
"cvss",
|
||||
"fs-err",
|
||||
"git2",
|
||||
"home",
|
||||
"humantime",
|
||||
"humantime-serde",
|
||||
"platforms",
|
||||
"semver 0.11.0",
|
||||
"serde",
|
||||
"smol_str",
|
||||
"thiserror",
|
||||
"toml",
|
||||
"url",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -1377,6 +1415,7 @@ dependencies = [
|
|||
"maud",
|
||||
"once_cell",
|
||||
"pin-project 1.0.2",
|
||||
"pulldown-cmark",
|
||||
"relative-path",
|
||||
"reqwest",
|
||||
"route-recognizer",
|
||||
|
@ -1456,9 +1495,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "smol_str"
|
||||
version = "0.1.16"
|
||||
version = "0.1.17"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2f7909a1d8bc166a862124d84fdc11bda0ea4ed3157ccca662296919c2972db1"
|
||||
checksum = "6ca0f7ce3a29234210f0f4f0b56f8be2e722488b95cb522077943212da3b32eb"
|
||||
dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
@ -1706,6 +1745,15 @@ version = "0.1.3"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "56dee185309b50d1f11bfedef0fe6d036842e3fb77413abef29f8f8d1c5d4c1c"
|
||||
|
||||
[[package]]
|
||||
name = "unicase"
|
||||
version = "2.6.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "50f37be617794602aabbeee0be4f259dc1778fabe05e2d67ee8f79326d5cb4f6"
|
||||
dependencies = [
|
||||
"version_check",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "unicode-bidi"
|
||||
version = "0.3.4"
|
||||
|
@ -1724,6 +1772,12 @@ dependencies = [
|
|||
"tinyvec",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "unicode-width"
|
||||
version = "0.1.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9337591893a19b88d8d87f2cec1e73fad5cdfd10e5a6f349f498ad6ea2ffb1e3"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-xid"
|
||||
version = "0.2.1"
|
||||
|
@ -1740,6 +1794,7 @@ dependencies = [
|
|||
"idna",
|
||||
"matches",
|
||||
"percent-encoding",
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
|
@ -21,11 +21,12 @@ hyper = { version = "0.14", features = ["full"] }
|
|||
indexmap = { version = "1", features = ["serde-1"] }
|
||||
lru_time_cache = "0.11.1"
|
||||
maud = "0.22.1"
|
||||
pulldown-cmark = "0.8"
|
||||
once_cell = "1"
|
||||
pin-project = "1"
|
||||
relative-path = { version = "1.3", features = ["serde"] }
|
||||
route-recognizer = "0.3"
|
||||
rustsec = "0.22"
|
||||
rustsec = "0.23"
|
||||
crates-index = "0.15" # held back by semver 0.11
|
||||
semver = { version = "0.10", features = ["serde"] } # held at 0.10 due to #63
|
||||
reqwest = { version = "0.11", features = ["json"] }
|
||||
|
|
|
@ -42,11 +42,12 @@ impl DependencyAnalyzer {
|
|||
let version: cargo_lock::Version = ver.to_string().parse().unwrap();
|
||||
let query = database::Query::new().package_version(name, version);
|
||||
|
||||
if !advisory_db
|
||||
.map(|db| db.query(&query).is_empty())
|
||||
.unwrap_or(true)
|
||||
{
|
||||
dep.insecure = true;
|
||||
if let Some(db) = advisory_db {
|
||||
let vulnerabilities = db.query(&query);
|
||||
if !vulnerabilities.is_empty() {
|
||||
dep.vulnerabilities =
|
||||
vulnerabilities.into_iter().map(|v| v.to_owned()).collect();
|
||||
}
|
||||
}
|
||||
}
|
||||
if !ver.is_prerelease() {
|
||||
|
|
|
@ -3,6 +3,7 @@ use std::{borrow::Borrow, str::FromStr};
|
|||
use anyhow::{anyhow, Error};
|
||||
use indexmap::IndexMap;
|
||||
use relative_path::RelativePathBuf;
|
||||
use rustsec::Advisory;
|
||||
use semver::{Version, VersionReq};
|
||||
|
||||
#[derive(Clone, Debug, Hash, PartialEq, Eq)]
|
||||
|
@ -89,7 +90,7 @@ pub struct AnalyzedDependency {
|
|||
pub required: VersionReq,
|
||||
pub latest_that_matches: Option<Version>,
|
||||
pub latest: Option<Version>,
|
||||
pub insecure: bool,
|
||||
pub vulnerabilities: Vec<Advisory>,
|
||||
}
|
||||
|
||||
impl AnalyzedDependency {
|
||||
|
@ -98,10 +99,14 @@ impl AnalyzedDependency {
|
|||
required,
|
||||
latest_that_matches: None,
|
||||
latest: None,
|
||||
insecure: false,
|
||||
vulnerabilities: Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_insecure(&self) -> bool {
|
||||
!self.vulnerabilities.is_empty()
|
||||
}
|
||||
|
||||
pub fn is_outdated(&self) -> bool {
|
||||
self.latest > self.latest_that_matches
|
||||
}
|
||||
|
@ -181,8 +186,16 @@ impl AnalyzedDependencies {
|
|||
|
||||
/// Returns the number of insecure main and build dependencies
|
||||
pub fn count_insecure(&self) -> usize {
|
||||
let main_insecure = self.main.iter().filter(|&(_, dep)| dep.insecure).count();
|
||||
let build_insecure = self.build.iter().filter(|&(_, dep)| dep.insecure).count();
|
||||
let main_insecure = self
|
||||
.main
|
||||
.iter()
|
||||
.filter(|&(_, dep)| dep.is_insecure())
|
||||
.count();
|
||||
let build_insecure = self
|
||||
.build
|
||||
.iter()
|
||||
.filter(|&(_, dep)| dep.is_insecure())
|
||||
.count();
|
||||
main_insecure + build_insecure
|
||||
}
|
||||
|
||||
|
@ -203,14 +216,17 @@ impl AnalyzedDependencies {
|
|||
|
||||
/// Counts the number of insecure `dev-dependencies`
|
||||
pub fn count_dev_insecure(&self) -> usize {
|
||||
self.dev.iter().filter(|&(_, dep)| dep.insecure).count()
|
||||
self.dev
|
||||
.iter()
|
||||
.filter(|&(_, dep)| dep.is_insecure())
|
||||
.count()
|
||||
}
|
||||
|
||||
/// Returns `true` if any dev-dependencies are either insecure or outdated.
|
||||
pub fn any_dev_issues(&self) -> bool {
|
||||
self.dev
|
||||
.iter()
|
||||
.any(|(_, dep)| dep.is_outdated() || dep.insecure)
|
||||
.any(|(_, dep)| dep.is_outdated() || dep.is_insecure())
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
use hyper::{Body, Response};
|
||||
use indexmap::IndexMap;
|
||||
use maud::{html, Markup};
|
||||
use maud::{html, Markup, PreEscaped};
|
||||
use pulldown_cmark::{html, Parser};
|
||||
use rustsec::advisory::Advisory;
|
||||
use semver::Version;
|
||||
|
||||
use crate::engine::AnalyzeDependenciesOutcome;
|
||||
|
@ -17,7 +19,7 @@ fn get_crates_version_url(name: impl AsRef<str>, version: &Version) -> String {
|
|||
format!("https://crates.io/crates/{}/{}", name.as_ref(), version)
|
||||
}
|
||||
|
||||
fn dependency_tables(crate_name: CrateName, deps: AnalyzedDependencies) -> Markup {
|
||||
fn dependency_tables(crate_name: &CrateName, deps: &AnalyzedDependencies) -> Markup {
|
||||
html! {
|
||||
h2 class="title is-3" {
|
||||
"Crate "
|
||||
|
@ -29,22 +31,22 @@ fn dependency_tables(crate_name: CrateName, deps: AnalyzedDependencies) -> Marku
|
|||
}
|
||||
|
||||
@if !deps.main.is_empty() {
|
||||
(dependency_table("Dependencies", deps.main))
|
||||
(dependency_table("Dependencies", &deps.main))
|
||||
}
|
||||
|
||||
@if !deps.dev.is_empty() {
|
||||
(dependency_table("Dev dependencies", deps.dev))
|
||||
(dependency_table("Dev dependencies", &deps.dev))
|
||||
}
|
||||
|
||||
@if !deps.build.is_empty() {
|
||||
(dependency_table("Build dependencies", deps.build))
|
||||
(dependency_table("Build dependencies", &deps.build))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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_insecure = deps.iter().filter(|&(_, dep)| dep.insecure).count();
|
||||
let count_insecure = deps.iter().filter(|&(_, dep)| dep.is_insecure()).count();
|
||||
let count_outdated = deps.iter().filter(|&(_, dep)| dep.is_outdated()).count();
|
||||
|
||||
html! {
|
||||
|
@ -86,7 +88,7 @@ fn dependency_table(title: &str, deps: IndexMap<CrateName, AnalyzedDependency>)
|
|||
}
|
||||
}
|
||||
td class="has-text-right" {
|
||||
@if dep.insecure {
|
||||
@if dep.is_insecure() {
|
||||
span class="tag is-danger" { "insecure" }
|
||||
} @else if dep.is_outdated() {
|
||||
span class="tag is-warning" { "out of date" }
|
||||
|
@ -147,6 +149,92 @@ fn render_dev_dependency_box(outcome: &AnalyzeDependenciesOutcome) -> Markup {
|
|||
}
|
||||
}
|
||||
|
||||
fn build_rustsec_link(advisory: &Advisory) -> String {
|
||||
format!(
|
||||
"https://rustsec.org/advisories/{}.html",
|
||||
advisory.id().as_str()
|
||||
)
|
||||
}
|
||||
|
||||
fn render_markdown(description: &str) -> Markup {
|
||||
let mut rendered = String::new();
|
||||
html::push_html(&mut rendered, Parser::new(description));
|
||||
PreEscaped(rendered)
|
||||
}
|
||||
|
||||
/// Renders a list of all security vulnerabilities affecting the repository
|
||||
fn vulnerability_list(analysis_outcome: &AnalyzeDependenciesOutcome) -> Markup {
|
||||
let mut vulnerabilities = Vec::new();
|
||||
for (_, analyzed_crate) in &analysis_outcome.crates {
|
||||
vulnerabilities.extend(
|
||||
&mut analyzed_crate
|
||||
.main
|
||||
.iter()
|
||||
.filter(|&(_, dep)| dep.is_insecure())
|
||||
.map(|(_, dep)| &dep.vulnerabilities),
|
||||
);
|
||||
vulnerabilities.extend(
|
||||
&mut analyzed_crate
|
||||
.dev
|
||||
.iter()
|
||||
.filter(|&(_, dep)| dep.is_insecure())
|
||||
.map(|(_, dep)| &dep.vulnerabilities),
|
||||
);
|
||||
vulnerabilities.extend(
|
||||
&mut analyzed_crate
|
||||
.build
|
||||
.iter()
|
||||
.filter(|&(_, dep)| dep.is_insecure())
|
||||
.map(|(_, dep)| &dep.vulnerabilities),
|
||||
);
|
||||
}
|
||||
|
||||
// flatten Vec<Vec<&Advisory>> -> Vec<&Advisory>
|
||||
let mut vulnerabilities: Vec<&Advisory> = vulnerabilities.into_iter().flatten().collect();
|
||||
vulnerabilities.sort_unstable_by_key(|&v| v.id());
|
||||
vulnerabilities.dedup();
|
||||
|
||||
html! {
|
||||
h3 class="title is-3" id="vulnerabilities" { "Security Vulnerabilities" }
|
||||
|
||||
@for vuln in vulnerabilities {
|
||||
div class="box" {
|
||||
h3 class="title is-4" { code { (vuln.metadata.package.as_str()) } ": " (vuln.title()) }
|
||||
p class="subtitle is-5" style="margin-top: -0.5rem;" { a href=(build_rustsec_link(vuln)) { (vuln.id()) } }
|
||||
|
||||
article { (render_markdown(vuln.description())) }
|
||||
|
||||
nav class="level" style="margin-top: 1rem;" {
|
||||
div class="level-item has-text-centered" {
|
||||
div {
|
||||
p class="heading" { "Unaffected" }
|
||||
@if vuln.versions.unaffected.is_empty() {
|
||||
p class="is-grey" { "None"}
|
||||
} @else {
|
||||
@for item in &vuln.versions.unaffected {
|
||||
p { code { (item) } }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
div class="level-item has-text-centered" {
|
||||
div {
|
||||
p class="heading" { "Patched" }
|
||||
@if vuln.versions.unaffected.is_empty() {
|
||||
p class="has-text-grey" { "None"}
|
||||
} @else {
|
||||
@for item in &vuln.versions.patched {
|
||||
p { code { (item) } }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn render_failure(subject_path: SubjectPath) -> Markup {
|
||||
html! {
|
||||
section class="hero is-light" {
|
||||
|
@ -220,12 +308,24 @@ fn render_success(
|
|||
}
|
||||
section class="section" {
|
||||
div class="container" {
|
||||
@if analysis_outcome.any_dev_issues() {
|
||||
@if analysis_outcome.any_insecure() {
|
||||
div class="notification is-warning" {
|
||||
p { "This project contains "
|
||||
b { "known security vulnerabilities" }
|
||||
". Find detailed information at the "
|
||||
a href="#vulnerabilities" { "bottom"} "."
|
||||
}
|
||||
}
|
||||
} @else if analysis_outcome.any_dev_issues() {
|
||||
(render_dev_dependency_box(&analysis_outcome))
|
||||
}
|
||||
@for (crate_name, deps) in analysis_outcome.crates {
|
||||
@for (crate_name, deps) in &analysis_outcome.crates {
|
||||
(dependency_tables(crate_name, deps))
|
||||
}
|
||||
|
||||
@if analysis_outcome.any_insecure() {
|
||||
(vulnerability_list(&analysis_outcome))
|
||||
}
|
||||
}
|
||||
}
|
||||
(super::render_footer(Some(analysis_outcome.duration)))
|
||||
|
|
Loading…
Reference in a new issue