Skip to content

Commit

Permalink
Add version = 2 (#611)
Browse files Browse the repository at this point in the history
This is a follow up to #606 that actually provides a way to remove the
deprecated fields and opt in to the new behavior until the fields are
removed and the new behavior becomes the only behavior.

Basically, `version = 2` can be added to the `[advisories]` and
`[licenses]`, which opts in to the new behavior, and means any of the
deprecated keys no longer impact the results of the checks.

The new behavior is as follows:

### `[advisories]`

- `vulnerability` - `deny`
- `unmaintained` - `deny`, old default = `warn`
- `unsound` - `deny`, old default = `warn`
- `notice` - `deny`, old default = `warn`
- `severity-threshold` - CVSS severity no longer considered

Resolves: #449

### `[licenses]`

#### `unlicensed`

New default of `deny`, old default was `warn`.

If a crate is unlicensed, a
[clarification](https://embarkstudios.github.io/cargo-deny/checks/licenses/cfg.html#the-clarify-field-optional)
can be used to assign a license based on one or more source files in the
package

#### `allow-osi-fsf-free`

Old default was `both`, the new default is `neither`, ie, it doesn't
matter if the license is osi and/or fsf free, only if it is in the allow
(or exception) list.

#### `copyleft`

Old default was `warn`, the new default is `deny, it only matters if the
license is allowed in the allow or exception list.

Resolves: #602
Resolves: #354

#### `default`

Provided the default for a license not otherwise listed, now all
licenses are `deny` unless explicitly in the allow or exception list.

#### `deny`

This list served no purpose, if the license is not in the allow or
exception list, it is denied.
  • Loading branch information
Jake-Shadle authored Feb 23, 2024
1 parent 55b0471 commit c5721db
Show file tree
Hide file tree
Showing 9 changed files with 208 additions and 206 deletions.
8 changes: 4 additions & 4 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,7 @@ time = { version = "0.3", default-features = false, features = [
"macros",
] }
# Deserialization of configuration files and crate manifests
toml-span = { version = "0.1.0", features = ["reporting"] }
toml-span = { version = "0.2", features = ["reporting"] }
# Small fast hash crate
twox-hash = { version = "1.5", default-features = false }
# Url parsing/manipulation
Expand All @@ -138,7 +138,7 @@ fs_extra = "1.3"
insta = { version = "1.21", features = ["json"] }
tame-index = { version = "0.9", features = ["local-builder"] }
time = { version = "0.3", features = ["serde"] }
toml-span = { version = "0.1.0", features = ["serde"] }
toml-span = { version = "0.2", features = ["serde"] }
# We use this for creating fake crate directories for crawling license files on disk
tempfile = "3.1.0"

Expand Down
13 changes: 3 additions & 10 deletions deny.toml
Original file line number Diff line number Diff line change
Expand Up @@ -11,14 +11,9 @@ targets = [
all-features = true

[advisories]
vulnerability = "deny"
unmaintained = "deny"
notice = "deny"
unsound = "deny"
version = 2
ignore = [
# rmp-serde used by askalono for the cache files, these are always utf-8 so
# the advisory is not relevant
"RUSTSEC-2022-0092",
{ id = "RUSTSEC-2022-0092", reason = "askalono always provides valid utf-8 files from a cache, this is not relevant" },
]

[bans]
Expand All @@ -44,9 +39,7 @@ unknown-registry = "deny"
unknown-git = "deny"

[licenses]
unlicensed = "deny"
allow-osi-fsf-free = "neither"
copyleft = "deny"
version = 2
# We want really high confidence when inferring licenses from text
confidence-threshold = 0.93
allow = [
Expand Down
84 changes: 43 additions & 41 deletions src/advisories/cfg.rs
Original file line number Diff line number Diff line change
Expand Up @@ -64,30 +64,34 @@ impl PartialEq for IgnoreId {

impl Eq for IgnoreId {}

pub struct Config {
/// Path to the root directory where advisory databases are stored (default: $CARGO_HOME/advisory-dbs)
pub db_path: Option<PathBuf>,
/// List of urls to git repositories of different advisory databases.
pub db_urls: Vec<Spanned<Url>>,
#[cfg_attr(test, derive(serde::Serialize))]
pub(crate) struct Deprecated {
/// How to handle crates that have a security vulnerability
pub vulnerability: LintLevel,
/// How to handle crates that have been marked as unmaintained in an advisory database
pub unmaintained: LintLevel,
/// How to handle crates that have been marked as unsound in an advisory database
pub unsound: LintLevel,
/// How to handle crates that have been yanked from eg crates.io
pub yanked: Spanned<LintLevel>,
/// How to handle crates that have been marked with a notice in the advisory database
pub notice: LintLevel,
/// Ignore advisories for the given IDs
ignore: Vec<Spanned<IgnoreId>>,
/// Ignore yanked crates
pub ignore_yanked: Vec<Spanned<PackageSpecOrExtended<Reason>>>,
/// CVSS Qualitative Severity Rating Scale threshold to alert at.
///
/// Vulnerabilities with explicit CVSS info which have a severity below
/// this threshold will be ignored.
pub severity_threshold: Option<advisory::Severity>,
}

pub struct Config {
/// Path to the root directory where advisory databases are stored (default: $CARGO_HOME/advisory-dbs)
pub db_path: Option<PathBuf>,
/// List of urls to git repositories of different advisory databases.
pub db_urls: Vec<Spanned<Url>>,
/// How to handle crates that have been yanked from eg crates.io
pub yanked: Spanned<LintLevel>,
/// Ignore advisories for the given IDs
ignore: Vec<Spanned<IgnoreId>>,
/// Ignore yanked crates
pub ignore_yanked: Vec<Spanned<PackageSpecOrExtended<Reason>>>,
/// Use the git executable to fetch advisory database rather than gitoxide
pub git_fetch_with_cli: Option<bool>,
/// If set to true, the local crates indices are not checked for yanked crates
Expand All @@ -100,7 +104,8 @@ pub struct Config {
/// use the '.' separator instead of ',' which is used by some locales and
/// supported in the RFC3339 format, but not by this implementation
pub maximum_db_staleness: Spanned<Duration>,
deprecated: Vec<Span>,
deprecated: Option<Deprecated>,
deprecated_spans: Vec<Span>,
}

impl Default for Config {
Expand All @@ -110,16 +115,12 @@ impl Default for Config {
db_urls: Vec::new(),
ignore: Vec::new(),
ignore_yanked: Vec::new(),
vulnerability: LintLevel::Deny,
unmaintained: LintLevel::Warn,
unsound: LintLevel::Warn,
yanked: Spanned::new(LintLevel::Warn),
notice: LintLevel::Warn,
severity_threshold: None,
git_fetch_with_cli: None,
disable_yank_checking: false,
maximum_db_staleness: Spanned::new(Duration::seconds_f64(NINETY_DAYS)),
deprecated: Vec::new(),
deprecated: None,
deprecated_spans: Vec::new(),
}
}
}
Expand All @@ -130,6 +131,8 @@ impl<'de> Deserialize<'de> for Config {
fn deserialize(value: &mut Value<'de>) -> Result<Self, toml_span::DeserError> {
let mut th = TableHelper::new(value)?;

let version = th.optional("version").unwrap_or(1);

let db_path = th.optional::<String>("db-path").map(PathBuf::from);
let db_urls = if let Some((_, mut urls)) = th.take("db-urls") {
let mut u = Vec::new();
Expand Down Expand Up @@ -158,15 +161,14 @@ impl<'de> Deserialize<'de> for Config {

let mut fdeps = Vec::new();

let vulnerability =
deprecated(&mut th, "vulnerability", &mut fdeps).unwrap_or(LintLevel::Deny);
let unmaintained =
deprecated(&mut th, "unmaintained", &mut fdeps).unwrap_or(LintLevel::Warn);
let unsound = deprecated(&mut th, "unsound", &mut fdeps).unwrap_or(LintLevel::Warn);
let vulnerability = deprecated(&mut th, "vulnerability", &mut fdeps);
let unmaintained = deprecated(&mut th, "unmaintained", &mut fdeps);
let unsound = deprecated(&mut th, "unsound", &mut fdeps);
let notice = deprecated(&mut th, "notice", &mut fdeps);

let yanked = th
.optional_s("yanked")
.unwrap_or(Spanned::new(LintLevel::Warn));
let notice = deprecated(&mut th, "notice", &mut fdeps).unwrap_or(LintLevel::Warn);
let (ignore, ignore_yanked) = if let Some((_, mut ignore)) = th.take("ignore") {
let mut u = Vec::new();
let mut y = Vec::new();
Expand Down Expand Up @@ -303,21 +305,29 @@ impl<'de> Deserialize<'de> for Config {
let maximum_db_staleness = maximum_db_staleness
.unwrap_or_else(|| Spanned::new(Duration::seconds_f64(NINETY_DAYS)));

let deprecated = if version <= 1 {
Some(Deprecated {
vulnerability: vulnerability.unwrap_or(LintLevel::Deny),
unmaintained: unmaintained.unwrap_or(LintLevel::Warn),
unsound: unsound.unwrap_or(LintLevel::Warn),
notice: notice.unwrap_or(LintLevel::Warn),
severity_threshold,
})
} else {
None
};

Ok(Self {
db_path,
db_urls,
vulnerability,
unmaintained,
unsound,
yanked,
notice,
ignore,
ignore_yanked,
severity_threshold,
git_fetch_with_cli,
disable_yank_checking,
maximum_db_staleness,
deprecated: fdeps,
deprecated,
deprecated_spans: fdeps,
})
}
}
Expand Down Expand Up @@ -349,7 +359,7 @@ impl crate::cfg::UnvalidatedConfig for Config {

// Output any deprecations, we'll remove the fields at the same time we
// remove all the logic they drive
for dep in self.deprecated {
for dep in self.deprecated_spans {
ctx.push(
Deprecated {
reason: DeprecationReason::WillBeRemoved(Some(
Expand All @@ -376,12 +386,8 @@ impl crate::cfg::UnvalidatedConfig for Config {
file_id: ctx.cfg_id,
})
.collect(),
vulnerability: self.vulnerability,
unmaintained: self.unmaintained,
unsound: self.unsound,
deprecated: self.deprecated,
yanked: self.yanked,
notice: self.notice,
severity_threshold: self.severity_threshold,
git_fetch_with_cli: self.git_fetch_with_cli.unwrap_or_default(),
disable_yank_checking: self.disable_yank_checking,
maximum_db_staleness: self.maximum_db_staleness,
Expand All @@ -396,12 +402,8 @@ pub struct ValidConfig {
pub db_urls: Vec<Spanned<Url>>,
pub(crate) ignore: Vec<IgnoreId>,
pub(crate) ignore_yanked: Vec<crate::bans::SpecAndReason>,
pub vulnerability: LintLevel,
pub unmaintained: LintLevel,
pub unsound: LintLevel,
pub(crate) deprecated: Option<Deprecated>,
pub yanked: Spanned<LintLevel>,
pub notice: LintLevel,
pub severity_threshold: Option<advisory::Severity>,
pub git_fetch_with_cli: bool,
pub disable_yank_checking: bool,
pub maximum_db_staleness: Spanned<Duration>,
Expand Down
57 changes: 28 additions & 29 deletions src/advisories/diags.rs
Original file line number Diff line number Diff line change
Expand Up @@ -89,26 +89,18 @@ impl<'a> crate::CheckCtx<'a, super::cfg::ValidConfig> {
let mut pack = Pack::with_kid(Check::Advisories, krate.id.clone());

let (severity, ty) = {
let (lint_level, ty) = match &advisory.informational {
// Everything that isn't an informational advisory is a vulnerability
None => (self.cfg.vulnerability, AdvisoryType::Vulnerability),
Some(info) => match info {
// Security notices for a crate which are published on https://rustsec.org
// but don't represent a vulnerability in a crate itself.
Informational::Notice => (self.cfg.notice, AdvisoryType::Notice),
let adv_ty = advisory.informational.as_ref().map_or(AdvisoryType::Vulnerability, |info| {
match info {
// Crate is unmaintained / abandoned
Informational::Unmaintained => {
(self.cfg.unmaintained, AdvisoryType::Unmaintained)
Informational::Unmaintained => AdvisoryType::Unmaintained,
Informational::Unsound => AdvisoryType::Unsound,
Informational::Notice => AdvisoryType::Notice,
Informational::Other(other) => {
unreachable!("rustsec only returns Informational::Other({other}) advisories if we ask, and there are none at the moment to ask for");
}
Informational::Unsound => (self.cfg.unsound, AdvisoryType::Unsound),
// Other types of informational advisories: left open-ended to add
// more of them in the future.
Informational::Other(_) => {
unreachable!("rustsec only returns these if we ask, and there are none at the moment to ask for");
}
_ => unreachable!("unknown advisory type encountered"),
},
};
_ => unreachable!("non_exhaustive enums are the worst"),
}
});

// Ok, we found a crate whose version lies within the range of an
// advisory, but the user might have decided to ignore it
Expand All @@ -132,22 +124,29 @@ impl<'a> crate::CheckCtx<'a, super::cfg::ValidConfig> {
);

LintLevel::Allow
} else if let Some(severity_threshold) = self.cfg.severity_threshold {
if let Some(advisory_severity) = advisory.cvss.as_ref().map(|cvss| cvss.severity())
{
if advisory_severity < severity_threshold {
LintLevel::Allow
} else {
lint_level
} else if let Some(deprecated) = &self.cfg.deprecated {
'll: {
if let (Some(st), Some(sev)) = (
deprecated.severity_threshold,
advisory.cvss.as_ref().map(|c| c.severity()),
) {
if sev < st {
break 'll LintLevel::Allow;
}
}

match adv_ty {
AdvisoryType::Vulnerability => deprecated.vulnerability,
AdvisoryType::Unmaintained => deprecated.unmaintained,
AdvisoryType::Unsound => deprecated.unsound,
AdvisoryType::Notice => deprecated.notice,
}
} else {
lint_level
}
} else {
lint_level
LintLevel::Deny
};

(lint_level.into(), ty)
(lint_level.into(), adv_ty)
};

let mut notes = get_notes_from_advisory(advisory);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,12 +32,14 @@ expression: validated
"use-instead": null
}
],
"vulnerability": "deny",
"unmaintained": "warn",
"unsound": "warn",
"deprecated": {
"vulnerability": "deny",
"unmaintained": "warn",
"unsound": "warn",
"notice": "warn",
"severity_threshold": "medium"
},
"yanked": "warn",
"notice": "warn",
"severity_threshold": "medium",
"git_fetch_with_cli": false,
"disable_yank_checking": false,
"maximum_db_staleness": [
Expand Down
Loading

0 comments on commit c5721db

Please sign in to comment.