---
title: "TeamPCP Compromises LiteLLM: Credential Stealer in PyPI, 70 Repos Exposed"
tldr: "TeamPCP published two malicious litellm versions to PyPI containing a .pth infostealer that runs on every Python startup. A compromised maintainer account was then used to silence the disclosure, deface repositories, and expose 70 private BerriAI repos in minutes. This is a Boost Security contribution to a broader community investigation: multiple teams worked this incident in parallel, each bringing their own angle. We focused on CI/CD forensics and GitHub account takeover evidence. The hunt continues."
author: François Proulx
date: 2026-03-24
category: threat-intel
tags:
  - supply-chain
  - pypi
  - github
  - ci-cd
  - infostealer
banner: /images/articles/teampcp-litellm-supply-chain-compromise-banner.png
bannerAlt: "TeamPCP Compromises LiteLLM"
---

**TL;DR**: TeamPCP published two malicious `litellm` versions to PyPI (1.82.7 and 1.82.8) containing a `.pth` infostealer that executes on every Python startup, no `import` required. Using a compromised GitHub account, the attacker closed the public security disclosure, defaced 15 org repositories, wiped 182 personal repos, and made 70 private BerriAI repositories public in a scripted sweep. This report is a Boost Security contribution to a broader community investigation: many researchers worked the same incident in parallel and we focused on what our CI/CD expertise and threat hunting infrastructure could add that was novel. The hunt continues.

---

> **Note:** This article is one piece of a distributed community investigation. Several teams published independent analyses in parallel; we coordinated where we could and each brought different angles. Our specific focus: CI/CD forensics, GitHub account takeover evidence, and botnet attribution using our threat hunting data lake. If you have additional evidence, please reach out to [labs@boostsecurity.io](mailto:labs@boostsecurity.io).

## A community investigation

When the first public warning of this attack surfaced on March 24, the security community mobilized fast and in parallel. Researchers on Telegram, X, GitHub, and Hacker News were all pulling threads simultaneously. No single team had the full picture; the picture assembled from many overlapping contributions. That is how incident response on live OSS supply chain attacks actually works at its best.

Our role in this investigation was specific: we brought CI/CD forensics, our threat hunting data lake (indexed GitHub Archive events), and our prior work on the Trivy compromise as context for attribution. Where others had already established facts, we cross-checked and cited rather than re-derive. The credits section at the end of this article lists the researchers and organizations whose work we built on; read their analyses too.

Two researchers deserve special mention for getting this hunt started.

**Adnan Khan** was monitoring the `team_pcp` Telegram channel on the morning of March 24 when two messages appeared in quick succession:

> "TeamPCP gonna do another large Supply chain attack, be ready for it" (7:50 AM, 97 views)
>
> "+35k stars github repo 😮" (8:13 AM, 69 views)

Adnan immediately recognized this as an active warning and shared it with us. The "+35k stars" clue was the starting point for identifying the target.

