A deep dive into how uv's exclude-newer handles PyPI supply chain attack vectors, with reference to the LiteLLM/TeamPCP incident (March 2026).
TeamPCP compromised the Trivy GitHub Action, which exfiltrated LiteLLM's PYPI_PUBLISH token from CI. They then published two malicious versions directly to PyPI:
| Version | Time (UTC) | Technique | Trigger |
|---|---|---|---|
| 1.82.7 | 10:39 | Payload in proxy_server.py |
import litellm.proxy |
| 1.82.8 | 10:52 | litellm_init.pth in site-packages |
Every Python startup — no import needed |
The .pth file in 1.82.8 was particularly dangerous: Python auto-executes .pth files on interpreter startup, meaning even pip install, python -c, or an IDE's language server would trigger the credential stealer.
uv's exclude-newer checks the per-file upload timestamp (via PEP 700's upload-time field) for each wheel/sdist on PyPI.
From crates/uv-resolver/src/version_map.rs:
let (excluded, upload_time) = if let Some(exclude_newer) = &self.exclude_newer {
match file.upload_time_utc_ms.as_ref() {
// File uploaded AFTER cutoff → EXCLUDE
Some(&upload_time) if upload_time >= exclude_newer.timestamp_millis() => {
(true, Some(upload_time))
}
// File has NO upload time → EXCLUDE (conservative)
None => {
(true, None)
}
// File uploaded BEFORE cutoff → ALLOW
_ => (false, None),
}
} else {
(false, None)
};Key behaviors:
| Scenario | Result |
|---|---|
upload_time >= exclude_newer |
Excluded |
upload_time < exclude_newer |
Allowed |
upload_time missing |
Excluded (conservative) |
exclude_newer not set |
All allowed |
Protected? Yes.
If UV_EXCLUDE_NEWER=2026-03-10T00:00:00Z, the malicious versions (uploaded March 24) are blocked because their upload time exceeds the cutoff.
An attacker with maintainer access uploads a higher build tag to an existing version:
package-1.0.0-py3-none-any.whl # original, uploaded 2025-01-01
package-1.0.0-1-py3-none-any.whl # attacker's, uploaded 2026-03-24
pip would prefer the higher build tag. But uv checks per-file upload time, so the new wheel is excluded independently of the version number.
Protected? Yes — the new file's upload time is after the cutoff.
An attacker uploads a more specific wheel to shadow the generic one:
package-1.0.0-py3-none-any.whl # original, uploaded 2025-01-01
package-1.0.0-cp310-none-any.whl # attacker's, uploaded 2026-03-24
pip/uv would normally prefer the more specific cp310 wheel.
Protected? Yes — same reason, per-file upload time check.
If the index itself is compromised and serves a different file with a spoofed upload time (claiming it was uploaded long ago):
Protected? No. exclude-newer trusts the upload-time field from the index. If the index lies, the check is bypassed. This is where lockfile hash pinning (uv sync --locked) provides the real defense.
If the attacker uploads malicious code before the exclude-newer cutoff (e.g., a sleeper attack), the timestamp check won't help.
Protected? No. This is a time-of-check problem — exclude-newer only protects against packages uploaded after the cutoff.
PyPI provides upload-time for all packages (PEP 700). But private registries may not.
Protected? Yes, conservatively — uv excludes files with missing upload times when exclude-newer is set, and warns the user.
| Attack Vector | exclude-newer Protects? |
Notes |
|---|---|---|
| New malicious version | ✅ Yes | Blocked by upload timestamp |
| Build tag shadowing | ✅ Yes | Per-file timestamp check |
| Platform wheel injection | ✅ Yes | Per-file timestamp check |
| Compromised/MITM index | ❌ No | Spoofed upload-time bypasses check |
| Sleeper attack (pre-cutoff) | ❌ No | Already within the allowed window |
| Missing upload-time | ✅ Yes | Conservative: excluded by default |
No single mechanism is sufficient. The recommended stack:
uv.lockwith hash pinning — rejects any file not matching locked SHA256 hashesuv sync --locked— refuses to install if lockfile is staleexclude-newer/ dependency cooldowns — time-based filter blocks very recent uploads- Review lockfile diffs in PRs — new hashes for unchanged versions are a red flag
- PyPI Trusted Publishers (OIDC) — limits who can upload, tied to CI identity
- 2FA on PyPI accounts — reduces account takeover risk