We're announcing two new security features for uv:

  • uv audit is a new command that scans your dependencies for known vulnerabilities and "adverse" project statuses (such as being deprecated). It's a uv-native alternative to pip-audit, and is between 4x and 10x1 faster on typical projects.
  • uv add, uv sync, etc. can now perform a lightweight OSV-based lookup for previously-resolved malware on every sync operation. This feature is not enabled by default, but you can try it by setting UV_MALWARE_CHECK=1.

Both of these features are in preview for now. They're considered unstable and there may be breaking changes as we iterate on their design. We encourage you to read our docs, try these out and share your feedback with us!

Output from uv audit, identifying vulnerabilities in dependencies on aiohttp and pyjwt

Why uv audit?

Developers are reasonably and rightfully concerned about the security of their dependencies, and expect tooling that helps them address those concerns. This is particularly true with the rise of "supply chain" concerns, including:

  • Large, complex and/or opaque dependency graphs;
  • Year-over-year increases in the number of known (i.e. disclosed) vulnerabilities;
  • Decreases in the cost of discovering vulnerabilities, in part due to recent advances in LLM-guided vulnerability discovery and exploitation.

At the same time, Python packaging already has tools for auditing dependencies, like pip-audit and safety. So why build a new tool? Two big reasons:

  • A uv-native experience: vulnerability scanning should be a first-class citizen in engineering flows, rather than an afterthought. By building auditing directly into uv, we can ensure that users receive a consistent experience and don't need to integrate a separate tool into their development and testing workflows.

    Similarly, we believe that a vulnerability scanning should take full advantage of its surrounding context. In uv's case, that means accelerating audits by leveraging uv's locked resolutions, as well as supporting configuration in uv.toml and pyproject.toml.

  • Performance: implementing auditing in uv means we can take advantage of performant primitives for networking, caching, package resolution, parsing, etc.

Why malware scanning?

The dependency risk environment has shifted dramatically over the past few years: remediating normal vulnerabilities continues to be critical, but users are increasingly affected by actively malicious dependencies as well.

The threat of malware in open source dependencies has a subtly different detection and remediation profile than ordinary vulnerabilities:

  • Ordinary vulnerabilities often require only passive remediation: patching to a non-vulnerable version is often sufficent unless the vulnerability is being actively exploited.

    By contrast, malicious dependencies tend to require active remediation: malware frequently steals credentials or other sensitive materials. This means that a malware presence signal requires immediate response.

  • Malware is remediated in parallel by both users and indices: PyPI, for example, will quarantine known and suspected malware to limit its spread.

    This is a critical part of remediation, but it presents a new challenge for locking installers like uv: quarantine status means that the malware gets removed from the index, but a lockfile that points directly to the underlying object storage is still capable of directly installing malicious distributions2.

Together, these differences mean that defending users against malicious packages requires a different approach than a discrete uv audit command.

The approach we're exploring is a lightweight malware check when installing packages: whenever you run uv add or any other command that causes a sync, uv will ask OSV whether it has any MAL advisories for your currently locked resolution. If a package in your lockfile matches any known3 malware advisories, the sync will be terminated before the malicious code has a chance to run.

Malware checks are currently opt-in: you'll need to set UV_MALWARE_CHECK=1 to enable them. We'll evaluate enabling them by default in a future release.

Looking forwards

The design space for vulnerability and malware scanning and management is large. These new features are in preview to give us the flexibility to iterate on their design.

Some of the things we're evaluating:

  • Future proofing through further integration: our broader perspective on tooling is that tight integrations between historically devolved features unlock new capabilities that the next generation of developers will take for granted.

    At the moment, the typical developer's experiences with vulnerability scanning is either discrete (performing audits as a separate step in CI) or frustratingly integrated (npm install telling you that you have hundreds of vulnerabilities at exactly the moment you don't want to deal with them). But there's a third way: tools that adapt based on known vulnerabilities and other security signals.

    For example, imagine a future in which locking a resolution also takes into account any known vulnerabilities among candidate versions, and produces a resolution that preserves compatibility while also minimizing the number and severity of known vulnerabilities.

    Similarly, imagine a future in which uv add tells you about vulnerabilities, but only when they're present in a newly added dependency. This is a plausible way to reduce some of the alert fatigue that comes with the npm install-style approach: users still receive in-flow alerts, but only on dependencies they're considering depending on.

    The only way to build futures like these is with tight integrations between vulnerability scanning and package management.

  • Improving finding precision: vulnerabilities typically only affect specific APIs within a dependency, and users should not be presented with alerts that do not actually interact with their usage. Doing this requires the participation of three traditionally separated groups of Python tooling (vulnerability management, packaging, and static source/reachability analysis); we're actively looking at ways to better integrate uv and ty's views of the world to provide this kind of functionality.

  • Support for vulnerability "backends" other than OSV, such as PYSEC (via PyPI's JSON API) and ecosyste.ms. These backends have different tradeoffs in terms of coverage, freshness, cacheability, query performance, and so forth. We'll evaluate each as we consider integrating it.

  • Support for other packaging formats, such as requirements.txt and PEP 751's pylock.toml.

You can see more on our roadmap as well.

Footnotes

  1. Comparisons between uv audit and pip-audit are slightly apples-to-oranges, since the representative uses of the two are often different. The short version: uv audit is significantly faster than most equivalent pip-audit invocations, but using pip-audit with a fully-primed cache can be roughly as fast as uv audit.

  2. When PyPI deleted (or in this case, quarantines) a distribution, it does not typically delete that distribution from the underlying object storage; only the index itself gets updated. In other words, the "pointer" to the distribution is deleted, not the distribution itself.

    The reasons for this are threefold: (1) modifying the underyling object storage is expensive, (2) deletions sometimes need to be reverted, and reverting the index deletion is cheaper and simpler than restoring the object, and (3) Python packaging as a whole considers the index to be the "canonical" view of all resolvable distributions, so deletion from the index is intended to be logically equivalent to "hard" deletion.

    By contrast, locking installers like uv (along with pip when using pylock.toml) store direct references to distributions in the object storage. This is a performance advantage (the installer doesn't need to fetch index responses after a resolution has been locked), but also means that index-level metadata changes like quarantine status cease to be visible at the lockfile layer.

  3. It's important to note that the current malware check implementation relies on public advisories: malware (particularly within compromised packages) is typically not detected immediately after the it becomes publicly visible. This is why we recommend employing dependency cooldowns — it gives security parties in the ecosystem a chance to review packages before you get exposed to them.