Skip to content

Make uv’s first-index strategy more secure by default by failing early on authentication failure #12805

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 7 commits into from
Apr 29, 2025

Conversation

jtfmumm
Copy link
Contributor

@jtfmumm jtfmumm commented Apr 10, 2025

uv’s default index strategy was designed with dependency confusion attacks in mind. According to the docs, “if a package exists on an internal index, it should always be installed from the internal index, and never from PyPI”. Unfortunately, this is not true in the case where authentication fails on that internal index. In that case, uv will simply try the next index (even on the first-index strategy). This means that uv is not secure by default in this common scenario.

This PR causes uv to stop searching for a package if it encounters an authentication failure at an index. It is possible to opt out of this behavior for an index with a new pyproject.toml option ignore-error-codes. For example:

[[tool.uv.index]]
name = "my-index"
url = "<index-url>"
ignore-error-codes = [401, 403]

This will also enable users to handle idiosyncratic registries in a more fine-grained way. For example, PyTorch registries return a 403 when a package is not found. In this PR, we special-case PyTorch registries to ignore 403s, but users can use ignore-error-codes to handle similar behaviors if they encounter them on internal registries.

Depends on #12651

Closes #9429
Closes #12362

@jtfmumm jtfmumm added enhancement New feature or improvement to existing functionality network Network connectivity e.g. proxies, DNS, and SSL breaking A breaking change labels Apr 10, 2025
@jtfmumm jtfmumm changed the title Exit indexes search on auth failure to make uv's first-index strategy more secure by default Make uv’s first-index strategy more secure by default by failing early on authentication failure Apr 10, 2025
@jtfmumm jtfmumm force-pushed the jtfm/first-index-secure-by-default branch from 324c1b4 to f57e40c Compare April 10, 2025 18:55
@jtfmumm jtfmumm force-pushed the jtfm/index-url-auth branch from da2c4d8 to b694b92 Compare April 11, 2025 08:19
@jtfmumm jtfmumm force-pushed the jtfm/first-index-secure-by-default branch from f57e40c to 5e6f511 Compare April 11, 2025 09:23
@jtfmumm jtfmumm force-pushed the jtfm/index-url-auth branch from b694b92 to d7b6799 Compare April 11, 2025 12:44
@@ -14,11 +15,26 @@ use crate::Realm;

type FxOnceMap<K, V> = OnceMap<K, V, BuildHasherDefault<FxHasher>>;

#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub(crate) enum FetchUrl {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Rustdoc please for the variants.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added

