Popular Python Package Becomes Crypto Miner
Based on The PrimeTime's video on YouTube. If you like this content, support the original creators by watching, liking and subscribing to their content.
Ultralytics’ CI/CD was compromised by exploiting pull_request_target privileged execution combined with unsafe template interpolation in a composite GitHub Action.
Briefing
A widely used Python vision library, Ultralytics, was compromised through its GitHub Actions pipeline and used to publish multiple malicious PyPI releases containing a crypto miner and credential-stealing behavior. The key impact: attackers didn’t need to break into every downstream user’s machine directly—poisoned CI/CD steps and package distribution channels pushed harmful code to PyPI, where it was available to users for roughly 13 hours before takedowns.
The incident began with an exploitation of Ultralytics’ CI workflow. An attacker used a pull request workflow to gain privileged execution in the “pull_request_target” context, then combined that with a template injection in a composite GitHub Action. That injection allowed untrusted branch names to be expanded into a shell context without proper escaping, enabling the attacker to run arbitrary code with the permissions of a normal privileged workflow. From there, the attacker pivoted to compromising the PyPI publishing process.
Once code execution was achieved in the CI environment, the attacker appears to have used stolen API credentials to publish a malicious package version (first identified as 8341) directly to PyPI. The release process was “trusted publishing,” meaning PyPI publishing was automatically authorized by the CI workflow without additional manual sign-offs or extra deployment protections. That automation removed friction: once the workflow was compromised, a publish event could fire immediately.
Investigators also found evidence consistent with “cache poisoning.” Ultralytics’ CI likely pulled dependencies through cached artifacts, and those caches were tampered with so that subsequent builds would retrieve malicious packages under legitimate names. Additional clues pointed to poisoning of the pip cache server URL as well, strengthening the case that the attacker redirected dependency resolution to a malicious registry while keeping the expected package identifiers.
The attacker’s activity didn’t stop at the first release. Within about 36 hours, two more malicious versions (45 and 46) were pushed to PyPI and later deleted. Transparency logs and action logs showed the malicious releases were triggered by pushes to the main branch, and the attacker removed an “actor check” that previously restricted who could publish the YAML workflow—effectively widening who could trigger the publishing pipeline.
Beyond the miner, analysis of payload artifacts suggested token theft and exfiltration. The malicious scripts searched for secrets (including GitHub tokens), base64-encoded results, and exfiltrated them to external endpoints. Investigators also traced related attacker infrastructure to deleted GitHub identities and gists, including experimentation with similar template-injection techniques in earlier attempts. A static analysis tool for GitHub Actions (referred to as zizmor) flagged key insecure patterns in the exploit chain—especially unsafe trigger configuration and template injection patterns—though it had not yet audited composite-action template injection in the specific Ultralytics action at the time.
Overall, the incident underscores how a single CI/CD weakness—especially one involving pull_request_target plus unsafe templating—can cascade into supply-chain compromise. It also highlights why automated trusted publishing and dependency caching can turn a CI breach into a fast-moving distribution attack, leaving downstream users exposed until the malicious versions are removed.
Cornell Notes
Ultralytics, a popular Python package, was compromised via GitHub Actions. Attackers exploited a pull_request_target workflow combined with unsafe template interpolation in a composite action, allowing arbitrary code execution in a privileged CI context. With stolen PyPI publishing credentials and trusted publishing automation, they published malicious Ultralytics versions to PyPI (including 8341), and later pushed additional malicious versions that were subsequently deleted. Evidence also points to cache poisoning—redirecting dependency retrieval so builds would incorporate attacker-controlled packages. The result was a supply-chain incident where malicious code was available to users for about 13 hours, demonstrating how CI/CD misconfigurations can quickly become distribution-level attacks.
How did the attacker turn a pull request into privileged code execution?
Why did trusted publishing make the PyPI compromise move faster?
What is “cache poisoning” in this context, and what evidence supported it?
What did the malicious payload appear to do besides mining?
How did attackers enable publishing by removing a safety control?
What role did automated GitHub Actions auditing play in identifying the exploit chain?
Review Questions
- What specific combination of GitHub Actions features created the privileged execution path (name the workflow context and the injection mechanism)?
- Why does trusted publishing increase the blast radius of a CI compromise compared with manual release approvals?
- What indicators in the investigation supported the cache poisoning hypothesis, and how would that change what code ends up in a released wheel/sdist?
Key Points
- 1
Ultralytics’ CI/CD was compromised by exploiting pull_request_target privileged execution combined with unsafe template interpolation in a composite GitHub Action.
- 2
Malicious branch-name content was injected into shell execution due to missing escaping/quoting, enabling arbitrary code execution in the workflow context.
- 3
Stolen credentials and trusted publishing allowed attackers to publish malicious Ultralytics versions directly to PyPI with minimal friction.
- 4
Multiple malicious releases were pushed (including 8341 and later versions 45 and 46) and were available to users for roughly 13 hours before deletion/takedowns.
- 5
Evidence indicates cache poisoning, including likely manipulation of pip cache server behavior so builds pulled attacker-controlled dependencies under expected names.
- 6
The attacker removed an actor restriction that limited who could trigger the publishing workflow, widening who could cause main-branch publish events.
- 7
Static analysis tools flagged major insecure patterns, but composite-action template injection coverage lagged, leaving gaps that the incident exploited.