Skip to content

Commit

Permalink
Turn --verify-hashes on by default (#9170)
Browse files Browse the repository at this point in the history
Fixes #9164

Using clap's `default_value_t` makes the `flag` function unhappy, so
just set the default when we unwrap. Tested with no flags,
`--verify-hashes`, `--no-verify-hashes` and setting in uv.toml

---------

Co-authored-by: Charlie Marsh <[email protected]>
  • Loading branch information
hauntsaninja and charliermarsh authored Nov 18, 2024
1 parent 5ba1866 commit 71d9c45
Show file tree
Hide file tree
Showing 11 changed files with 1,004 additions and 183 deletions.
84 changes: 46 additions & 38 deletions crates/uv-cli/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1263,9 +1263,12 @@ pub struct PipSyncArgs {

/// Require a matching hash for each requirement.
///
/// Hash-checking mode is all or nothing. If enabled, _all_ requirements must be provided
/// with a corresponding hash or set of hashes. Additionally, if enabled, _all_ requirements
/// must either be pinned to exact versions (e.g., `==1.0.0`), or be specified via direct URL.
/// By default, uv will verify any available hashes in the requirements file, but will not
/// require that all requirements have an associated hash.
///
/// When `--require-hashes` is enabled, _all_ requirements must include a hash or set of hashes,
/// and _all_ requirements must either be pinned to exact versions (e.g., `==1.0.0`), or be
/// specified via direct URL.
///
/// Hash-checking mode introduces a number of additional constraints:
///
Expand All @@ -1284,20 +1287,20 @@ pub struct PipSyncArgs {
#[arg(long, overrides_with("require_hashes"), hide = true)]
pub no_require_hashes: bool,

/// Validate any hashes provided in the requirements file.
#[arg(long, overrides_with("no_verify_hashes"), hide = true)]
pub verify_hashes: bool,

/// Disable validation of hashes in the requirements file.
///
/// Unlike `--require-hashes`, `--verify-hashes` does not require that all requirements have
/// hashes; instead, it will limit itself to verifying the hashes of those requirements that do
/// include them.
/// By default, uv will verify any available hashes in the requirements file, but will not
/// require that all requirements have an associated hash. To enforce hash validation, use
/// `--require-hashes`.
#[arg(
long,
env = EnvVars::UV_VERIFY_HASHES,
env = EnvVars::UV_NO_VERIFY_HASHES,
value_parser = clap::builder::BoolishValueParser::new(),
overrides_with("no_verify_hashes"),
overrides_with("verify_hashes"),
)]
pub verify_hashes: bool,

#[arg(long, overrides_with("verify_hashes"), hide = true)]
pub no_verify_hashes: bool,

/// The Python interpreter into which packages should be installed.
Expand Down Expand Up @@ -1546,9 +1549,12 @@ pub struct PipInstallArgs {

/// Require a matching hash for each requirement.
///
/// Hash-checking mode is all or nothing. If enabled, _all_ requirements must be provided
/// with a corresponding hash or set of hashes. Additionally, if enabled, _all_ requirements
/// must either be pinned to exact versions (e.g., `==1.0.0`), or be specified via direct URL.
/// By default, uv will verify any available hashes in the requirements file, but will not
/// require that all requirements have an associated hash.
///
/// When `--require-hashes` is enabled, _all_ requirements must include a hash or set of hashes,
/// and _all_ requirements must either be pinned to exact versions (e.g., `==1.0.0`), or be
/// specified via direct URL.
///
/// Hash-checking mode introduces a number of additional constraints:
///
Expand All @@ -1567,20 +1573,20 @@ pub struct PipInstallArgs {
#[arg(long, overrides_with("require_hashes"), hide = true)]
pub no_require_hashes: bool,

/// Validate any hashes provided in the requirements file.
#[arg(long, overrides_with("no_verify_hashes"), hide = true)]
pub verify_hashes: bool,

/// Disable validation of hashes in the requirements file.
///
/// Unlike `--require-hashes`, `--verify-hashes` does not require that all requirements have
/// hashes; instead, it will limit itself to verifying the hashes of those requirements that do
/// include them.
/// By default, uv will verify any available hashes in the requirements file, but will not
/// require that all requirements have an associated hash. To enforce hash validation, use
/// `--require-hashes`.
#[arg(
long,
env = EnvVars::UV_VERIFY_HASHES,
env = EnvVars::UV_NO_VERIFY_HASHES,
value_parser = clap::builder::BoolishValueParser::new(),
overrides_with("no_verify_hashes"),
overrides_with("verify_hashes"),
)]
pub verify_hashes: bool,

#[arg(long, overrides_with("verify_hashes"), hide = true)]
pub no_verify_hashes: bool,

/// The Python interpreter into which packages should be installed.
Expand Down Expand Up @@ -2177,12 +2183,14 @@ pub struct BuildArgs {
#[arg(long, short, env = EnvVars::UV_BUILD_CONSTRAINT, value_delimiter = ' ', value_parser = parse_maybe_file_path)]
pub build_constraint: Vec<Maybe<PathBuf>>,

/// Require a matching hash for each build requirement.
/// Require a matching hash for each requirement.
///
/// By default, uv will verify any available hashes in the requirements file, but will not
/// require that all requirements have an associated hash.
///
/// Hash-checking mode is all or nothing. If enabled, _all_ build requirements must be provided
/// with a corresponding hash or set of hashes via the `--build-constraint` argument.
/// Additionally, if enabled, _all_ requirements must either be pinned to exact versions
/// (e.g., `==1.0.0`), or be specified via direct URL.
/// When `--require-hashes` is enabled, _all_ requirements must include a hash or set of hashes,
/// and _all_ requirements must either be pinned to exact versions (e.g., `==1.0.0`), or be
/// specified via direct URL.
///
/// Hash-checking mode introduces a number of additional constraints:
///
Expand All @@ -2201,20 +2209,20 @@ pub struct BuildArgs {
#[arg(long, overrides_with("require_hashes"), hide = true)]
pub no_require_hashes: bool,

/// Validate any hashes provided in the build constraints file.
#[arg(long, overrides_with("no_verify_hashes"), hide = true)]
pub verify_hashes: bool,

/// Disable validation of hashes in the requirements file.
///
/// Unlike `--require-hashes`, `--verify-hashes` does not require that all requirements have
/// hashes; instead, it will limit itself to verifying the hashes of those requirements that do
/// include them.
/// By default, uv will verify any available hashes in the requirements file, but will not
/// require that all requirements have an associated hash. To enforce hash validation, use
/// `--require-hashes`.
#[arg(
long,
env = EnvVars::UV_VERIFY_HASHES,
env = EnvVars::UV_NO_VERIFY_HASHES,
value_parser = clap::builder::BoolishValueParser::new(),
overrides_with("no_verify_hashes"),
overrides_with("verify_hashes"),
)]
pub verify_hashes: bool,

#[arg(long, overrides_with("verify_hashes"), hide = true)]
pub no_verify_hashes: bool,

/// The Python interpreter to use for the build environment.
Expand Down
21 changes: 17 additions & 4 deletions crates/uv-configuration/src/hash.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,26 @@ pub enum HashCheckingMode {

impl HashCheckingMode {
/// Return the [`HashCheckingMode`] from the command-line arguments, if any.
pub fn from_args(require_hashes: bool, verify_hashes: bool) -> Option<Self> {
if require_hashes {
///
/// By default, the hash checking mode is [`HashCheckingMode::Verify`]. If `--require-hashes` is
/// passed, the hash checking mode is [`HashCheckingMode::Require`]. If `--no-verify-hashes` is
/// passed, then no hash checking is performed.
pub fn from_args(require_hashes: Option<bool>, verify_hashes: Option<bool>) -> Option<Self> {
if require_hashes == Some(true) {
// Given `--require-hashes`, always require hashes, regardless of any other flags.
Some(Self::Require)
} else if verify_hashes {
} else if verify_hashes == Some(true) {
// Given `--verify-hashes`, always verify hashes, regardless of any other flags.
Some(Self::Verify)
} else {
} else if verify_hashes == Some(false) {
// Given `--no-verify-hashes` (without `--require-hashes`), do not verify hashes.
None
} else if require_hashes == Some(false) {
// Given `--no-require-hashes` (without `--verify-hashes`), do not require hashes.
None
} else {
// By default, verify hashes.
Some(Self::Verify)
}
}

Expand Down
2 changes: 1 addition & 1 deletion crates/uv-settings/src/settings.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1348,7 +1348,7 @@ pub struct PipOptions {
/// hashes; instead, it will limit itself to verifying the hashes of those requirements that do
/// include them.
#[option(
default = "false",
default = "true",
value_type = "bool",
example = r#"
verify-hashes = true
Expand Down
5 changes: 3 additions & 2 deletions crates/uv-static/src/env_vars.rs
Original file line number Diff line number Diff line change
Expand Up @@ -167,8 +167,9 @@ impl EnvVars {
/// Equivalent to the `--token` argument for self update. A GitHub token for authentication.
pub const UV_GITHUB_TOKEN: &'static str = "UV_GITHUB_TOKEN";

/// Equivalent to the `--verify-hashes` argument. Verifies included hashes.
pub const UV_VERIFY_HASHES: &'static str = "UV_VERIFY_HASHES";
/// Equivalent to the `--no-verify-hashes` argument. Disables hash verification for
/// `requirements.txt` files.
pub const UV_NO_VERIFY_HASHES: &'static str = "UV_VERIFY_HASHES";

/// Equivalent to the `--allow-insecure-host` argument.
pub const UV_INSECURE_HOST: &'static str = "UV_INSECURE_HOST";
Expand Down
12 changes: 4 additions & 8 deletions crates/uv/src/settings.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2003,8 +2003,8 @@ impl BuildSettings {
.filter_map(Maybe::into_option)
.collect(),
hash_checking: HashCheckingMode::from_args(
flag(require_hashes, no_require_hashes).unwrap_or_default(),
flag(verify_hashes, no_verify_hashes).unwrap_or_default(),
flag(require_hashes, no_require_hashes),
flag(verify_hashes, no_verify_hashes),
),
python: python.and_then(Maybe::into_option),
refresh: Refresh::from(refresh),
Expand Down Expand Up @@ -2641,12 +2641,8 @@ impl PipSettings {
.unwrap_or_default(),
link_mode: args.link_mode.combine(link_mode).unwrap_or_default(),
hash_checking: HashCheckingMode::from_args(
args.require_hashes
.combine(require_hashes)
.unwrap_or_default(),
args.verify_hashes
.combine(verify_hashes)
.unwrap_or_default(),
args.require_hashes.combine(require_hashes),
args.verify_hashes.combine(verify_hashes),
),
python: args.python.combine(python),
system: args.system.combine(system).unwrap_or_default(),
Expand Down
97 changes: 19 additions & 78 deletions crates/uv/tests/it/build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1447,103 +1447,40 @@ fn sha() -> Result<()> {
project.child("src").child("__init__.py").touch()?;
project.child("README").touch()?;

// Ignore an incorrect hash, if `--require-hashes` is not provided.
// Reject an incorrect hash.
let constraints = project.child("constraints.txt");
constraints.write_str("setuptools==68.2.2 --hash=sha256:a248cb506794bececcddeddb1678bc722f9cfcacf02f98f7c0af6b9ed893caf2")?;

uv_snapshot!(&filters, context.build().arg("--build-constraint").arg("constraints.txt").current_dir(&project), @r###"
success: true
exit_code: 0
success: false
exit_code: 2
----- stdout -----
----- stderr -----
Building source distribution...
running egg_info
creating src/project.egg-info
writing src/project.egg-info/PKG-INFO
writing dependency_links to src/project.egg-info/dependency_links.txt
writing requirements to src/project.egg-info/requires.txt
writing top-level names to src/project.egg-info/top_level.txt
writing manifest file 'src/project.egg-info/SOURCES.txt'
reading manifest file 'src/project.egg-info/SOURCES.txt'
writing manifest file 'src/project.egg-info/SOURCES.txt'
running sdist
running egg_info
writing src/project.egg-info/PKG-INFO
writing dependency_links to src/project.egg-info/dependency_links.txt
writing requirements to src/project.egg-info/requires.txt
writing top-level names to src/project.egg-info/top_level.txt
reading manifest file 'src/project.egg-info/SOURCES.txt'
writing manifest file 'src/project.egg-info/SOURCES.txt'
running check
creating project-0.1.0
creating project-0.1.0/src
creating project-0.1.0/src/project.egg-info
copying files to project-0.1.0...
copying README -> project-0.1.0
copying pyproject.toml -> project-0.1.0
copying src/__init__.py -> project-0.1.0/src
copying src/project.egg-info/PKG-INFO -> project-0.1.0/src/project.egg-info
copying src/project.egg-info/SOURCES.txt -> project-0.1.0/src/project.egg-info
copying src/project.egg-info/dependency_links.txt -> project-0.1.0/src/project.egg-info
copying src/project.egg-info/requires.txt -> project-0.1.0/src/project.egg-info
copying src/project.egg-info/top_level.txt -> project-0.1.0/src/project.egg-info
Writing project-0.1.0/setup.cfg
Creating tar archive
removing 'project-0.1.0' (and everything under it)
Building wheel from source distribution...
running egg_info
writing src/project.egg-info/PKG-INFO
writing dependency_links to src/project.egg-info/dependency_links.txt
writing requirements to src/project.egg-info/requires.txt
writing top-level names to src/project.egg-info/top_level.txt
reading manifest file 'src/project.egg-info/SOURCES.txt'
writing manifest file 'src/project.egg-info/SOURCES.txt'
running bdist_wheel
running build
running build_py
creating build
creating build/lib
copying src/__init__.py -> build/lib
running egg_info
writing src/project.egg-info/PKG-INFO
writing dependency_links to src/project.egg-info/dependency_links.txt
writing requirements to src/project.egg-info/requires.txt
writing top-level names to src/project.egg-info/top_level.txt
reading manifest file 'src/project.egg-info/SOURCES.txt'
writing manifest file 'src/project.egg-info/SOURCES.txt'
installing to build/bdist.linux-x86_64/wheel
running install
running install_lib
creating build/bdist.linux-x86_64
creating build/bdist.linux-x86_64/wheel
copying build/lib/__init__.py -> build/bdist.linux-x86_64/wheel
running install_egg_info
Copying src/project.egg-info to build/bdist.linux-x86_64/wheel/project-0.1.0-py3.8.egg-info
running install_scripts
creating build/bdist.linux-x86_64/wheel/project-0.1.0.dist-info/WHEEL
creating '[TEMP_DIR]/project/dist/[TMP]/wheel' to it
adding '__init__.py'
adding 'project-0.1.0.dist-info/METADATA'
adding 'project-0.1.0.dist-info/WHEEL'
adding 'project-0.1.0.dist-info/top_level.txt'
adding 'project-0.1.0.dist-info/RECORD'
removing build/bdist.linux-x86_64/wheel
Successfully built dist/project-0.1.0.tar.gz and dist/project-0.1.0-py3-none-any.whl
error: Failed to install requirements from `build-system.requires`
Caused by: Failed to download `setuptools==68.2.2`
Caused by: Hash mismatch for `setuptools==68.2.2`
Expected:
sha256:a248cb506794bececcddeddb1678bc722f9cfcacf02f98f7c0af6b9ed893caf2
Computed:
sha256:b454a35605876da60632df1a60f736524eb73cc47bbc9f3f1ef1b644de74fd2a
"###);

project
.child("dist")
.child("project-0.1.0.tar.gz")
.assert(predicate::path::is_file());
.assert(predicate::path::missing());
project
.child("dist")
.child("project-0.1.0-py3-none-any.whl")
.assert(predicate::path::is_file());
.assert(predicate::path::missing());

fs_err::remove_dir_all(project.child("dist"))?;

// Reject an incorrect hash.
// Reject an incorrect hash with --requires-hashes.
uv_snapshot!(&filters, context.build().arg("--build-constraint").arg("constraints.txt").arg("--require-hashes").current_dir(&project), @r###"
success: false
exit_code: 2
Expand Down Expand Up @@ -1598,6 +1535,8 @@ fn sha() -> Result<()> {
.child("project-0.1.0-py3-none-any.whl")
.assert(predicate::path::missing());

fs_err::remove_dir_all(project.child("dist"))?;

// Accept a correct hash.
let constraints = project.child("constraints.txt");
constraints.write_str("setuptools==68.2.2 --hash=sha256:b454a35605876da60632df1a60f736524eb73cc47bbc9f3f1ef1b644de74fd2a")?;
Expand All @@ -1610,10 +1549,12 @@ fn sha() -> Result<()> {
----- stderr -----
Building source distribution...
running egg_info
creating src/project.egg-info
writing src/project.egg-info/PKG-INFO
writing dependency_links to src/project.egg-info/dependency_links.txt
writing requirements to src/project.egg-info/requires.txt
writing top-level names to src/project.egg-info/top_level.txt
writing manifest file 'src/project.egg-info/SOURCES.txt'
reading manifest file 'src/project.egg-info/SOURCES.txt'
writing manifest file 'src/project.egg-info/SOURCES.txt'
running sdist
Expand Down
Loading

0 comments on commit 71d9c45

Please sign in to comment.