**Rami McCarthy** identified `litellm`. Within hours, Rami published a [detailed thread on X](https://x.com/ramimacisabird/status/2036426565102227803) documenting the malicious `litellm_init.pth` payload, the PyPI compromise, and the `models.litellm.cloud` exfiltration endpoint. He also directed the investigation toward `krrishdholakia`'s GitHub account as the pivot point and connected BerriAI's CircleCI pipeline to the upstream Trivy attack.

## Context: the same actor, a new target

In our [previous report](/articles/20-days-later-trivy-compromise-act-ii), we documented how TeamPCP weaponized the `aqua-bot` service account on March 19, 2026 to push a poisoned `v0.69.4` Trivy release across GitHub Releases, Docker registries, and 75 of 76 `trivy-action` tags. The attacker's signature: taunting, automation, and maximum-impact destructive actions using stolen credentials.

The litellm compromise follows the exact same playbook. Steal credentials quietly, use them for supply chain impact (poisoned PyPI package), then end with public taunting and a destructive visibility operation. The calling card ("teampcp owns BerriAI" in a repository description) matches the "teampcp owns you" taunts posted during the Trivy incident on March 20 at 00:01 UTC.

Same actor. Same campaign. New victim. And litellm is far from the only one: Rami McCarthy's [TeamPCP IOC tracker](https://ramimac.me/trivy-teampcp/) documents a growing list of second-order victims over the past week, including components from Checkmarx. We expect more disclosures.

The Trivy compromise gave TeamPCP access to credentials across every project that ran the poisoned binary. Working through that stash takes time. Meanwhile, the wider LAPSUS$ community appears to be rallying around TeamPCP's operation, energized by the scale of what was taken, a dynamic visible in the `team_pcp` Telegram channel, where members were openly coordinating before and after the attack (screenshot below). The pace of follow-on compromises suggests TeamPCP currently has more opportunities than they have hours in the day to exploit them.

![TeamPCP Telegram channel warning screenshot](/images/articles/lapsus-telegram-teampcp-warning.jpg)

## Initial access: the Trivy connection (our working hypothesis)

**Note:** A LiteLLM maintainer posted on [Hacker News](https://news.ycombinator.com/item?id=47502858) stating "Looks like this originated from the trivvy used in our ci/cd" and linking to Rami McCarthy's TeamPCP IOC page. Worth noting: that page is itself based on community research (including ours), so this is not independent internal forensics from BerriAI. The maintainer is pointing at the same public hypothesis we all are. There is still no hard smoking gun. BerriAI's investigation is ongoing.

**The maintainer also confirmed:** users running the proxy via Docker were not impacted, as those builds pin versions in `requirements.txt`.

---

The section below documents our independent investigation of the possible delivery paths.

BerriAI's CircleCI pipeline includes a `litellm_security_tests` job that installs Trivy via the Aqua Security APT repository:

```yaml
# .circleci/config.yml, line 720
sudo apt-get install -y trivy
```

The obvious hypothesis was that the poisoned `v0.69.4` Trivy binary landed in this CI job. We checked. It did not. [CircleCI logs for pipeline 70146 / job 1427016](https://app.circleci.com/pipelines/github/BerriAI/litellm/70146/workflows/c082840a-c872-4454-b6e6-e03b26f1c32e/jobs/1427016/parallel-runs/0/steps/0-110) (the most recent run we could inspect) show the APT channel serving `trivy amd64 0.69.3` (the clean release) across all sampled runs between March 19 and March 24.

BerriAI also does not use `aquasecurity/trivy-action` in GitHub Actions (no references found across `.github/workflows`), so the 75-tag force-push vector does not apply here either.

**Important correction on Homebrew:** Homebrew builds Trivy from source. The poisoned `v0.69.4` commit only modified workflow YAML files; the backdoor Go source files were injected at runtime by the imposter `actions/checkout` during the GitHub Actions release build, and were never committed to the repository. Homebrew's `go build` compiled clean source code. The Homebrew vector is not valid for this compromise. (This same correction appears in our [Trivy Act II article](/articles/20-days-later-trivy-compromise-act-ii).)

**Where does that leave us?** The Trivy distribution channels that were genuinely poisoned are: the GitHub Release binaries, Docker images (`aquasec/trivy:0.69.4`, GHCR, ECR), and the force-pushed `trivy-action` tags. If the Trivy vector is correct, the most likely path is a developer or CI job that pulled the poisoned binary directly from GitHub Releases, or used the Docker image. The exact machine, session, and channel remain unconfirmed. Other vectors cannot be ruled out without BerriAI's own forensic investigation.

### We also checked for a GitHub Actions pwn request

Given our previous work on [Pwn Requests](/articles/megagame10418-the-user-behind-hackerbot-claw), we scanned `BerriAI/litellm`'s workflows for exploitable `pull_request_target` patterns. We found nothing of meaningful impact. The one notable artifact is a [since-deleted `pull_request_target` workflow](https://github.com/BerriAI/litellm/blob/c7d3198d9ac0a349287fcdd1e85e43b7392f74c0/.github/workflows/check_duplicate_prs.yml) that would not have been exploitable for credential exfiltration anyway. This vector is effectively ruled out.

## The malicious PyPI package

Without corresponding GitHub release tags and outside BerriAI's normal CircleCI-based release pipeline, the attacker published two malicious versions directly to PyPI using exfiltrated credentials. [OpenSourceMalware's deep-dive analysis](https://opensourcemalware.com/pypi/litellm) documents both:

| Version | Payload location | Trigger |
|---------|-----------------|---------|
| `1.82.7` | `litellm/proxy/proxy_server.py` | `import litellm.proxy` |
| `1.82.8` | `litellm_init.pth` (34,628 bytes) | **Any Python startup — no import needed** |

The escalation from 1.82.7 to 1.82.8 reveals attacker intent: embedding the payload in `proxy_server.py` required the victim to explicitly import the proxy module. By adding a `.pth` file in 1.82.8, the attacker eliminated that dependency entirely.

```
litellm_init.pth  (34,628 bytes)
sha256: ceNa7wMJnNHy1kRnNCcwJaFjWX3pORLfMh7xGL8TUjg
```

A `.pth` file placed in `site-packages/` is processed automatically on every Python startup. This works because Python's [`site` module](https://docs.python.org/3/library/site.html) (which handles `.pth` files) is itself automatically imported during interpreter initialization. The docs state: "This module is automatically imported during initialization." No `import litellm` is required, and there is no opt-out short of launching Python with the `-S` flag. Installing the package is sufficient for the payload to run in every subsequent Python process on that machine. The payload itself was double base64-encoded to evade naive detection.

All stolen data was exfiltrated to `https://models.litellm.cloud/` (a typosquat of the legitimate `litellm.ai` domain, designed to blend into network logs of any organization already using LiteLLM).

### Scope of credentials at risk

The `litellm_security_tests` CircleCI job had over 230 secrets in scope. If the poisoned Trivy binary executed in a context with these variables loaded (or if they were present as local files on the compromised developer machine), all of them were potentially exfiltrated. The full variable list is available as a [raw text file](/files/articles/litellm-circleci-env-vars.txt). Every value is `**REDACTED**` as CircleCI masks project secrets in logs, but the variable names alone map the full blast radius: PyPI publish credentials, GitHub tokens, AWS/Azure/GCP keys, HashiCorp Vault and Infisical tokens, production database URLs, DockerHub credentials, Redis clusters, Slack webhooks, Sentry, PagerDuty, Datadog, and API keys for every LLM provider BerriAI integrates with.

<div style="max-height: 320px; overflow-y: auto; border: 1px solid var(--color-border, #e2e8f0); border-radius: 6px; padding: 1rem; background: var(--color-code-bg, #f8fafc); margin: 1rem 0;">
<pre style="margin: 0; font-size: 0.78rem; line-height: 1.5;">A2A_AGENT_URL=**REDACTED**
A2A_API_BASE=**REDACTED**
AI21_API_KEY=**REDACTED**
AIML_API_KEY=**REDACTED**
ANTHROPIC_API_KEY=**REDACTED**
ANYSCALE_API_KEY=**REDACTED**
APORIA_API_BASE_1=**REDACTED**
APORIA_API_BASE_2=**REDACTED**
APORIA_API_KEY_1=**REDACTED**
APORIA_API_KEY_2=**REDACTED**
ARIZE_API_KEY=**REDACTED**
ARIZE_SPACE_KEY=**REDACTED**
ASSEMBLYAI_API_KEY=**REDACTED**
AWS_ACCESS_KEY_ID=**REDACTED**
AWS_ACCESS_KEY_ID_2=**REDACTED**
AWS_REGION_NAME=**REDACTED**
AWS_REGION_NAME_2=**REDACTED**
AWS_SECRET_ACCESS_KEY=**REDACTED**
AWS_SECRET_ACCESS_KEY_2=**REDACTED**
AWS_TEMP_ACCESS_KEY_ID=**REDACTED**
AWS_TEMP_ROLE_NAME=**REDACTED**
AWS_TEMP_SECRET_ACCESS_KEY=**REDACTED**
AZURE_AI_API_BASE_MISTRAL=**REDACTED**
AZURE_AI_API_KEY_MISTRAL=**REDACTED**
AZURE_AI_COHERE_API_BASE=**REDACTED**
AZURE_AI_COHERE_API_KEY=**REDACTED**
AZURE_AI_COHERE_API_KEY_2=**REDACTED**
AZURE_AI_MISTRAL_API_BASE=**REDACTED**
AZURE_AI_MISTRAL_API_KEY=**REDACTED**
AZURE_AI_OPENAI_KEY=**REDACTED**
AZURE_AI_SEARCH_EMBEDDING_API_BASE=**REDACTED**
AZURE_AI_SEARCH_EMBEDDING_API_KEY=**REDACTED**
AZURE_ANTHROPIC_API_KEY=**REDACTED**
AZURE_API_BASE=**REDACTED**
AZURE_API_BASE_PASSHROUGH=**REDACTED**
AZURE_API_KEY=**REDACTED**
AZURE_API_KEY_PASSHROUGH=**REDACTED**
AZURE_API_VERSION=**REDACTED**
AZURE_ASSISTANTS_API_BASE=**REDACTED**
AZURE_ASSISTANTS_API_KEY=**REDACTED**
AZURE_BATCHES_API_BASE=**REDACTED**
AZURE_BATCHES_API_KEY=**REDACTED**
AZURE_CANADA_API_KEY=**REDACTED**
AZURE_CLIENT_ID=**REDACTED**
AZURE_CLIENT_SECRET=**REDACTED**
AZURE_COHERE_API_BASE=**REDACTED**
AZURE_COHERE_API_KEY=**REDACTED**
AZURE_EUROPE_API_BASE=**REDACTED**
AZURE_EUROPE_API_KEY=**REDACTED**
AZURE_FLUX_API_BASE=**REDACTED**
AZURE_FRANCE_API_BASE=**REDACTED**
AZURE_FRANCE_API_KEY=**REDACTED**
AZURE_FT_API_BASE=**REDACTED**
AZURE_FT_API_KEY=**REDACTED**
AZURE_GPT5_API_BASE=**REDACTED**
AZURE_GPT5_API_KEY=**REDACTED**
AZURE_JSON_MODE_API_BASE=**REDACTED**
AZURE_JSON_MODE_API_KEY=**REDACTED**
AZURE_MAX_RETRIES=**REDACTED**
AZURE_MISTRAL_API_BASE=**REDACTED**
AZURE_MISTRAL_API_KEY=**REDACTED**
AZURE_MODEL_ROUTER_API_KEY=**REDACTED**
AZURE_O3_API_BASE=**REDACTED**
AZURE_O3_API_KEY=**REDACTED**
AZURE_OPENAI_O1_KEY=**REDACTED**
AZURE_REALTIME_API_BASE=**REDACTED**
AZURE_REALTIME_API_KEY=**REDACTED**
AZURE_REALTIME_API_VERSION=**REDACTED**
AZURE_RESPONSES_OPENAI_API_KEY=**REDACTED**
AZURE_RESPONSES_OPENAI_API_VERSION=**REDACTED**
AZURE_RESPONSES_OPENAI_ENDPOINT=**REDACTED**
AZURE_STORAGE_ACCOUNT_NAME=**REDACTED**
AZURE_STORAGE_CLIENT_ID=**REDACTED**
AZURE_STORAGE_CLIENT_SECRET=**REDACTED**
AZURE_STORAGE_FILE_SYSTEM=**REDACTED**
AZURE_STORAGE_TENANT_ID=**REDACTED**
AZURE_STREAM_TIMEOUT=**REDACTED**
AZURE_SWEDEN_API_BASE=**REDACTED**
AZURE_SWEDEN_API_KEY=**REDACTED**
AZURE_TENANT_ID=**REDACTED**
AZURE_TIMEOUT=**REDACTED**
AZURE_TTS_API_BASE=**REDACTED**
AZURE_TTS_API_KEY=**REDACTED**
AZURE_VISION_API_KEY=**REDACTED**
AZURE_VISION_ENHANCE_ENDPOINT=**REDACTED**
AZURE_VISION_ENHANCE_KEY=**REDACTED**
AZURE_WHISPER_API_BASE=**REDACTED**
AZURE_WHISPER_API_KEY=**REDACTED**
BACKUP_OPENAI_API_KEY_1=**REDACTED**
BACKUP_OPENAI_API_KEY_2=**REDACTED**
BASETEN_API_KEY=**REDACTED**
BRAINTRUST_API_KEY=**REDACTED**
CIRCLE_OIDC_TOKEN=**REDACTED**
CIRCLE_OIDC_TOKEN_V2=**REDACTED**
CLARIFAI_API_KEY=**REDACTED**
CLEAN_STORE_MODEL_IN_DB_DATABASE_URL=**REDACTED**
CLOUDFLARE_ACCOUNT_ID=**REDACTED**
CLOUDFLARE_API_KEY=**REDACTED**
CLOUDFLARE_AZURE_BASE_URL=**REDACTED**
CODECOV_TOKEN=**REDACTED**
CODESTRAL_API_KEY=**REDACTED**
COHERE_API_KEY=**REDACTED**
DATABASE_URL=**REDACTED**
DATABRICKS_API_BASE=**REDACTED**
DATABRICKS_API_KEY=**REDACTED**
DATAFORSEO_LOGIN=**REDACTED**
DATAFORSEO_PASSWORD=**REDACTED**
DD_API_KEY=**REDACTED**
DD_SITE=**REDACTED**
DEEPGRAM_API_KEY=**REDACTED**
DEEPINFRA_API_KEY=**REDACTED**
DEEPSEEK_API_KEY=**REDACTED**
DEFAULT_NUM_WORKERS_LITELLM_PROXY=**REDACTED**
DOCKERHUB_PASSWORD=**REDACTED**
DOCKERHUB_USERNAME=**REDACTED**
ELEVENLABS_API_KEY=**REDACTED**
EXA_API_KEY=**REDACTED**
FAL_AI_API_KEY=**REDACTED**
FIRECRAWL_API_KEY=**REDACTED**
FIREWORKS_AI_API_KEY=**REDACTED**
GCS_BUCKET_NAME=**REDACTED**
GCS_PRIVATE_KEY=**REDACTED**
GCS_PRIVATE_KEY_ID=**REDACTED**
GCS_PUBSUB_PROJECT_ID=**REDACTED**
GCS_PUBSUB_TOPIC_ID=**REDACTED**
GEMINI_API_KEY=**REDACTED**
GITGUARDIAN_API_KEY=**REDACTED**
GITHUB_ACCESS_TOKEN=**REDACTED**
GITHUB_TOKEN=**REDACTED**
GOOGLE_PSE_API_KEY=**REDACTED**
GOOGLE_PSE_ENGINE_ID=**REDACTED**
GROQ_API_KEY=**REDACTED**
HCP_VAULT_ADDR=**REDACTED**
HCP_VAULT_NAMESPACE=**REDACTED**
HCP_VAULT_TOKEN=**REDACTED**
HELICONE_API_KEY=**REDACTED**
HF_TOKEN=**REDACTED**
INFISICAL_TOKEN=**REDACTED**
JINA_AI_API_KEY=**REDACTED**
JWT_PUBLIC_KEY_URL=**REDACTED**
LAKERA_API_KEY=**REDACTED**
LANGFUSE_FLUSH_AT=**REDACTED**
LANGFUSE_HOST=**REDACTED**
LANGFUSE_PROJECT1_PUBLIC=**REDACTED**
LANGFUSE_PROJECT1_SECRET=**REDACTED**
LANGFUSE_PROJECT2_PUBLIC=**REDACTED**
LANGFUSE_PROJECT2_SECRET=**REDACTED**
LANGFUSE_PUBLIC_KEY=**REDACTED**
LANGFUSE_SECRET_KEY=**REDACTED**
LANGSMITH_API_KEY=**REDACTED**
LANGSMITH_FLUSH_INTERVAL=**REDACTED**
LANGSMITH_PROJECT=**REDACTED**
LANGTRACE_API_KEY=**REDACTED**
LITELLM_LICENSE=**REDACTED**
MANUS_API_KEY=**REDACTED**
MASTER_KEY_CHECK_DB_URL=**REDACTED**
MISTRAL_API_KEY=**REDACTED**
NLP_CLOUD_API_KEY=**REDACTED**
NVIDIA_NIM_API_KEY=**REDACTED**
OPENAI_API_KEY=**REDACTED**
OPENAI_ORGANIZATION=**REDACTED**
OPENROUTER_API_KEY=**REDACTED**
PAGERDUTY_API_KEY=**REDACTED**
PALM_API_KEY=**REDACTED**
PARALLEL_API_KEY=**REDACTED**
PERPLEXITYAI_API_KEY=**REDACTED**
POSTHOG_API_KEY=**REDACTED**
POSTHOG_API_URL=**REDACTED**
PREDIBASE_API_KEY=**REDACTED**
PREDIBASE_TENANT_ID=**REDACTED**
PRESIDIO_ANALYZER_API_BASE=**REDACTED**
PRESIDIO_ANONYMIZER_API_BASE=**REDACTED**
PROXY_DATABASE_URL=**REDACTED**
PROXY_DATABASE_URL_2=**REDACTED**
PROXY_MASTER_KEY=**REDACTED**
PROXY_UNIT_TEST_DATABASE_URL=**REDACTED**
PYPI_API_TOKEN=**REDACTED**
PYPI_PUBLISH_PASSWORD=**REDACTED**
PYPI_PUBLISH_USERNAME=**REDACTED**
QDRANT_API_KEY=**REDACTED**
QDRANT_URL=**REDACTED**
R2_S3_ACCESS_ID=**REDACTED**
R2_S3_ACCESS_KEY=**REDACTED**
R2_S3_BUCKET_NAME=**REDACTED**
R2_S3_REGION_NAME=**REDACTED**
R2_S3_URL=**REDACTED**
RECRAFT_API_KEY=**REDACTED**
REDIS_HOST=**REDACTED**
REDIS_HOST_2=**REDACTED**
REDIS_HOST_WITH_SSL=**REDACTED**
REDIS_PASSWORD=**REDACTED**
REDIS_PASSWORD_2=**REDACTED**
REDIS_PASSWORD_WITH_SSL=**REDACTED**
REDIS_PORT=**REDACTED**
REDIS_PORT_2=**REDACTED**
REDIS_PORT_WITH_SSL=**REDACTED**
REDIS_SSL_2=**REDACTED**
REDIS_SSL_URL=**REDACTED**
REDIS_USERNAME=**REDACTED**
REPLICATE_API_KEY=**REDACTED**
RUNWAYML_API_KEY=**REDACTED**
SEARXNG_API_BASE=**REDACTED**
SENTRY_API_TRACE_RATE=**REDACTED**
SENTRY_API_URL=**REDACTED**
SLACK_API_CHANNEL=**REDACTED**
SLACK_API_SECRET=**REDACTED**
SLACK_API_TOKEN=**REDACTED**
SLACK_WEBHOOK_URL=**REDACTED**
SMALL_DATABASE_URL=**REDACTED**
SNOWFLAKE_ACCOUNT_ID=**REDACTED**
SNOWFLAKE_JWT=**REDACTED**
STORE_MODEL_IN_DB=**REDACTED**
SUPABASE_KEY=**REDACTED**
SUPABASE_URL=**REDACTED**
TAVILY_API_KEY=**REDACTED**
TEST_SPECIAL_ASSEMBLYAI_API_KEY=**REDACTED**
TOGETHER_AI_TOKEN=**REDACTED**
TOPAZ_API_KEY=**REDACTED**
TOXI_PROXY_DATABASE_URL=**REDACTED**
TRACELOOP_API_KEY=**REDACTED**
TWINE_PASSWORD=**REDACTED**
VERTEXAI_PROJECT=**REDACTED**
VERTEX_AI_PRIVATE_KEY=**REDACTED**
VERTEX_AI_PRIVATE_KEY_ID=**REDACTED**
VOYAGE_API_KEY=**REDACTED**
WATSONX_API_BASE=**REDACTED**
WATSONX_API_KEY=**REDACTED**
WATSONX_PROJECT_ID=**REDACTED**
XAI_API_KEY=**REDACTED**
XINFERENCE_API_BASE=**REDACTED**
ZAPIER_CI_CD_MCP_TOKEN=**REDACTED**
ZAPIER_MCP_HTTPS_SERVER_URL=**REDACTED**</pre>
</div>

## Account takeover actions (12:49–13:01 UTC, March 24)

All three actions described below were performed from `krrishdholakia`'s GitHub account. The attacker had control of the session before 12:49 UTC and was still active at 13:01 UTC.

### 12:49 UTC: Security disclosure silenced

At 11:48 UTC, researcher `isfinne` opened issue #24512: "[Security]: CRITICAL: Malicious `litellm_init.pth` in litellm 1.82.8 (credential stealer via PyPI supply chain)."

At 12:49 UTC, 61 minutes later, the issue was closed with no comment, no acknowledgment, and no yank of the malicious package. Boost Security's threat hunting data lake records this as an `IssuesEvent` (action: closed, actor: `krrishdholakia`, event ID `7702784362`), the same account we show below performing the 70-repo de-privatization nine minutes later, which places the account firmly in attacker hands at this moment.

The issue was then flooded with bot comments in a pattern identical to what TeamPCP used during the Trivy incident on `aquasecurity/trivy` discussion #10420. As of this writing, issue #24512 has **334 total comments**. We pulled them all via the GitHub REST API: **293 of those 334 are generic noise**, rotating through just 8 phrases:

| Phrase | Count |
|--------|-------|
| "Worked like a charm, much appreciated." | 50 |
| "Great explanation, thanks for sharing." | 49 |
| "Appreciate the help, this fixed it for me." | 39 |
| "Thanks for the tip!" | 36 |
| "Thanks, that helped!" | 34 |
| "Exactly what I needed, thanks." | 33 |
| "This was the answer I was looking for." | 30 |
| "This solved my issue, thank you!" | 22 |

The goal is the same as in the Trivy incident: drown the legitimate signal in noise, making it harder for affected users to find the actual disclosure, and harder for the community to coordinate a response.

### Who are these accounts?

We pulled profiles for all 123 spam accounts via the GitHub REST API (all 123 resolved). The full dataset is available as [a JSON artifact](/files/articles/litellm-issue-24512-spam-accounts.json). The picture that emerges is not a fleet of freshly-minted throwaway accounts:

| Account age (as of March 24) | Count |
|------------------------------|-------|
| 6+ years old | 52 (42%) |
| 2 to 6 years old | 41 (33%) |
| Under 2 years old | 30 (24%) |

The oldest accounts in the set date back to **2009** and **2010**. The majority have public repositories, real names, and professional bios. A representative sample:

| Login | Created | Age | Repos | Profile |
|-------|---------|-----|-------|---------|
| `mj6uc` | 2009-02-16 | 17y | 6 | Mayank Jain |
| `bo7` | 2010-11-18 | 15y | 43 | Sven Bohnstedt |
| `bercanozcan` | 2014-07-04 | 12y | 83 | Bercan Özcan |
| `kelvinelove` | 2014-09-24 | 12y | 39 | kelvine (112 followers) |
| `OmkarKirpan` | 2015-10-04 | 10y | 588 | "Passionate coder, open-source enthusiast" |
| `aamitn` | 2015-04-10 | 11y | 45 | Amit Nandi, "network programming, REST, RPC" |
| `Hancie123` | 2016-12-21 | 9y | 57 | Hancie Phago, "Software Engineer" |
| `Eacaw` | 2016-11-29 | 9y | 12 | David Pinchen, "Engineering Manager" |
| `PAWAN1SINGH` | 2016-01-27 | 10y | 5 | "Senior Software Developer (10+ yrs)" |
| `sanchir2011` | 2017-05-04 | 7y | 5 | Sanchir Enkhbold, "CEO at @ShopMN, Senior SE" |
| `programonaut` | 2019-01-31 | 7y | 19 | Maximilian Kürschner (33 followers) |
| `praiitt` | 2016-03-30 | 10y | 27 | (most prolific spammer: 7 comments) |

These are real developers. The infostealer angle is consistent: TeamPCP's tooling steals browser-stored credentials and session tokens. A developer who had any infostealer from TeamPCP's stash execute on their machine would have had their GitHub session token exfiltrated along with everything else. The attacker can then post comments via the GitHub API using those stolen tokens without the account owner being aware.

This is the same pattern observed during the Trivy incident, where two-thirds of the spam bots were described as having "real-looking activity profiles." Here the accounts are older and more credentialed on average, which suggests a broader or more mature credential pool.

### Cross-referencing with the Trivy spam botnet

Rami McCarthy claims a 76% overlap between the LiteLLM spam accounts and the accounts used in the Trivy discussion #10420 flood. We cross-checked this independently using our own evidence from the Trivy incident investigation.

Our Trivy evidence file covers the first 100 comments of discussion #10420 (out of 772 total). From those 100 comments we extracted 95 unique spam accounts. Comparing against the 123 LiteLLM spam accounts:

- **89 of those 95 Trivy accounts (94%) also appear in the LiteLLM set**
- **89 of the 123 LiteLLM accounts (72%) are already present in our Trivy sample**

Since our Trivy sample covers only ~13% of the full discussion thread, 72% is a lower bound on the true overlap. The real figure is almost certainly higher and consistent with Rami's 76% claim. The same credential pool, the same botnet, the same operator.

### 12:56 UTC: Calling card (15 org repos defaced)

In a 3-second automated burst (12:56:41–12:56:44 UTC), the attacker changed the description of **15 BerriAI organization repositories** to **"teampcp owns BerriAI."** We independently detected one of these (`BerriAI/sans_prod_litellm`) via its `updated_at` timestamp; OpenSourceMalware's analysis established the full count. The message mirrors the "teampcp owns you" taunts posted during the Trivy incident four days earlier.

### 12:58–13:01 UTC: 70 repositories made public in 3 minutes

**70 private BerriAI repositories were changed to public in 3 minutes and 14 seconds.** The timing pattern (repositories exposed in roughly 2-second alphabetical batches) is consistent with a script iterating `PATCH /repos/{owner}/{repo}` with `{"private": false}` across the full organization repository list.

```
First event: 2026-03-24 12:58:35 UTC  BerriAI/agent_repo_148ca4b4...
Last event:  2026-03-24 13:01:49 UTC  BerriAI/test_backend_template
Total:       70 repositories
Window:      3 minutes 14 seconds
```

This was proven via Boost Security's threat hunting data lake, not inferred: 70 `PublicEvent` records with actor `krrishdholakia`, event IDs spanning `7702928677` through `7703046077`.

Among the highest-risk repositories exposed:

| Repository | Risk |
|------------|------|
| `adobe_prod_litellm` | Enterprise customer production deployment |
| `fletch-prod-litellm` | Enterprise customer production deployment |
| `hogarth-prod-litellm` | Enterprise customer production deployment |
| `ipsos_production_litellm` | Enterprise customer production deployment |
| `palisade-prod-litellm` | Enterprise customer production deployment |
| `payready_prod_litellm` | Enterprise customer production deployment |
| `swyx-prod` | Enterprise customer production deployment |
| `airgapped_license_key_creation` | License key generation; **`private_key.pem` still publicly accessible as of this writing** |
| `privacyKeys` | Stored credentials; **still public as of this writing** |
| `ci-cd-boss` | CI/CD pipeline configuration |
| `prod-prometheus` | Production monitoring infrastructure |
| `reliableKeyChecker_server` | Key management service |

### 12:59–13:01 UTC: 182 personal repositories defaced

While the org de-privatization was still running, the attacker simultaneously pushed to **182 personal repositories** belonging to `krrishdholakia`, replacing each README with a single line:

```
teampcp owns BerriAI
```

Commit message: `teampcp update`. Committer: `Krish Dholakia <krrishdholakia@gmail.com>`. This proves the attacker held the maintainer's personal GitHub PAT, not just an organization admin token. Org-level access alone would not have reached personal repositories. ([Source: OpenSourceMalware](https://opensourcemalware.com/pypi/litellm))

### The trolling continues

Spotted by **Adnan Khan** in [issue #24518](https://github.com/BerriAI/litellm/issues/24518#issuecomment-4119478134): a brand-new GitHub account (`krrish-berri-2`, joined 1 hour after the takeover) posted in the incident thread with the comment "updated my profile with a picture from today - hope that helps" alongside a selfie holding a handwritten sign reading "THIS IS ME 24/03/2026."

![krrish-berri-2 troll post in issue #24518](/images/articles/teampcp-litellm-krrish-berri-2-troll.png)

The account has 1 follower, 0 following, and links to an HN profile (`news.ycombinator.com/user?id=detente18`). Two readings are possible: the real Krrish Dholakia, locked out of `krrishdholakia`, created a new account to post a proof-of-life in his own incident thread; or the attacker created a fake impersonation account to sow confusion and mock the community's response. Either interpretation is telling: if it is the real CEO, his original account was still in attacker hands at posting time; if it is the attacker, they were actively monitoring and trolling the incident response thread in real time.

## Confirmed sequence of events

```
2026-03-19 17:43 UTC  TeamPCP pushes poisoned Trivy v0.69.4 (aquasecurity/trivy);
                      poisoned GitHub Release binaries and Docker images published
           [gap]      BerriAI credential exfiltration occurs via an as-yet unconfirmed
                      Trivy delivery channel (GitHub Release binary, Docker image, or
                      other); PYPI_PUBLISH_PASSWORD and/or GitHub PAT stolen
                      (exact date between Mar 19–24 not confirmed)
           [gap]      Attacker publishes litellm==1.82.7 to PyPI (payload in proxy_server.py;
                      requires explicit import to trigger)
           [gap]      Attacker publishes litellm==1.82.8 to PyPI with .pth file
                      (no GitHub tags; direct twine uploads; any Python startup triggers)
2026-03-24 11:48 UTC  Reporter 'isfinne' opens issue #24512 (security disclosure)
2026-03-24 12:49 UTC  krrishdholakia [ATTACKER] closes issue #24512 silently;
                      bot flood of 293 generic comments begins on same issue
2026-03-24 12:56 UTC  krrishdholakia [ATTACKER] changes description on 15 BerriAI org repos
                      to "teampcp owns BerriAI" (3-second automated burst)
2026-03-24 12:58 UTC  krrishdholakia [ATTACKER] begins mass repo de-privatization
2026-03-24 13:01 UTC  70 org repositories made public: operation complete
2026-03-24 12:59 UTC  krrishdholakia [ATTACKER] pushes "teampcp owns BerriAI" README
                      wipe to 182 personal repositories (overlapping window)
```

## Indicators of compromise

| Category | Indicator |
|----------|-----------|
| Compromised account | `krrishdholakia` (GitHub, sessions active as of 13:01 UTC March 24) |
| Malicious packages | `litellm==1.82.7` and `litellm==1.82.8` (PyPI, yanked) |
| Malicious file | `litellm_init.pth` in `site-packages/` |
| Malicious file hash | sha256: `ceNa7wMJnNHy1kRnNCcwJaFjWX3pORLfMh7xGL8TUjg` |
| C2 exfiltration endpoint | `https://models.litellm.cloud/` |
| Attacker calling card | Repository description "teampcp owns BerriAI" on `BerriAI/sans_prod_litellm` |
| Upstream poisoned binary | Trivy v0.69.4 GitHub Release / Docker image (see [previous report](/articles/20-days-later-trivy-compromise-act-ii)) |

To check whether a machine installed the malicious package:

```bash
find /usr /home ~/.local -name 'litellm_init.pth' 2>/dev/null
```

If found, treat all environment variables and credential files on that machine as compromised.

## Recommended remediation

**For BerriAI:**

1. Re-privatize all 70 exposed repositories if not already done
2. Revoke all active sessions for `krrishdholakia` on GitHub; rotate the account password and enable hardware MFA immediately
3. Rotate all CircleCI project secrets, prioritizing PyPI publish credentials, `GITHUB_TOKEN`, `GITHUB_ACCESS_TOKEN`, `HCP_VAULT_TOKEN`, `INFISICAL_TOKEN`, all AWS and Azure credentials, database URLs, `PROXY_MASTER_KEY`, and DockerHub credentials
4. Audit HCP Vault and Infisical audit logs for unauthorized access using the exfiltrated tokens
5. Audit GitHub Actions `workflow_dispatch` calls for unexpected triggers using the compromised `GITHUB_TOKEN`
6. Treat all secrets in the 70 exposed repositories as compromised and rotate accordingly

**For enterprise customers (Adobe, Fletch, Hogarth, Ipsos, Palisade, PayReady, Swyx, and others):** Your production deployment configurations were exposed in the repository dump. Rotate all credentials present in those repos.

**For any user or pipeline that installed `litellm==1.82.7` or `litellm==1.82.8`:** Rotate all credentials that were in environment variables or config files on affected machines. Check for `litellm_init.pth` using the command above. Users of 1.82.7 should also inspect `litellm/proxy/proxy_server.py` in their installed package for injected code.

**If you are running the LiteLLM proxy via Docker:** According to the maintainer, you were not impacted. The proxy Docker image pins versions in `requirements.txt` and did not pull `1.82.8`.

## Open questions

Several things remain unconfirmed as of this writing:

1. **Exact initial access vector, date, and machine.** How did the poisoned Trivy binary execute in BerriAI's environment? Homebrew is ruled out (builds from source). Candidates are the GitHub Release binary or Docker image. BerriAI's incident response team should audit CI job histories and developer machine logs for the March 19–24 window.
2. **`litellm==1.82.7` was also malicious** (confirmed via OpenSourceMalware's payload analysis). The payload was embedded in `litellm/proxy/proxy_server.py` and required an explicit `import litellm.proxy` to trigger. The attacker then published 1.82.8 with the `.pth` file to remove that dependency. Users who installed 1.82.7 are also affected.
3. **`privacyKeys` and `airgapped_license_key_creation` remain public.** As of this writing, BerriAI has not re-privatized these repositories. `airgapped_license_key_creation` contains a `private_key.pem` file that is still publicly accessible. Any licensing infrastructure or customer systems relying on the corresponding key pair should be considered compromised.
4. **Was the window of public exposure captured by crawlers or mirrors?** The 70 repositories were public for an unknown duration before BerriAI's response.

## Credits

This investigation was only possible because of immediate, generous sharing from fellow researchers as the incident unfolded in real time.

Additional credit to [StepSecurity](https://www.stepsecurity.io/blog/litellm-credential-stealer-hidden-in-pypi-wheel), who published the first blog post on the LiteLLM compromise. Their report documented the malicious wheel and the `.pth` payload mechanics. We build on that foundation here with a focus on the root cause, the account takeover chain, and the attribution back to TeamPCP.

[Paul McCarty at OpenSourceMalware](https://opensourcemalware.com/blog/teampcp-litellm-pypi-supply-chain-attack) published the most technically complete analysis of the attack, including the full deobfuscated payload, confirmation that `litellm==1.82.7` was also malicious, the 15-repo org defacement scope, and the 182 personal repository wipes. Their [malicious package deep-dive](https://opensourcemalware.com/pypi/litellm) is essential reading for anyone doing IR on this incident.

Enormous thanks to:

- [Adnan Khan](https://adnanthekhan.com/) for monitoring the TeamPCP Telegram channel and sounding the alarm the moment the warning messages appeared, for identifying the CEO account as the pivot point, and for connecting BerriAI's CircleCI pipeline back to the Trivy upstream attack. Without Adnan's early tip, this hunt would not have begun.
- [Rami McCarthy](https://ramimac.me/) for identifying `litellm` as the target, publishing the first detailed technical analysis of the `litellm_init.pth` payload and the `models.litellm.cloud` exfiltration endpoint, and for his outstanding [TeamPCP IOC microsite](https://ramimac.me/trivy-teampcp/#phase-09) that has become an essential reference for anyone tracking this campaign. Rami is on fire.
