Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 12 additions & 3 deletions crates/uv-audit/src/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ pub struct VulnerabilityID(SmallString);

impl VulnerabilityID {
/// Create a new vulnerability ID from a string.
pub(crate) fn new(id: impl Into<SmallString>) -> Self {
pub fn new(id: impl Into<SmallString>) -> Self {
Self(id.into())
}

Expand Down Expand Up @@ -120,12 +120,21 @@ impl Vulnerability {
}
}

/// Return an iterator over all identifiers for this vulnerability, including the primary ID and all aliases.
fn ids(&self) -> impl Iterator<Item = &VulnerabilityID> {
std::iter::once(&self.id).chain(self.aliases.iter())
}

/// Returns `true` if any of this vulnerability's identifiers (primary ID or aliases) match the given ID.
pub fn matches(&self, id: &VulnerabilityID) -> bool {
self.ids().any(|own_id| own_id == id)
}

/// Pick the subjectively "best" identifier for this vulnerability.
/// For our purposes we prefer PYSEC IDs, then GHSA, then CVE, then whatever
/// primary ID the vulnerability came with.
pub fn best_id(&self) -> &VulnerabilityID {
std::iter::once(&self.id)
.chain(self.aliases.iter())
self.ids()
.find(|id| {
id.as_str().starts_with("PYSEC-")
|| id.as_str().starts_with("GHSA-")
Expand Down
19 changes: 19 additions & 0 deletions crates/uv-cli/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5230,6 +5230,25 @@ pub struct AuditArgs {
#[arg(long)]
pub python_platform: Option<TargetTriple>,

/// Ignore a vulnerability by ID.
///
/// Vulnerabilities matching any of the provided IDs (including aliases) will be excluded from
/// the audit results.
///
/// May be provided multiple times.
#[arg(long)]
pub ignore: Vec<String>,

/// Ignore a vulnerability by ID, but only while no fix is available.
///
/// Vulnerabilities matching any of the provided IDs (including aliases) will be excluded from
/// the audit results as long as they have no known fix versions. Once a fix version becomes
/// available, the vulnerability will be reported again.
///
/// May be provided multiple times.
#[arg(long)]
pub ignore_until_fixed: Vec<String>,

/// The service format to use for vulnerability lookups.
///
/// Each service format has a default URL, which can be
Expand Down
11 changes: 10 additions & 1 deletion crates/uv-settings/src/combine.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ use uv_torch::TorchMode;
use uv_workspace::pyproject::ExtraBuildDependencies;
use uv_workspace::pyproject_mut::AddBoundsKind;

use crate::{FilesystemOptions, Options, PipOptions};
use crate::{AuditOptions, FilesystemOptions, Options, PipOptions};

pub trait Combine {
/// Combine two values, preferring the values in `self`.
Expand Down Expand Up @@ -72,6 +72,15 @@ impl Combine for Option<PipOptions> {
}
}

impl Combine for Option<AuditOptions> {
fn combine(self, other: Self) -> Self {
match (self, other) {
(Some(a), Some(b)) => Some(a.combine(b)),
(a, b) => a.or(b),
}
}
}

macro_rules! impl_combine_or {
($name:ident) => {
impl Combine for Option<$name> {
Expand Down
2 changes: 2 additions & 0 deletions crates/uv-settings/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -222,6 +222,7 @@ fn validate_uv_toml(path: &Path, options: &Options) -> Result<(), Error> {
install_mirrors: _,
publish: _,
add: _,
audit: _,
pip: _,
cache_keys: _,
override_dependencies: _,
Expand Down Expand Up @@ -370,6 +371,7 @@ fn warn_uv_toml_masked_fields(options: &Options) {
check_url,
},
add: AddOptions { add_bounds },
audit: _,
pip,
cache_keys,
override_dependencies,
Expand Down
38 changes: 38 additions & 0 deletions crates/uv-settings/src/settings.rs
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,9 @@ pub struct Options {
#[serde(flatten)]
pub add: AddOptions,

#[option_group]
pub audit: Option<AuditOptions>,

#[option_group]
pub pip: Option<PipOptions>,

Expand Down Expand Up @@ -2265,6 +2268,7 @@ pub struct OptionsWire {
// add: AddOptions
add_bounds: Option<AddBoundsKind>,

audit: Option<AuditOptions>,
pip: Option<PipOptions>,
cache_keys: Option<Vec<CacheKey>>,

Expand Down Expand Up @@ -2347,6 +2351,7 @@ impl From<OptionsWire> for Options {
no_binary,
no_binary_package,
torch_backend,
audit,
pip,
cache_keys,
override_dependencies,
Expand Down Expand Up @@ -2448,6 +2453,7 @@ impl From<OptionsWire> for Options {
check_url,
},
add: AddOptions { add_bounds: bounds },
audit,
workspace,
sources,
dev_dependencies,
Expand Down Expand Up @@ -2536,3 +2542,35 @@ pub struct AddOptions {
)]
pub add_bounds: Option<AddBoundsKind>,
}

#[derive(Debug, Clone, Default, PartialEq, Eq, Deserialize, CombineOptions, OptionsMetadata)]
#[serde(rename_all = "kebab-case")]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
pub struct AuditOptions {
/// A list of vulnerability IDs to ignore during auditing.
///
/// Vulnerabilities matching any of the provided IDs (including aliases) will be excluded from
/// the audit results.
#[option(
default = "[]",
value_type = "list[str]",
example = r#"
ignore = ["PYSEC-2022-43017", "GHSA-5239-wwwm-4pmq"]
"#
)]
pub ignore: Option<Vec<String>>,

/// A list of vulnerability IDs to ignore during auditing, but only while no fix is available.
///
/// Vulnerabilities matching any of the provided IDs (including aliases) will be excluded from
/// the audit results as long as they have no known fix versions. Once a fix version becomes
/// available, the vulnerability will be reported again.
#[option(
default = "[]",
value_type = "list[str]",
example = r#"
ignore-until-fixed = ["PYSEC-2022-43017"]
"#
)]
pub ignore_until_fixed: Option<Vec<String>>,
}
25 changes: 24 additions & 1 deletion crates/uv/src/commands/project/audit.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ use crate::settings::{FrozenSource, LockCheck, ResolverSettings};
use anyhow::Result;
use tracing::trace;
use uv_audit::service::{VulnerabilityServiceFormat, osv};
use uv_audit::types::{Dependency, Finding};
use uv_audit::types::{Dependency, Finding, VulnerabilityID};
use uv_cache::Cache;
use uv_client::BaseClientBuilder;
use uv_configuration::{Concurrency, DependencyGroups, ExtrasSpecification, TargetTriple};
Expand Down Expand Up @@ -53,6 +53,8 @@ pub(crate) async fn audit(
preview: Preview,
service: VulnerabilityServiceFormat,
service_url: Option<String>,
ignore: Vec<VulnerabilityID>,
ignore_until_fixed: Vec<VulnerabilityID>,
) -> Result<ExitStatus> {
// Check if the audit feature is in preview
if !preview.is_enabled(PreviewFeature::Audit) {
Expand Down Expand Up @@ -215,6 +217,27 @@ pub(crate) async fn audit(

reporter.on_audit_complete();

// Filter out ignored vulnerabilities.
let all_findings: Vec<_> = all_findings
.into_iter()
.filter(|finding| match finding {
Finding::Vulnerability(vulnerability) => {
if ignore.iter().any(|id| vulnerability.matches(id)) {
return false;
}
if vulnerability.fix_versions.is_empty()
&& ignore_until_fixed
.iter()
.any(|id| vulnerability.matches(id))
{
return false;
}
true
}
Finding::ProjectStatus(_) => true,
})
.collect();

let display = AuditResults {
printer,
n_packages: auditable.len(),
Expand Down
2 changes: 2 additions & 0 deletions crates/uv/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2663,6 +2663,8 @@ async fn run_project(
globals.preview,
args.service_format,
args.service_url,
args.ignore,
args.ignore_until_fixed,
))
.await
}
Expand Down
23 changes: 23 additions & 0 deletions crates/uv/src/settings.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ use std::time::Duration;

use rustc_hash::FxHashSet;
use uv_audit::service::VulnerabilityServiceFormat;
use uv_audit::types::VulnerabilityID;

use crate::commands::{PythonUpgrade, PythonUpgradeSource};
use uv_auth::Service;
Expand Down Expand Up @@ -2550,6 +2551,8 @@ pub(crate) struct AuditSettings {
pub(crate) settings: ResolverSettings,
pub(crate) service_format: VulnerabilityServiceFormat,
pub(crate) service_url: Option<String>,
pub(crate) ignore: Vec<VulnerabilityID>,
pub(crate) ignore_until_fixed: Vec<VulnerabilityID>,
}

impl AuditSettings {
Expand All @@ -2573,6 +2576,8 @@ impl AuditSettings {
frozen,
build,
resolver,
ignore,
ignore_until_fixed,
service_format,
service_url,
} = args;
Expand All @@ -2582,6 +2587,11 @@ impl AuditSettings {
.map(|fs| fs.install_mirrors.clone())
.unwrap_or_default();

let filesystem_audit = filesystem
.as_ref()
.and_then(|fs| fs.audit.clone())
.unwrap_or_default();

let no_dev = no_dev || environment.no_dev.value == Some(true);

// Resolve flags from CLI and environment variables.
Expand Down Expand Up @@ -2621,6 +2631,19 @@ impl AuditSettings {
settings: ResolverSettings::combine(resolver_options(resolver, build), filesystem),
service_format,
service_url,
ignore: {
let config_ignore = filesystem_audit.ignore.unwrap_or_default();
let mut merged = ignore;
merged.extend(config_ignore);
merged.into_iter().map(VulnerabilityID::new).collect()
},
ignore_until_fixed: {
let config_ignore_until_fixed =
filesystem_audit.ignore_until_fixed.unwrap_or_default();
let mut merged = ignore_until_fixed;
merged.extend(config_ignore_until_fixed);
merged.into_iter().map(VulnerabilityID::new).collect()
},
}
}
}
Expand Down
Loading
Loading