diff --git a/crates/uv-audit/src/types.rs b/crates/uv-audit/src/types.rs index 1bf6c3a1a34a2..2f0318b31757e 100644 --- a/crates/uv-audit/src/types.rs +++ b/crates/uv-audit/src/types.rs @@ -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) -> Self { + pub fn new(id: impl Into) -> Self { Self(id.into()) } @@ -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 { + 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-") diff --git a/crates/uv-cli/src/lib.rs b/crates/uv-cli/src/lib.rs index f4f9ad477ff7e..ab86c9e53bb3e 100644 --- a/crates/uv-cli/src/lib.rs +++ b/crates/uv-cli/src/lib.rs @@ -5230,6 +5230,25 @@ pub struct AuditArgs { #[arg(long)] pub python_platform: Option, + /// 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, + + /// 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, + /// The service format to use for vulnerability lookups. /// /// Each service format has a default URL, which can be diff --git a/crates/uv-settings/src/combine.rs b/crates/uv-settings/src/combine.rs index 90e91884b7b66..256d82a83df07 100644 --- a/crates/uv-settings/src/combine.rs +++ b/crates/uv-settings/src/combine.rs @@ -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`. @@ -72,6 +72,15 @@ impl Combine for Option { } } +impl Combine for Option { + 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> { diff --git a/crates/uv-settings/src/lib.rs b/crates/uv-settings/src/lib.rs index d7cf610be1470..f4a7e12a31f0d 100644 --- a/crates/uv-settings/src/lib.rs +++ b/crates/uv-settings/src/lib.rs @@ -222,6 +222,7 @@ fn validate_uv_toml(path: &Path, options: &Options) -> Result<(), Error> { install_mirrors: _, publish: _, add: _, + audit: _, pip: _, cache_keys: _, override_dependencies: _, @@ -370,6 +371,7 @@ fn warn_uv_toml_masked_fields(options: &Options) { check_url, }, add: AddOptions { add_bounds }, + audit: _, pip, cache_keys, override_dependencies, diff --git a/crates/uv-settings/src/settings.rs b/crates/uv-settings/src/settings.rs index d1f839a53a779..0a3c708c7af47 100644 --- a/crates/uv-settings/src/settings.rs +++ b/crates/uv-settings/src/settings.rs @@ -62,6 +62,9 @@ pub struct Options { #[serde(flatten)] pub add: AddOptions, + #[option_group] + pub audit: Option, + #[option_group] pub pip: Option, @@ -2265,6 +2268,7 @@ pub struct OptionsWire { // add: AddOptions add_bounds: Option, + audit: Option, pip: Option, cache_keys: Option>, @@ -2347,6 +2351,7 @@ impl From for Options { no_binary, no_binary_package, torch_backend, + audit, pip, cache_keys, override_dependencies, @@ -2448,6 +2453,7 @@ impl From for Options { check_url, }, add: AddOptions { add_bounds: bounds }, + audit, workspace, sources, dev_dependencies, @@ -2536,3 +2542,35 @@ pub struct AddOptions { )] pub add_bounds: Option, } + +#[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>, + + /// 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>, +} diff --git a/crates/uv/src/commands/project/audit.rs b/crates/uv/src/commands/project/audit.rs index d6f64e2e3daab..cff5fee96079f 100644 --- a/crates/uv/src/commands/project/audit.rs +++ b/crates/uv/src/commands/project/audit.rs @@ -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}; @@ -53,6 +53,8 @@ pub(crate) async fn audit( preview: Preview, service: VulnerabilityServiceFormat, service_url: Option, + ignore: Vec, + ignore_until_fixed: Vec, ) -> Result { // Check if the audit feature is in preview if !preview.is_enabled(PreviewFeature::Audit) { @@ -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(), diff --git a/crates/uv/src/lib.rs b/crates/uv/src/lib.rs index bc347fb68b1c0..8263f3b3afaf8 100644 --- a/crates/uv/src/lib.rs +++ b/crates/uv/src/lib.rs @@ -2663,6 +2663,8 @@ async fn run_project( globals.preview, args.service_format, args.service_url, + args.ignore, + args.ignore_until_fixed, )) .await } diff --git a/crates/uv/src/settings.rs b/crates/uv/src/settings.rs index a0bf551a382f1..e0230020f29e3 100644 --- a/crates/uv/src/settings.rs +++ b/crates/uv/src/settings.rs @@ -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; @@ -2550,6 +2551,8 @@ pub(crate) struct AuditSettings { pub(crate) settings: ResolverSettings, pub(crate) service_format: VulnerabilityServiceFormat, pub(crate) service_url: Option, + pub(crate) ignore: Vec, + pub(crate) ignore_until_fixed: Vec, } impl AuditSettings { @@ -2573,6 +2576,8 @@ impl AuditSettings { frozen, build, resolver, + ignore, + ignore_until_fixed, service_format, service_url, } = args; @@ -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. @@ -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() + }, } } } diff --git a/crates/uv/tests/it/audit.rs b/crates/uv/tests/it/audit.rs index 08d13d2153d0b..c81ad567631c4 100644 --- a/crates/uv/tests/it/audit.rs +++ b/crates/uv/tests/it/audit.rs @@ -626,3 +626,503 @@ async fn audit_dependency_groups() { Found no known vulnerabilities and no adverse project statuses in 1 package "); } + +/// `--ignore` excludes a vulnerability by its primary ID. +#[tokio::test] +async fn audit_ignore_by_id() { + let context = uv_test::test_context!("3.12"); + + let pyproject_toml = context.temp_dir.child("pyproject.toml"); + pyproject_toml + .write_str(indoc! {r#" + [project] + name = "project" + version = "0.1.0" + requires-python = ">=3.12" + dependencies = ["iniconfig==2.0.0"] + "#}) + .unwrap(); + + context.lock().assert().success(); + + let server = MockServer::start().await; + + Mock::given(method("POST")) + .and(path("/v1/querybatch")) + .respond_with(ResponseTemplate::new(200).set_body_json(json!({ + "results": [{"vulns": [{"id": "PYSEC-2023-0001"}]}] + }))) + .mount(&server) + .await; + + Mock::given(method("GET")) + .and(path("/v1/vulns/PYSEC-2023-0001")) + .respond_with(ResponseTemplate::new(200).set_body_json(json!({ + "id": "PYSEC-2023-0001", + "modified": "2026-01-01T00:00:00Z", + "summary": "A test vulnerability in iniconfig", + "affected": [{ + "ranges": [{ + "type": "ECOSYSTEM", + "events": [ + {"introduced": "0"}, + {"fixed": "2.1.0"} + ] + }] + }] + }))) + .mount(&server) + .await; + + // Without --ignore, the vulnerability is reported. + uv_snapshot!(context.filters(), context + .audit() + .arg("--frozen") + .arg("--preview") + .arg("--service-url") + .arg(server.uri()), @" + success: false + exit_code: 1 + ----- stdout ----- + + Vulnerabilities: + + iniconfig 2.0.0 has 1 known vulnerability: + + - PYSEC-2023-0001: A test vulnerability in iniconfig + + Fixed in: 2.1.0 + + Advisory information: https://osv.dev/vulnerability/PYSEC-2023-0001 + + + ----- stderr ----- + Found 1 known vulnerability and no adverse project statuses in 1 package + "); + + // With --ignore, the vulnerability is suppressed. + uv_snapshot!(context.filters(), context + .audit() + .arg("--frozen") + .arg("--preview") + .arg("--ignore") + .arg("PYSEC-2023-0001") + .arg("--service-url") + .arg(server.uri()), @" + success: true + exit_code: 0 + ----- stdout ----- + + ----- stderr ----- + Found no known vulnerabilities and no adverse project statuses in 1 package + "); +} + +/// `--ignore` matches against aliases, not just the primary ID. +#[tokio::test] +async fn audit_ignore_by_alias() { + let context = uv_test::test_context!("3.12"); + + let pyproject_toml = context.temp_dir.child("pyproject.toml"); + pyproject_toml + .write_str(indoc! {r#" + [project] + name = "project" + version = "0.1.0" + requires-python = ">=3.12" + dependencies = ["iniconfig==2.0.0"] + "#}) + .unwrap(); + + context.lock().assert().success(); + + let server = MockServer::start().await; + + Mock::given(method("POST")) + .and(path("/v1/querybatch")) + .respond_with(ResponseTemplate::new(200).set_body_json(json!({ + "results": [{"vulns": [{"id": "OSV-2023-0001"}]}] + }))) + .mount(&server) + .await; + + Mock::given(method("GET")) + .and(path("/v1/vulns/OSV-2023-0001")) + .respond_with(ResponseTemplate::new(200).set_body_json(json!({ + "id": "OSV-2023-0001", + "modified": "2026-01-01T00:00:00Z", + "summary": "A vulnerability with aliases", + "aliases": ["PYSEC-2023-0042", "CVE-2023-9999"] + }))) + .mount(&server) + .await; + + // Ignoring by alias (CVE-2023-9999) should suppress the vulnerability. + uv_snapshot!(context.filters(), context + .audit() + .arg("--frozen") + .arg("--preview") + .arg("--ignore") + .arg("CVE-2023-9999") + .arg("--service-url") + .arg(server.uri()), @" + success: true + exit_code: 0 + ----- stdout ----- + + ----- stderr ----- + Found no known vulnerabilities and no adverse project statuses in 1 package + "); +} + +/// `--ignore-until-fixed` suppresses a vulnerability only when no fix versions are available. +#[tokio::test] +async fn audit_ignore_until_fixed() { + let context = uv_test::test_context!("3.12"); + + let pyproject_toml = context.temp_dir.child("pyproject.toml"); + pyproject_toml + .write_str(indoc! {r#" + [project] + name = "project" + version = "0.1.0" + requires-python = ">=3.12" + dependencies = ["iniconfig==2.0.0"] + "#}) + .unwrap(); + + context.lock().assert().success(); + + let server = MockServer::start().await; + + // A vulnerability with no fix versions. + Mock::given(method("POST")) + .and(path("/v1/querybatch")) + .respond_with(ResponseTemplate::new(200).set_body_json(json!({ + "results": [{"vulns": [{"id": "VULN-NO-FIX"}]}] + }))) + .mount(&server) + .await; + + Mock::given(method("GET")) + .and(path("/v1/vulns/VULN-NO-FIX")) + .respond_with(ResponseTemplate::new(200).set_body_json(json!({ + "id": "VULN-NO-FIX", + "modified": "2026-01-01T00:00:00Z", + "summary": "A vulnerability with no fix available" + }))) + .mount(&server) + .await; + + // With --ignore-until-fixed and no fix versions, the vulnerability is suppressed. + uv_snapshot!(context.filters(), context + .audit() + .arg("--frozen") + .arg("--preview") + .arg("--ignore-until-fixed") + .arg("VULN-NO-FIX") + .arg("--service-url") + .arg(server.uri()), @" + success: true + exit_code: 0 + ----- stdout ----- + + ----- stderr ----- + Found no known vulnerabilities and no adverse project statuses in 1 package + "); +} + +/// `--ignore-until-fixed` stops suppressing once a fix version is available. +#[tokio::test] +async fn audit_ignore_until_fixed_with_fix() { + let context = uv_test::test_context!("3.12"); + + let pyproject_toml = context.temp_dir.child("pyproject.toml"); + pyproject_toml + .write_str(indoc! {r#" + [project] + name = "project" + version = "0.1.0" + requires-python = ">=3.12" + dependencies = ["iniconfig==2.0.0"] + "#}) + .unwrap(); + + context.lock().assert().success(); + + let server = MockServer::start().await; + + // A vulnerability WITH fix versions. + Mock::given(method("POST")) + .and(path("/v1/querybatch")) + .respond_with(ResponseTemplate::new(200).set_body_json(json!({ + "results": [{"vulns": [{"id": "PYSEC-2023-0001"}]}] + }))) + .mount(&server) + .await; + + Mock::given(method("GET")) + .and(path("/v1/vulns/PYSEC-2023-0001")) + .respond_with(ResponseTemplate::new(200).set_body_json(json!({ + "id": "PYSEC-2023-0001", + "modified": "2026-01-01T00:00:00Z", + "summary": "A test vulnerability in iniconfig", + "affected": [{ + "ranges": [{ + "type": "ECOSYSTEM", + "events": [ + {"introduced": "0"}, + {"fixed": "2.1.0"} + ] + }] + }] + }))) + .mount(&server) + .await; + + // With --ignore-until-fixed but a fix IS available, the vulnerability is still reported. + uv_snapshot!(context.filters(), context + .audit() + .arg("--frozen") + .arg("--preview") + .arg("--ignore-until-fixed") + .arg("PYSEC-2023-0001") + .arg("--service-url") + .arg(server.uri()), @" + success: false + exit_code: 1 + ----- stdout ----- + + Vulnerabilities: + + iniconfig 2.0.0 has 1 known vulnerability: + + - PYSEC-2023-0001: A test vulnerability in iniconfig + + Fixed in: 2.1.0 + + Advisory information: https://osv.dev/vulnerability/PYSEC-2023-0001 + + + ----- stderr ----- + Found 1 known vulnerability and no adverse project statuses in 1 package + "); +} + +/// `[tool.uv.audit]` config supports `ignore`. +#[tokio::test] +async fn audit_ignore_config() { + let context = uv_test::test_context!("3.12"); + + let pyproject_toml = context.temp_dir.child("pyproject.toml"); + pyproject_toml + .write_str(indoc! {r#" + [project] + name = "project" + version = "0.1.0" + requires-python = ">=3.12" + dependencies = ["iniconfig==2.0.0"] + + [tool.uv.audit] + ignore = ["PYSEC-2023-0001"] + "#}) + .unwrap(); + + context.lock().assert().success(); + + let server = MockServer::start().await; + + Mock::given(method("POST")) + .and(path("/v1/querybatch")) + .respond_with(ResponseTemplate::new(200).set_body_json(json!({ + "results": [{"vulns": [{"id": "PYSEC-2023-0001"}]}] + }))) + .mount(&server) + .await; + + Mock::given(method("GET")) + .and(path("/v1/vulns/PYSEC-2023-0001")) + .respond_with(ResponseTemplate::new(200).set_body_json(json!({ + "id": "PYSEC-2023-0001", + "modified": "2026-01-01T00:00:00Z", + "summary": "A test vulnerability in iniconfig", + "affected": [{ + "ranges": [{ + "type": "ECOSYSTEM", + "events": [ + {"introduced": "0"}, + {"fixed": "2.1.0"} + ] + }] + }] + }))) + .mount(&server) + .await; + + // The vulnerability is suppressed by the config. + uv_snapshot!(context.filters(), context + .audit() + .arg("--frozen") + .arg("--preview") + .arg("--service-url") + .arg(server.uri()), @" + success: true + exit_code: 0 + ----- stdout ----- + + ----- stderr ----- + Found no known vulnerabilities and no adverse project statuses in 1 package + "); +} + +/// `[tool.uv.audit]` config supports `ignore-until-fixed`. +#[tokio::test] +async fn audit_ignore_until_fixed_config() { + let context = uv_test::test_context!("3.12"); + + let pyproject_toml = context.temp_dir.child("pyproject.toml"); + pyproject_toml + .write_str(indoc! {r#" + [project] + name = "project" + version = "0.1.0" + requires-python = ">=3.12" + dependencies = ["iniconfig==2.0.0"] + + [tool.uv.audit] + ignore-until-fixed = ["VULN-NO-FIX"] + "#}) + .unwrap(); + + context.lock().assert().success(); + + let server = MockServer::start().await; + + Mock::given(method("POST")) + .and(path("/v1/querybatch")) + .respond_with(ResponseTemplate::new(200).set_body_json(json!({ + "results": [{"vulns": [{"id": "VULN-NO-FIX"}]}] + }))) + .mount(&server) + .await; + + Mock::given(method("GET")) + .and(path("/v1/vulns/VULN-NO-FIX")) + .respond_with(ResponseTemplate::new(200).set_body_json(json!({ + "id": "VULN-NO-FIX", + "modified": "2026-01-01T00:00:00Z", + "summary": "A vulnerability with no fix available" + }))) + .mount(&server) + .await; + + // The vulnerability is suppressed because no fix is available. + uv_snapshot!(context.filters(), context + .audit() + .arg("--frozen") + .arg("--preview") + .arg("--service-url") + .arg(server.uri()), @" + success: true + exit_code: 0 + ----- stdout ----- + + ----- stderr ----- + Found no known vulnerabilities and no adverse project statuses in 1 package + "); +} + +/// `--ignore` only suppresses the targeted vulnerability, not others. +#[tokio::test] +async fn audit_ignore_partial() { + let context = uv_test::test_context!("3.12"); + + let pyproject_toml = context.temp_dir.child("pyproject.toml"); + pyproject_toml + .write_str(indoc! {r#" + [project] + name = "project" + version = "0.1.0" + requires-python = ">=3.12" + dependencies = ["iniconfig==2.0.0"] + "#}) + .unwrap(); + + context.lock().assert().success(); + + let server = MockServer::start().await; + + Mock::given(method("POST")) + .and(path("/v1/querybatch")) + .respond_with(ResponseTemplate::new(200).set_body_json(json!({ + "results": [{"vulns": [{"id": "VULN-A"}, {"id": "VULN-B"}]}] + }))) + .mount(&server) + .await; + + Mock::given(method("GET")) + .and(path("/v1/vulns/VULN-A")) + .respond_with(ResponseTemplate::new(200).set_body_json(json!({ + "id": "VULN-A", + "modified": "2026-01-01T00:00:00Z", + "summary": "First vulnerability", + "affected": [{ + "ranges": [{ + "type": "ECOSYSTEM", + "events": [ + {"introduced": "0"}, + {"fixed": "2.1.0"} + ] + }] + }] + }))) + .mount(&server) + .await; + + Mock::given(method("GET")) + .and(path("/v1/vulns/VULN-B")) + .respond_with(ResponseTemplate::new(200).set_body_json(json!({ + "id": "VULN-B", + "modified": "2026-01-02T00:00:00Z", + "summary": "Second vulnerability", + "affected": [{ + "ranges": [{ + "type": "ECOSYSTEM", + "events": [ + {"introduced": "2.0.0"}, + {"fixed": "2.0.1"} + ] + }] + }] + }))) + .mount(&server) + .await; + + // Ignoring VULN-A should still report VULN-B. + uv_snapshot!(context.filters(), context + .audit() + .arg("--frozen") + .arg("--preview") + .arg("--ignore") + .arg("VULN-A") + .arg("--service-url") + .arg(server.uri()), @" + success: false + exit_code: 1 + ----- stdout ----- + + Vulnerabilities: + + iniconfig 2.0.0 has 1 known vulnerability: + + - VULN-B: Second vulnerability + + Fixed in: 2.0.1 + + Advisory information: https://osv.dev/vulnerability/VULN-B + + + ----- stderr ----- + Found 1 known vulnerability and no adverse project statuses in 1 package + "); +} diff --git a/crates/uv/tests/it/pip_install.rs b/crates/uv/tests/it/pip_install.rs index 4d2bbad8b5128..31cc8af7812f2 100644 --- a/crates/uv/tests/it/pip_install.rs +++ b/crates/uv/tests/it/pip_install.rs @@ -234,7 +234,7 @@ fn invalid_pyproject_toml_option_unknown_field() -> Result<()> { | 2 | unknown = "field" | ^^^^^^^ - unknown field `unknown`, expected one of `required-version`, `system-certs`, `native-tls`, `offline`, `no-cache`, `cache-dir`, `preview`, `python-preference`, `python-downloads`, `concurrent-downloads`, `concurrent-builds`, `concurrent-installs`, `index`, `index-url`, `extra-index-url`, `no-index`, `find-links`, `index-strategy`, `keyring-provider`, `http-proxy`, `https-proxy`, `no-proxy`, `allow-insecure-host`, `resolution`, `prerelease`, `fork-strategy`, `dependency-metadata`, `config-settings`, `config-settings-package`, `no-build-isolation`, `no-build-isolation-package`, `extra-build-dependencies`, `extra-build-variables`, `exclude-newer`, `exclude-newer-package`, `link-mode`, `compile-bytecode`, `no-sources`, `no-sources-package`, `upgrade`, `upgrade-package`, `reinstall`, `reinstall-package`, `no-build`, `no-build-package`, `no-binary`, `no-binary-package`, `torch-backend`, `python-install-mirror`, `pypy-install-mirror`, `python-downloads-json-url`, `publish-url`, `trusted-publishing`, `check-url`, `add-bounds`, `pip`, `cache-keys`, `override-dependencies`, `exclude-dependencies`, `constraint-dependencies`, `build-constraint-dependencies`, `environments`, `required-environments`, `conflicts`, `workspace`, `sources`, `managed`, `package`, `default-groups`, `dependency-groups`, `dev-dependencies`, `build-backend` + unknown field `unknown`, expected one of `required-version`, `system-certs`, `native-tls`, `offline`, `no-cache`, `cache-dir`, `preview`, `python-preference`, `python-downloads`, `concurrent-downloads`, `concurrent-builds`, `concurrent-installs`, `index`, `index-url`, `extra-index-url`, `no-index`, `find-links`, `index-strategy`, `keyring-provider`, `http-proxy`, `https-proxy`, `no-proxy`, `allow-insecure-host`, `resolution`, `prerelease`, `fork-strategy`, `dependency-metadata`, `config-settings`, `config-settings-package`, `no-build-isolation`, `no-build-isolation-package`, `extra-build-dependencies`, `extra-build-variables`, `exclude-newer`, `exclude-newer-package`, `link-mode`, `compile-bytecode`, `no-sources`, `no-sources-package`, `upgrade`, `upgrade-package`, `reinstall`, `reinstall-package`, `no-build`, `no-build-package`, `no-binary`, `no-binary-package`, `torch-backend`, `python-install-mirror`, `pypy-install-mirror`, `python-downloads-json-url`, `publish-url`, `trusted-publishing`, `check-url`, `add-bounds`, `audit`, `pip`, `cache-keys`, `override-dependencies`, `exclude-dependencies`, `constraint-dependencies`, `build-constraint-dependencies`, `environments`, `required-environments`, `conflicts`, `workspace`, `sources`, `managed`, `package`, `default-groups`, `dependency-groups`, `dev-dependencies`, `build-backend` Resolved in [TIME] Checked in [TIME] diff --git a/crates/uv/tests/it/show_settings.rs b/crates/uv/tests/it/show_settings.rs index a838ad3d08416..1e93feb3e66ba 100644 --- a/crates/uv/tests/it/show_settings.rs +++ b/crates/uv/tests/it/show_settings.rs @@ -4839,7 +4839,7 @@ fn resolve_config_file() -> anyhow::Result<()> { | 1 | [project] | ^^^^^^^ - unknown field `project`, expected one of `required-version`, `system-certs`, `native-tls`, `offline`, `no-cache`, `cache-dir`, `preview`, `python-preference`, `python-downloads`, `concurrent-downloads`, `concurrent-builds`, `concurrent-installs`, `index`, `index-url`, `extra-index-url`, `no-index`, `find-links`, `index-strategy`, `keyring-provider`, `http-proxy`, `https-proxy`, `no-proxy`, `allow-insecure-host`, `resolution`, `prerelease`, `fork-strategy`, `dependency-metadata`, `config-settings`, `config-settings-package`, `no-build-isolation`, `no-build-isolation-package`, `extra-build-dependencies`, `extra-build-variables`, `exclude-newer`, `exclude-newer-package`, `link-mode`, `compile-bytecode`, `no-sources`, `no-sources-package`, `upgrade`, `upgrade-package`, `reinstall`, `reinstall-package`, `no-build`, `no-build-package`, `no-binary`, `no-binary-package`, `torch-backend`, `python-install-mirror`, `pypy-install-mirror`, `python-downloads-json-url`, `publish-url`, `trusted-publishing`, `check-url`, `add-bounds`, `pip`, `cache-keys`, `override-dependencies`, `exclude-dependencies`, `constraint-dependencies`, `build-constraint-dependencies`, `environments`, `required-environments`, `conflicts`, `workspace`, `sources`, `managed`, `package`, `default-groups`, `dependency-groups`, `dev-dependencies`, `build-backend` + unknown field `project`, expected one of `required-version`, `system-certs`, `native-tls`, `offline`, `no-cache`, `cache-dir`, `preview`, `python-preference`, `python-downloads`, `concurrent-downloads`, `concurrent-builds`, `concurrent-installs`, `index`, `index-url`, `extra-index-url`, `no-index`, `find-links`, `index-strategy`, `keyring-provider`, `http-proxy`, `https-proxy`, `no-proxy`, `allow-insecure-host`, `resolution`, `prerelease`, `fork-strategy`, `dependency-metadata`, `config-settings`, `config-settings-package`, `no-build-isolation`, `no-build-isolation-package`, `extra-build-dependencies`, `extra-build-variables`, `exclude-newer`, `exclude-newer-package`, `link-mode`, `compile-bytecode`, `no-sources`, `no-sources-package`, `upgrade`, `upgrade-package`, `reinstall`, `reinstall-package`, `no-build`, `no-build-package`, `no-binary`, `no-binary-package`, `torch-backend`, `python-install-mirror`, `pypy-install-mirror`, `python-downloads-json-url`, `publish-url`, `trusted-publishing`, `check-url`, `add-bounds`, `audit`, `pip`, `cache-keys`, `override-dependencies`, `exclude-dependencies`, `constraint-dependencies`, `build-constraint-dependencies`, `environments`, `required-environments`, `conflicts`, `workspace`, `sources`, `managed`, `package`, `default-groups`, `dependency-groups`, `dev-dependencies`, `build-backend` " ); diff --git a/uv.schema.json b/uv.schema.json index 18272f5f91d0e..d68021afbee8a 100644 --- a/uv.schema.json +++ b/uv.schema.json @@ -22,6 +22,16 @@ "$ref": "#/definitions/TrustedHost" } }, + "audit": { + "anyOf": [ + { + "$ref": "#/definitions/AuditOptions" + }, + { + "type": "null" + } + ] + }, "build-backend": { "description": "Configuration for the uv build backend.\n\nNote that those settings only apply when using the `uv_build` backend, other build backends\n(such as hatchling) have their own configuration.", "anyOf": [ @@ -612,6 +622,25 @@ } ] }, + "AuditOptions": { + "type": "object", + "properties": { + "ignore": { + "description": "A list of vulnerability IDs to ignore during auditing.\n\nVulnerabilities matching any of the provided IDs (including aliases) will be excluded from\nthe audit results.", + "type": ["array", "null"], + "items": { + "type": "string" + } + }, + "ignore-until-fixed": { + "description": "A list of vulnerability IDs to ignore during auditing, but only while no fix is available.\n\nVulnerabilities matching any of the provided IDs (including aliases) will be excluded from\nthe audit results as long as they have no known fix versions. Once a fix version becomes\navailable, the vulnerability will be reported again.", + "type": ["array", "null"], + "items": { + "type": "string" + } + } + } + }, "AuthPolicy": { "description": "When to use authentication.", "oneOf": [