When attackers compromised Ultralytics YOLO, a popular real-time object detection machine-learning package for Python, most assumed the Python Package Index, or PyPI, must be the point of failure. That made sense because the tampered software artifact was first found on PyPI. Moreover, the Python software repository has become a major attack vector for one of the software world’s most popular languages.

But it turned out the compromised PyPI package was just a symptom and the real exploit lay elsewhere—a sophisticated and daring compromise of a common GitHub build mechanism. Now that the dust has started to settle, it’s a good time to consider the three big takeaways from the Ultralytics AI library hack.

Python’s own supply chain wasn’t the point of compromise

Most developers are rightly aware of PyPI as a compromise point in the Python supply chain. Existing, high-traffic PyPI projects need only be compromised for a brief time to spread a malicious package to thousands of victims. Abandoned or little-used PyPI packages also pose a security risk. The Ultralytics hack at first seemed like yet another case of PyPI being compromised, perhaps through stolen developer credentials or a compromised contributor machine.

The reality was entirely different. The attackers leveraged a known exploit in GitHub Actions (in fact, a regression of a previously-patched vulnerability) to capture an automated build process. This let them deliver a compromised package to PyPI without attracting scrutiny. Because no compromised code showed up on GitHub itself, only on PyPI, the first impulse was to blame PyPI’s security or processes. But this proved misleading.

PyPI has many internal security and safety challenges, some of which echo issues experienced by the NPM ecosystem: typosquatting, dependency confusion, and so on. This attack constituted an end run around the protections layered in place against those challenges. Ultimately, there may be no good defenses on PyPI’s side against such an exploit.

Every API is a possible point of security failure

The automated work done on modern software development and delivery platforms is driven by APIs like those that power GitHub Actions. It is tempting to assume that everything’s okay if a given API endpoint can only be used by a properly credentialed user with permissions to perform a specific action (e.g., “publish this package to GitHub after making these changes”).

But every single API is a potential point of failure and warrants aggressive auditing—especially when the API in question is a key link in automating the software distribution ecosystem. This exploit succeeded by attacking a point in the supply chain that is quietly taken for granted, and thus easy to overlook.

This also was not the first time GitHub Actions has been a point of failure for a Python project. Back in January 2024, researchers demonstrated how to hijack GitHub Actions workflows to compromise the development infrastructure for the PyTorch project. Thousands of other projects using GitHub Actions were shown to be vulnerable, as well, in part because they shared a similarly unsafe practice: using self-hosted infrastructure to run the GitHub Actions build agents for the sake of flexibility and convenience.

But at that scale, the problem seemed less a matter of developers shirking their duty to implement GitHub Actions best practices, and more about generally unsafe defaults for GitHub Actions. The bigger the project and the larger the contributor base, the broader the attack surface is for any automated process that’s used to deliver artifacts to the world at large. All of this points to a greater need for sane defaults for widely used systems like GitHub Actions, even if those defaults mean less functionality out of the box.

The Python software supply chain is a prime target

The more popular the software ecosystem, the more likely it will be targeted. As Python’s popular ascent continues, so will attacks on its ecosystem. And these will come on many fronts, both direct and indirect.

What makes Python particularly susceptible isn’t only its popularity but its unique place in the software ecosystem. Python plays at least two key roles that make it an appealing vector for compromises:

  • Process automation: Python is often used to stitch together multiple parts of a project by providing a common foundation for things like running tests or performing intermediate build steps. If you hijack a project’s automation tool, you can compromise every other aspect of the project by proxy. The GitHub Actions compromise offers a template for future attacks: Exploit a little-scrutinized aspect of software delivery automation and take control of some aspect of the project’s management.
  • Machine learning/AI: More businesses are adding AI to their product portfolios or internal processes, and Python’s ecosystem offers ways to develop both end-facing products and a convenient playground for experimenting with AI technology. A compromised machine learning library could have wide-ranging access to a company’s internal resources for such projects, like proprietary data used to train equally proprietary models.

The Ultralytics attack was relatively unambitious, with its payload being a cryptominer and thus easy to detect forensically. But more ambitious compromises can deliver advanced persistent threats into infrastructure. Python’s growing prominence, what it does, and what it’s meant to accomplish will make it more of a target going forward.