fn fmt(&self, f: &mut Formatter) -> std::fmt::Result {
match self {
FetchUrl::Index(index) => Display::fmt(index, f),
FetchUrl::Realm(realm) => Display::fmt(realm, f),
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: Self instead of FetchUrl

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated

@@ -60,6 +60,23 @@ pub struct Index {
pub auth_policy: AuthPolicy,
}

impl Index {
pub fn is_prefix_for(&self, url: &Url) -> bool {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: Rustdoc please with an example of when this returns true vs. false.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Because of the timing of the release, can we defer this to a PR afterwards? I just moved this logic from a function to a method in this PR.

}
_ => Err(ErrorKind::WrappedReqwestError(url, err).into()),
},
ErrorKind::WrappedReqwestError(url, err) => {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm still struggling a bit. Why do we break when (e.g.) we hit a 401, but 401 isn't included in the list of ignored errors? Why is it not something like this?

diff --git a/crates/uv-client/src/registry_client.rs b/crates/uv-client/src/registry_client.rs
index 8bdf2c065..3cb6b4978 100644
--- a/crates/uv-client/src/registry_client.rs
+++ b/crates/uv-client/src/registry_client.rs
@@ -348,12 +348,6 @@ impl RegistryClient {
                                 }
                                 // Package not found, so we will continue on to the next index (if there is one)
                                 SimpleMetadataSearchOutcome::NotFound => {}
-                                // The search failed because of an HTTP status code that we don't ignore for
-                                // this index. We end our search here.
-                                SimpleMetadataSearchOutcome::StatusCodeFailure(status_code) => {
-                                    debug!("Indexes search failed because of status code failure: {status_code}");
-                                    break;
-                                }
                             }
                         }
                         IndexFormat::Flat => {
@@ -522,17 +516,15 @@ impl RegistryClient {
                     let Some(status_code) = err.status() else {
                         return Err(ErrorKind::WrappedReqwestError(url, err).into());
                     };
-                    let decision =
-                        status_code_strategy.handle_status_code(status_code, index, capabilities);
-                    if let IndexStatusCodeDecision::Fail(status_code) = decision {
-                        if !matches!(
-                            status_code,
-                            StatusCode::UNAUTHORIZED | StatusCode::FORBIDDEN
-                        ) {
-                            return Err(ErrorKind::WrappedReqwestError(url, err).into());
+                    match status_code_strategy.handle_status_code(status_code, index, capabilities)
+                    {
+                        IndexStatusCodeDecision::Ignore => {
+                            Ok(SimpleMetadataSearchOutcome::NotFound)
+                        }
+                        IndexStatusCodeDecision::Fail(..) => {
+                            Err(ErrorKind::WrappedReqwestError(url, err).into())
                         }
                     }
-                    Ok(SimpleMetadataSearchOutcome::from(decision))
                 }
 
                 // The package is unavailable due to a lack of connectivity.
@@ -998,18 +990,6 @@ pub(crate) enum SimpleMetadataSearchOutcome {
     Found(OwnedArchive<SimpleMetadata>),
     /// Simple metadata was not found
     NotFound,
-    /// A status code failure was encountered when searching for
-    /// simple metadata and our strategy did not ignore it
-    StatusCodeFailure(StatusCode),
-}
-
-impl From<IndexStatusCodeDecision> for SimpleMetadataSearchOutcome {
-    fn from(item: IndexStatusCodeDecision) -> Self {
-        match item {
-            IndexStatusCodeDecision::Ignore => Self::NotFound,
-            IndexStatusCodeDecision::Fail(status_code) => Self::StatusCodeFailure(status_code),
-        }
-    }
 }
 
 /// A map from [`IndexUrl`] to [`FlatIndexEntry`] entries found at the given URL, indexed by

// Package not found, so we will continue on to the next index (if there is one)
SimpleMetadataSearchOutcome::NotFound => {}
// The search failed because of an HTTP status code that we don't ignore for
// this index. We end our search here.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this should mention the assumption that the status code failure will be tracked by IndexCapabilities and surfaced via hints.

if let IndexStatusCodeDecision::Fail(status_code) = decision {
if !matches!(
status_code,
StatusCode::UNAUTHORIZED | StatusCode::FORBIDDEN
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this should mention that these specific codes are tracked via IndexCapabilities and surfaced via hints; otherwise, it's not really clear why these specific codes are ignored.

NotFound,
/// A status code failure was encountered when searching for
/// simple metadata and our strategy did not ignore it
StatusCodeFailure(StatusCode),
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think I would find it clearer if we actually just made individual enum variants for Unauthorized and Forbidden, rather than allowing an arbitrary status code here. That way the expectations are encoded in the type system. We should make the invalid state (e.g., StatusCodeFailure(400)) unrepresentable.

@zanieb
Copy link
Member

zanieb commented Apr 29, 2025

I'm going to merge and kick the tires on the release.

@zanieb zanieb merged commit 5bbf839 into release/070 Apr 29, 2025
@zanieb zanieb deleted the jtfm/first-index-secure-by-default branch April 29, 2025 17:48
zanieb added a commit that referenced this pull request Apr 29, 2025
…y on authentication failure (#12805)

uv’s default index strategy was designed with dependency confusion
attacks in mind. [According to the
docs](https://docs.astral.sh/uv/configuration/indexes/#searching-across-multiple-indexes),
“if a package exists on an internal index, it should always be installed
from the internal index, and never from PyPI”. Unfortunately, this is
not true in the case where authentication fails on that internal index.
In that case, uv will simply try the next index (even on the
`first-index` strategy). This means that uv is not secure by default in
this common scenario.

This PR causes uv to stop searching for a package if it encounters an
authentication failure at an index. It is possible to opt out of this
behavior for an index with a new `pyproject.toml` option
`ignore-error-codes`. For example:

```
[[tool.uv.index]]
name = "my-index"
url = "<index-url>"
ignore-error-codes = [401, 403]
```

This will also enable users to handle idiosyncratic registries in a more
fine-grained way. For example, PyTorch registries return a 403 when a
package is not found. In this PR, we special-case PyTorch registries to
ignore 403s, but users can use `ignore-error-codes` to handle similar
behaviors if they encounter them on internal registries.

Depends on #12651

Closes #9429
Closes #12362
zanieb added a commit that referenced this pull request Apr 29, 2025
…y on authentication failure (#12805)

uv’s default index strategy was designed with dependency confusion
attacks in mind. [According to the
docs](https://docs.astral.sh/uv/configuration/indexes/#searching-across-multiple-indexes),
“if a package exists on an internal index, it should always be installed
from the internal index, and never from PyPI”. Unfortunately, this is
not true in the case where authentication fails on that internal index.
In that case, uv will simply try the next index (even on the
`first-index` strategy). This means that uv is not secure by default in
this common scenario.

This PR causes uv to stop searching for a package if it encounters an
authentication failure at an index. It is possible to opt out of this
behavior for an index with a new `pyproject.toml` option
`ignore-error-codes`. For example:

```
[[tool.uv.index]]
name = "my-index"
url = "<index-url>"
ignore-error-codes = [401, 403]
```

This will also enable users to handle idiosyncratic registries in a more
fine-grained way. For example, PyTorch registries return a 403 when a
package is not found. In this PR, we special-case PyTorch registries to
ignore 403s, but users can use `ignore-error-codes` to handle similar
behaviors if they encounter them on internal registries.

Depends on #12651

Closes #9429
Closes #12362
zanieb added a commit that referenced this pull request Apr 29, 2025
…y on authentication failure (#12805)

uv’s default index strategy was designed with dependency confusion
attacks in mind. [According to the
docs](https://docs.astral.sh/uv/configuration/indexes/#searching-across-multiple-indexes),
“if a package exists on an internal index, it should always be installed
from the internal index, and never from PyPI”. Unfortunately, this is
not true in the case where authentication fails on that internal index.
In that case, uv will simply try the next index (even on the
`first-index` strategy). This means that uv is not secure by default in
this common scenario.

This PR causes uv to stop searching for a package if it encounters an
authentication failure at an index. It is possible to opt out of this
behavior for an index with a new `pyproject.toml` option
`ignore-error-codes`. For example:

```
[[tool.uv.index]]
name = "my-index"
url = "<index-url>"
ignore-error-codes = [401, 403]
```

This will also enable users to handle idiosyncratic registries in a more
fine-grained way. For example, PyTorch registries return a 403 when a
package is not found. In this PR, we special-case PyTorch registries to
ignore 403s, but users can use `ignore-error-codes` to handle similar
behaviors if they encounter them on internal registries.

Depends on #12651

Closes #9429
Closes #12362
@juhaszp95
Copy link

Hi There,

Just a quick question on the above - I have an internal package index which also returns 403 for missing packages. Is it possible to make the ignore-error-codes a user-wide setting by somehow incorporating it in uv.toml? I.e. to declare custom exceptions user-wide.

Furthermore, it seems to me that the workaround in pyproject.toml only works if I include the authentication token as well in the url, which is obviously undesirable.

@jtfmumm
Copy link
Contributor Author

jtfmumm commented Apr 30, 2025

Hi There,

Just a quick question on the above - I have an internal package index which also returns 403 for missing packages. Is it possible to make the ignore-error-codes a user-wide setting by somehow incorporating it in uv.toml? I.e. to declare custom exceptions user-wide.

Furthermore, it seems to me that the workaround in pyproject.toml only works if I include the authentication token as well in the url, which is obviously undesirable.

You shouldn't need to include a token in the URL (and I agree you shouldn't include one in pyproject.toml). Did you run into an issue when excluding it?

@juhaszp95
Copy link

Yes - interestingly, I get the error An index URL (https://myorg.jfrog.io/artifactory/api/pypi/pypi/simple) could not be queried due to a lack of valid authentication credentials (403 Forbidden)., even though I set ignore-error-codes = [401, 403]. This index is set as an extra-index-url in uv.toml. This doesn't seem to be an issue for the other internal index I use, which is set as the index-url in uv.toml.

@jtfmumm
Copy link
Contributor Author

jtfmumm commented Apr 30, 2025

Ok, would you mind opening an issue and including a reproduction and trace (-vv) logs? I can investigate this.

@juhaszp95
Copy link

Should I put it here as it seems closely related, or would you rather a separate issue?

@jtfmumm
Copy link
Contributor Author

jtfmumm commented Apr 30, 2025

I think a separate issue for now would be helpful. It's not clear to me these are the same problem.

@juhaszp95
Copy link

juhaszp95 commented Apr 30, 2025

I've realised what the problem is just now (and it's sort of OK): as I was debugging, I left the url in the format USER@ORG@repository-url which works for extra-index-url but strangely does not for index-url. When I removed the USER@ORG part it worked again, so that's all solved I believe! I have no idea why it behaves differently for index-url and extra-index-url, but at least it works for both when there aren't really any credentials there (i.e. not just the token deleted, but everything). Also, interestingly, I need to put the index-url in pyproject.toml (without any ignore-error-codes) for it to work - strange. It's like as if the extra-index-url is treated differently than the index-url, or perhaps if indexes are declared in pyproject.toml, the contents of uv.toml are ignored, hence the need to declare the index in pyproject.toml as well.

This still leaves the outstanding question whether all this config could be saved to a uv.toml for user-level configuration so I don't need to do this at every single project.

@jtfmumm
Copy link
Contributor Author

jtfmumm commented Apr 30, 2025

Is this problem unique to 0.7.0 or do you also see it on 0.6.17? The ignore-error-codes feature doesn't exist on 0.6.17, but I'm trying to understand if something broke with 0.7.0 or if this is just an enhancement you'd like to see to the new error code behavior. Or if the main concern is the change that we are no longer ignoring 403s by default (and you can't find a good way to configure uv to ignore them for your situation).

@juhaszp95
Copy link

juhaszp95 commented Apr 30, 2025

The problem and enhancement request is both unique to 0.7.0. Before, I had all index and authentication info saved in uv.toml with no issues. Now, due to how 0.7.0 handles error codes, I have to copy ignores to all projects as well. It'd be good if the ignores could be configured centrally rather than at a project level (for my use-case). I've checked 0.7.1 doesn't solve this (as expected).

tmeijn pushed a commit to tmeijn/dotfiles that referenced this pull request May 10, 2025
This MR contains the following updates:

| Package | Update | Change |
|---|---|---|
| [astral-sh/uv](https://github.com/astral-sh/uv) | minor | `0.6.16` -> `0.7.3` |

MR created with the help of [el-capitano/tools/renovate-bot](https://gitlab.com/el-capitano/tools/renovate-bot).

**Proposed changes to behavior should be submitted there as MRs.**

---

### Release Notes

<details>
<summary>astral-sh/uv (astral-sh/uv)</summary>

### [`v0.7.3`](https://github.com/astral-sh/uv/blob/HEAD/CHANGELOG.md#073)

[Compare Source](astral-sh/uv@0.7.2...0.7.3)

##### Enhancements

-   Add `--dry-run` support to `uv self update` ([#&#8203;9829](astral-sh/uv#9829))
-   Add `--show-with` to `uv tool list` to list packages included by `--with` ([#&#8203;13264](astral-sh/uv#13264))
-   De-duplicate fetched index URLs ([#&#8203;13205](astral-sh/uv#13205))
-   Support more zip compression formats: bzip2, lzma, xz, zstd ([#&#8203;13285](astral-sh/uv#13285))
-   Add support for downloading GraalPy ([#&#8203;13172](astral-sh/uv#13172))
-   Improve error message when a virtual environment Python symlink is broken ([#&#8203;12168](astral-sh/uv#12168))
-   Use `fs_err` for paths in symlinking errors ([#&#8203;13303](astral-sh/uv#13303))
-   Minify and embed managed Python JSON at compile time ([#&#8203;12967](astral-sh/uv#12967))

##### Preview features

-   Build backend: Make preview default and add configuration docs ([#&#8203;12804](astral-sh/uv#12804))
-   Build backend: Allow escaping in globs ([#&#8203;13313](astral-sh/uv#13313))
-   Build backend: Make builds reproducible across operating systems ([#&#8203;13171](astral-sh/uv#13171))

##### Configuration

-   Add `python-downloads-json-url` option for `uv.toml` to configure custom Python installations via JSON URL ([#&#8203;12974](astral-sh/uv#12974))

##### Bug fixes

-   Check nested IO errors for retries ([#&#8203;13260](astral-sh/uv#13260))
-   Accept `musllinux_1_0` as a valid platform tag ([#&#8203;13289](astral-sh/uv#13289))
-   Fix discovery of pre-release managed Python versions in range requests ([#&#8203;13330](astral-sh/uv#13330))
-   Respect locked script preferences in `uv run --with` ([#&#8203;13283](astral-sh/uv#13283))
-   Retry streaming downloads on broken pipe errors ([#&#8203;13281](astral-sh/uv#13281))
-   Treat already-installed base environment packages as preferences in `uv run --with` ([#&#8203;13284](astral-sh/uv#13284))
-   Avoid enumerating sources in errors for path Python requests ([#&#8203;13335](astral-sh/uv#13335))
-   Avoid re-creating virtual environment with `--no-sync` ([#&#8203;13287](astral-sh/uv#13287))

##### Documentation

-   Remove outdated description of index strategy ([#&#8203;13326](astral-sh/uv#13326))
-   Update "Viewing the version" docs ([#&#8203;13241](astral-sh/uv#13241))

### [`v0.7.2`](https://github.com/astral-sh/uv/blob/HEAD/CHANGELOG.md#072)

[Compare Source](astral-sh/uv@0.7.1...0.7.2)

##### Enhancements

-   Improve trace log for retryable errors ([#&#8203;13228](astral-sh/uv#13228))
-   Use "error" instead of "warning" for self-update message ([#&#8203;13229](astral-sh/uv#13229))
-   Error when `uv version` is used with project-specific flags but no project is found ([#&#8203;13203](astral-sh/uv#13203))

##### Bug fixes

-   Fix incorrect virtual environment invalidation for pre-release Python versions ([#&#8203;13234](astral-sh/uv#13234))
-   Fix patching of `clang` in managed Python sysconfig ([#&#8203;13237](astral-sh/uv#13237))
-   Respect `--project` in `uv version` ([#&#8203;13230](astral-sh/uv#13230))

### [`v0.7.1`](https://github.com/astral-sh/uv/blob/HEAD/CHANGELOG.md#071)

[Compare Source](astral-sh/uv@0.7.0...0.7.1)

##### Enhancement

-   Add support for BLAKE2b-256 ([#&#8203;13204](astral-sh/uv#13204))

##### Bugfix

-   Revert fix handling of authentication when encountering redirects ([#&#8203;13215](astral-sh/uv#13215))

### [`v0.7.0`](https://github.com/astral-sh/uv/blob/HEAD/CHANGELOG.md#070)

[Compare Source](astral-sh/uv@0.6.17...0.7.0)

This release contains various changes that improve correctness and user experience, but could break some workflows; many changes have been marked as breaking out of an abundance of caution. We expect most users to be able to upgrade without making changes.

##### Breaking changes

-   **Update `uv version` to display and update project versions ([#&#8203;12349](astral-sh/uv#12349

    Previously, `uv version` displayed uv's version. Now, `uv version` will display or update the project's version. This interface was [heavily requested](astral-sh/uv#6298) and, after much consideration, we decided that transitioning the top-level command was the best option.

    Here's a brief example:

    ```console
    $ uv init example
    Initialized project `example` at `./example`
    $ cd example
    $ uv version
    example 0.1.0
    $ uv version --bump major
    example 0.1.0 => 1.0.0
    $ uv version --short
    1.0.0
    ```

    If used outside of a project, uv will fallback to showing its own version still:

    ```console
    $ uv version
    warning: failed to read project: No `pyproject.toml` found in current directory or any parent directory
      running `uv self version` for compatibility with old `uv version` command.
      this fallback will be removed soon, pass `--preview` to make this an error.

    uv 0.7.0 (4433f41c9 2025-04-29)
    ```

    As described in the warning, `--preview` can be used to error instead:

    ```console
    $ uv version --preview
    error: No `pyproject.toml` found in current directory or any parent directory
    ```

    The previous functionality of `uv version` was moved to `uv self version`.
-   **Avoid fallback to subsequent indexes on authentication failure ([#&#8203;12805](astral-sh/uv#12805

    When using the `first-index` strategy (the default), uv will stop searching indexes for a package once it is found on a single index. Previously, uv considered a package as "missing" from an index during authentication failures, such as an HTTP 401 or HTTP 403 (normally, missing packages are represented by an HTTP 404). This behavior was motivated by unusual responses from some package indexes, but reduces the safety of uv's index strategy when authentication fails. Now, uv will consider an authentication failure as a stop-point when searching for a package across indexes. The `index.ignore-error-codes` option can be used to recover the existing behavior, e.g.:

    ```toml
    [[tool.uv.index]]
    name = "pytorch"
    url = "https://download.pytorch.org/whl/cpu"
    ignore-error-codes = [401, 403]
    ```

    Since PyTorch's indexes always return a HTTP 403 for missing packages, uv special-cases indexes on the `pytorch.org` domain to ignore that error code by default.
-   **Require the command in `uvx <name>` to be available in the Python environment ([#&#8203;11603](astral-sh/uv#11603

    Previously, `uvx` would attempt to execute a command even if it was not provided by a Python package. For example, if we presume `foo` is an empty Python package which provides no command, `uvx foo` would invoke the `foo` command on the `PATH` (if present). Now, uv will error early if the `foo` executable is not provided by the requested Python package. This check is not enforced when `--from` is used, so patterns like `uvx --from foo bash -c "..."` are still valid. uv also still allows `uvx foo` where the `foo` executable is provided by a dependency of `foo` instead of `foo` itself, as this is fairly common for packages which depend on a dedicated package for their command-line interface.
-   **Use index URL instead of package URL for keyring credential lookups ([#&#8203;12651](astral-sh/uv#12651

    When determining credentials for querying a package URL, uv previously sent the full URL to the `keyring` command. However, some keyring plugins expect to receive the *index URL* (which is usually a parent of the package URL). Now, uv requests credentials for the index URL instead. This behavior matches `pip`.
-   **Remove `--version` from subcommands ([#&#8203;13108](astral-sh/uv#13108

    Previously, uv allowed the `--version` flag on arbitrary subcommands, e.g., `uv run --version`. However, the `--version` flag is useful for other operations since uv is a package manager. Consequently, we've removed the `--version` flag from subcommands — it is only available as `uv --version`.
-   **Omit Python 3.7 downloads from managed versions ([#&#8203;13022](astral-sh/uv#13022

    Python 3.7 is EOL and not formally supported by uv; however, Python 3.7 was previously available for download on a subset of platforms.
-   **Reject non-PEP 751 TOML files in install, compile, and export commands ([#&#8203;13120](astral-sh/uv#13120), [#&#8203;13119](astral-sh/uv#13119

    Previously, uv treated arbitrary `.toml` files passed to commands (e.g., `uv pip install -r foo.toml` or `uv pip compile -o foo.toml`) as `requirements.txt`-formatted files. Now, uv will error instead. If using PEP 751 lockfiles, use the standardized format for custom names instead, e.g., `pylock.foo.toml`.
-   **Ignore arbitrary Python requests in version files ([#&#8203;12909](astral-sh/uv#12909

    uv allows arbitrary strings to be used for Python version requests, in which they are treated as an executable name to search for in the `PATH`. However, using this form of request in `.python-version` files is non-standard and conflicts with `pyenv-virtualenv` which writes environment names to `.python-version` files. In this release, uv will now ignore requests that are arbitrary strings when found in `.python-version` files.
-   **Error on unknown dependency object specifiers ([12811](astral-sh/uv#12811

    The `[dependency-groups]` entries can include "object specifiers", e.g. `set-phasers-to = ...` in:

    ```toml
    [dependency-groups]
    foo = ["pyparsing"]
    bar = [{set-phasers-to = "stun"}]
    ```

    However, the only current spec-compliant object specifier is `include-group`. Previously, uv would ignore unknown object specifiers. Now, uv will error.
-   **Make `--frozen` and `--no-sources` conflicting options ([#&#8203;12671](astral-sh/uv#12671

    Using `--no-sources` always requires a new resolution and `--frozen` will always fail when used with it. Now, this conflict is encoded in the CLI options for clarity.
-   **Treat empty `UV_PYTHON_INSTALL_DIR` and `UV_TOOL_DIR` as unset ([#&#8203;12907](astral-sh/uv#12907), [#&#8203;12905](astral-sh/uv#12905

    Previously, these variables were treated as set to the current working directory when set to an empty string. Now, uv will ignore these variables when empty. This matches uv's behavior for other environment variables which configure directories.

##### Enhancements

-   Disallow mixing requirements across PyTorch indexes ([#&#8203;13179](astral-sh/uv#13179))
-   Add optional managed Python archive download cache ([#&#8203;12175](astral-sh/uv#12175))
-   Add `poetry-core` as a `uv init` build backend option ([#&#8203;12781](astral-sh/uv#12781))
-   Show tag hints when failing to find a compatible wheel in `pylock.toml` ([#&#8203;13136](astral-sh/uv#13136))
-   Report Python versions in `pyvenv.cfg` version mismatch ([#&#8203;13027](astral-sh/uv#13027))

##### Bug fixes

-   Avoid erroring on omitted wheel-only packages in `pylock.toml` ([#&#8203;13132](astral-sh/uv#13132))
-   Fix display name for `uvx --version` ([#&#8203;13109](astral-sh/uv#13109))
-   Restore handling of authentication when encountering redirects ([#&#8203;13050](astral-sh/uv#13050))
-   Respect build options (`--no-binary` et al) in `pylock.toml` ([#&#8203;13134](astral-sh/uv#13134))
-   Use `upload-time` rather than `upload_time` in `uv.lock` ([#&#8203;13176](astral-sh/uv#13176))

##### Documentation

-   Changed `fish` completions append `>>` to overwrite `>` ([#&#8203;13130](astral-sh/uv#13130))
-   Add `pylock.toml` mentions where relevant ([#&#8203;13115](astral-sh/uv#13115))
-   Add ROCm example to the PyTorch guide ([#&#8203;13200](astral-sh/uv#13200))
-   Upgrade PyTorch guide to CUDA 12.8 and PyTorch 2.7 ([#&#8203;13199](astral-sh/uv#13199))

### [`v0.6.17`](https://github.com/astral-sh/uv/releases/tag/0.6.17)

[Compare Source](astral-sh/uv@0.6.16...0.6.17)

#### Release Notes

##### Preview features

-   Add PyTorch v2.7.0 to GPU backend ([#&#8203;13072](astral-sh/uv#13072))

##### Bug fixes

-   Avoid panic for invalid Python versions ([#&#8203;13077](astral-sh/uv#13077))
-   Block scripts from overwriting `python` ([#&#8203;13051](astral-sh/uv#13051))
-   Check distribution names to handle invalid redirects ([#&#8203;12917](astral-sh/uv#12917))
-   Check for mismatched package and distribution names on resolver thread ([#&#8203;13088](astral-sh/uv#13088))
-   Fix panic with invalid last character in PEP 508 name ([#&#8203;13105](astral-sh/uv#13105))
-   Reject `requires-python` even if not listed on the index page ([#&#8203;13086](astral-sh/uv#13086))

#### Install uv 0.6.17

##### Install prebuilt binaries via shell script

```sh
curl --proto '=https' --tlsv1.2 -LsSf https://github.com/astral-sh/uv/releases/download/0.6.17/uv-installer.sh | sh
```

##### Install prebuilt binaries via powershell script

```sh
powershell -ExecutionPolicy Bypass -c "irm https://github.com/astral-sh/uv/releases/download/0.6.17/uv-installer.ps1 | iex"
```

#### Download uv 0.6.17

|  File  | Platform | Checksum |
|--------|----------|----------|
| [uv-aarch64-apple-darwin.tar.gz](https://github.com/astral-sh/uv/releases/download/0.6.17/uv-aarch64-apple-darwin.tar.gz) | Apple Silicon macOS | [checksum](https://github.com/astral-sh/uv/releases/download/0.6.17/uv-aarch64-apple-darwin.tar.gz.sha256) |
| [uv-x86\_64-apple-darwin.tar.gz](https://github.com/astral-sh/uv/releases/download/0.6.17/uv-x86\_64-apple-darwin.tar.gz) | Intel macOS | [checksum](https://github.com/astral-sh/uv/releases/download/0.6.17/uv-x86\_64-apple-darwin.tar.gz.sha256) |
| [uv-aarch64-pc-windows-msvc.zip](https://github.com/astral-sh/uv/releases/download/0.6.17/uv-aarch64-pc-windows-msvc.zip) | ARM64 Windows | [checksum](https://github.com/astral-sh/uv/releases/download/0.6.17/uv-aarch64-pc-windows-msvc.zip.sha256) |
| [uv-i686-pc-windows-msvc.zip](https://github.com/astral-sh/uv/releases/download/0.6.17/uv-i686-pc-windows-msvc.zip) | x86 Windows | [checksum](https://github.com/astral-sh/uv/releases/download/0.6.17/uv-i686-pc-windows-msvc.zip.sha256) |
| [uv-x86\_64-pc-windows-msvc.zip](https://github.com/astral-sh/uv/releases/download/0.6.17/uv-x86\_64-pc-windows-msvc.zip) | x64 Windows | [checksum](https://github.com/astral-sh/uv/releases/download/0.6.17/uv-x86\_64-pc-windows-msvc.zip.sha256) |
| [uv-aarch64-unknown-linux-gnu.tar.gz](https://github.com/astral-sh/uv/releases/download/0.6.17/uv-aarch64-unknown-linux-gnu.tar.gz) | ARM64 Linux | [checksum](https://github.com/astral-sh/uv/releases/download/0.6.17/uv-aarch64-unknown-linux-gnu.tar.gz.sha256) |
| [uv-i686-unknown-linux-gnu.tar.gz](https://github.com/astral-sh/uv/releases/download/0.6.17/uv-i686-unknown-linux-gnu.tar.gz) | x86 Linux | [checksum](https://github.com/astral-sh/uv/releases/download/0.6.17/uv-i686-unknown-linux-gnu.tar.gz.sha256) |
| [uv-powerpc64-unknown-linux-gnu.tar.gz](https://github.com/astral-sh/uv/releases/download/0.6.17/uv-powerpc64-unknown-linux-gnu.tar.gz) | PPC64 Linux | [checksum](https://github.com/astral-sh/uv/releases/download/0.6.17/uv-powerpc64-unknown-linux-gnu.tar.gz.sha256) |
| [uv-powerpc64le-unknown-linux-gnu.tar.gz](https://github.com/astral-sh/uv/releases/download/0.6.17/uv-powerpc64le-unknown-linux-gnu.tar.gz) | PPC64LE Linux | [checksum](https://github.com/astral-sh/uv/releases/download/0.6.17/uv-powerpc64le-unknown-linux-gnu.tar.gz.sha256) |
| [uv-s390x-unknown-linux-gnu.tar.gz](https://github.com/astral-sh/uv/releases/download/0.6.17/uv-s390x-unknown-linux-gnu.tar.gz) | S390x Linux | [checksum](https://github.com/astral-sh/uv/releases/download/0.6.17/uv-s390x-unknown-linux-gnu.tar.gz.sha256) |
| [uv-x86\_64-unknown-linux-gnu.tar.gz](https://github.com/astral-sh/uv/releases/download/0.6.17/uv-x86\_64-unknown-linux-gnu.tar.gz) | x64 Linux | [checksum](https://github.com/astral-sh/uv/releases/download/0.6.17/uv-x86\_64-unknown-linux-gnu.tar.gz.sha256) |
| [uv-armv7-unknown-linux-gnueabihf.tar.gz](https://github.com/astral-sh/uv/releases/download/0.6.17/uv-armv7-unknown-linux-gnueabihf.tar.gz) | ARMv7 Linux | [checksum](https://github.com/astral-sh/uv/releases/download/0.6.17/uv-armv7-unknown-linux-gnueabihf.tar.gz.sha256) |
| [uv-aarch64-unknown-linux-musl.tar.gz](https://github.com/astral-sh/uv/releases/download/0.6.17/uv-aarch64-unknown-linux-musl.tar.gz) | ARM64 MUSL Linux | [checksum](https://github.com/astral-sh/uv/releases/download/0.6.17/uv-aarch64-unknown-linux-musl.tar.gz.sha256) |
| [uv-i686-unknown-linux-musl.tar.gz](https://github.com/astral-sh/uv/releases/download/0.6.17/uv-i686-unknown-linux-musl.tar.gz) | x86 MUSL Linux | [checksum](https://github.com/astral-sh/uv/releases/download/0.6.17/uv-i686-unknown-linux-musl.tar.gz.sha256) |
| [uv-x86\_64-unknown-linux-musl.tar.gz](https://github.com/astral-sh/uv/releases/download/0.6.17/uv-x86\_64-unknown-linux-musl.tar.gz) | x64 MUSL Linux | [checksum](https://github.com/astral-sh/uv/releases/download/0.6.17/uv-x86\_64-unknown-linux-musl.tar.gz.sha256) |
| [uv-arm-unknown-linux-musleabihf.tar.gz](https://github.com/astral-sh/uv/releases/download/0.6.17/uv-arm-unknown-linux-musleabihf.tar.gz) | ARMv6 MUSL Linux (Hardfloat) | [checksum](https://github.com/astral-sh/uv/releases/download/0.6.17/uv-arm-unknown-linux-musleabihf.tar.gz.sha256) |
| [uv-armv7-unknown-linux-musleabihf.tar.gz](https://github.com/astral-sh/uv/releases/download/0.6.17/uv-armv7-unknown-linux-musleabihf.tar.gz) | ARMv7 MUSL Linux | [checksum](https://github.com/astral-sh/uv/releases/download/0.6.17/uv-armv7-unknown-linux-musleabihf.tar.gz.sha256) |

</details>

---

### Configuration

📅 **Schedule**: Branch creation - At any time (no schedule defined), Automerge - At any time (no schedule defined).

🚦 **Automerge**: Disabled by config. Please merge this manually once you are satisfied.

♻ **Rebasing**: Whenever MR becomes conflicted, or you tick the rebase/retry checkbox.

🔕 **Ignore**: Close this MR and you won't be reminded about this update again.

---

 - [ ] <!-- rebase-check -->If you want to rebase/retry this MR, check this box

---

This MR has been generated by [Renovate Bot](https://github.com/renovatebot/renovate).
<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiIzOS4yNTcuOCIsInVwZGF0ZWRJblZlciI6IjM5LjI2NC4wIiwidGFyZ2V0QnJhbmNoIjoibWFpbiIsImxhYmVscyI6WyJSZW5vdmF0ZSBCb3QiXX0=-->
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
breaking A breaking change enhancement New feature or improvement to existing functionality network Network connectivity e.g. proxies, DNS, and SSL
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants