Get AI summaries of any video or article — Sign up free
Popular Python Package Becomes Crypto Miner thumbnail

Popular Python Package Becomes Crypto Miner

The PrimeTime·
5 min read

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.

TL;DR

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?

The workflow used pull_request_target, which runs in a context with elevated permissions compared with typical pull_request. The exploit then relied on a template injection in a composite GitHub Action: a branch name (from the PR) was interpolated into a shell script without proper escaping/quoting. That made it possible for the attacker to inject payload content into the shell execution environment, effectively running attacker-controlled commands with the workflow’s privileges.

Why did trusted publishing make the PyPI compromise move faster?

Ultralytics used PyPI trusted publishing, meaning releases were authorized automatically by the CI workflow rather than requiring additional deployment environment approvals or sign-offs. Once the CI workflow was compromised and publishing credentials were available (or could be stolen), the publish step could trigger immediately, pushing malicious versions to PyPI with little delay.

What is “cache poisoning” in this context, and what evidence supported it?

Cache poisoning here means the build’s dependency resolution was manipulated so that when the CI later requested packages, it retrieved attacker-controlled content under legitimate package names. Investigators noted fixes that removed cash/pip poisoning from setup.py-related behavior and observed indicators consistent with a poisoned pip cache server URL being used during the build, aligning with the idea that dependency downloads were redirected to a malicious registry.

What did the malicious payload appear to do besides mining?

Payload analysis described scripts that searched for secrets (including GitHub tokens), transformed findings (e.g., base64 encoding), and exfiltrated them to external endpoints. This suggests the compromise wasn’t limited to CPU theft; it also aimed at credential theft and further access.

How did attackers enable publishing by removing a safety control?

Investigators found that the triggering commit for the first malicious release both bumped the package version and removed an “actor check” that limited who could publish the YAML workflow from the main branch. With that check gone, the publishing workflow could be triggered by a broader set of actors, making it easier for the attacker’s PR-driven path to reach PyPI.

What role did automated GitHub Actions auditing play in identifying the exploit chain?

A static analysis tool referred to as zizmor detected key insecure patterns in the exploit chain, including unsafe trigger configuration and template injection patterns similar to those used in the incident. However, it was noted that composite-action template injection wasn’t fully supported yet, which limited detection for the exact Ultralytics composite action behavior at the time.

Review Questions

  1. What specific combination of GitHub Actions features created the privileged execution path (name the workflow context and the injection mechanism)?
  2. Why does trusted publishing increase the blast radius of a CI compromise compared with manual release approvals?
  3. 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. 1

    Ultralytics’ CI/CD was compromised by exploiting pull_request_target privileged execution combined with unsafe template interpolation in a composite GitHub Action.

  2. 2

    Malicious branch-name content was injected into shell execution due to missing escaping/quoting, enabling arbitrary code execution in the workflow context.

  3. 3

    Stolen credentials and trusted publishing allowed attackers to publish malicious Ultralytics versions directly to PyPI with minimal friction.

  4. 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. 5

    Evidence indicates cache poisoning, including likely manipulation of pip cache server behavior so builds pulled attacker-controlled dependencies under expected names.

  6. 6

    The attacker removed an actor restriction that limited who could trigger the publishing workflow, widening who could cause main-branch publish events.

  7. 7

    Static analysis tools flagged major insecure patterns, but composite-action template injection coverage lagged, leaving gaps that the incident exploited.

Highlights

The exploit chain hinged on pull_request_target plus a composite-action template injection where branch names were expanded into an unescaped shell context.
Trusted publishing turned a CI compromise into an immediate PyPI distribution event, enabling malicious releases without extra approvals.
Investigators found signs consistent with cache poisoning—dependency retrieval was redirected so poisoned packages could be incorporated into released artifacts.
Malicious versions were available on PyPI for about 13 hours, and additional releases followed within 36 hours before being deleted.
Beyond mining, payload analysis pointed to secret/token theft and exfiltration behavior.

Topics

  • Supply-Chain Attacks
  • GitHub Actions
  • PyPI Publishing
  • CI/CD Security
  • Cache Poisoning

Mentioned

  • Seth Larson
  • Aden Con
  • Andy Lindenman
  • Glenn
  • Jeff Ismer
  • Jeff
  • Morning Joe
  • Atkins
  • Glenn Glenn
  • CI
  • API
  • PR
  • PyPI
  • CI/CD
  • IOCs
  • IOC
  • UUID
  • OS
  • URL