TeamPCP Compromises LiteLLM
threat-intel

TeamPCP Compromises LiteLLM: Credential Stealer in PyPI, 70 Repos Exposed

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.

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 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, 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 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

Initial access: the Trivy connection (our working hypothesis)

Note: A LiteLLM maintainer posted on Hacker News 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:

# .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 (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.)

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, 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 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 documents both:

VersionPayload locationTrigger
1.82.7litellm/proxy/proxy_server.pyimport litellm.proxy
1.82.8litellm_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 (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. 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.

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**

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:

PhraseCount
”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. The picture that emerges is not a fleet of freshly-minted throwaway accounts:

Account age (as of March 24)Count
6+ years old52 (42%)
2 to 6 years old41 (33%)
Under 2 years old30 (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:

LoginCreatedAgeReposProfile
mj6uc2009-02-1617y6Mayank Jain
bo72010-11-1815y43Sven Bohnstedt
bercanozcan2014-07-0412y83Bercan Özcan
kelvinelove2014-09-2412y39kelvine (112 followers)
OmkarKirpan2015-10-0410y588”Passionate coder, open-source enthusiast”
aamitn2015-04-1011y45Amit Nandi, “network programming, REST, RPC”
Hancie1232016-12-219y57Hancie Phago, “Software Engineer”
Eacaw2016-11-299y12David Pinchen, “Engineering Manager”
PAWAN1SINGH2016-01-2710y5”Senior Software Developer (10+ yrs)“
sanchir20112017-05-047y5Sanchir Enkhbold, “CEO at @ShopMN, Senior SE”
programonaut2019-01-317y19Maximilian Kürschner (33 followers)
praiitt2016-03-3010y27(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:

RepositoryRisk
adobe_prod_litellmEnterprise customer production deployment
fletch-prod-litellmEnterprise customer production deployment
hogarth-prod-litellmEnterprise customer production deployment
ipsos_production_litellmEnterprise customer production deployment
palisade-prod-litellmEnterprise customer production deployment
payready_prod_litellmEnterprise customer production deployment
swyx-prodEnterprise customer production deployment
airgapped_license_key_creationLicense key generation; private_key.pem still publicly accessible as of this writing
privacyKeysStored credentials; still public as of this writing
ci-cd-bossCI/CD pipeline configuration
prod-prometheusProduction monitoring infrastructure
reliableKeyChecker_serverKey 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)

The trolling continues

Spotted by Adnan Khan in issue #24518: 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

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

CategoryIndicator
Compromised accountkrrishdholakia (GitHub, sessions active as of 13:01 UTC March 24)
Malicious packageslitellm==1.82.7 and litellm==1.82.8 (PyPI, yanked)
Malicious filelitellm_init.pth in site-packages/
Malicious file hashsha256: ceNa7wMJnNHy1kRnNCcwJaFjWX3pORLfMh7xGL8TUjg
C2 exfiltration endpointhttps://models.litellm.cloud/
Attacker calling cardRepository description “teampcp owns BerriAI” on BerriAI/sans_prod_litellm
Upstream poisoned binaryTrivy v0.69.4 GitHub Release / Docker image (see previous report)

To check whether a machine installed the malicious package:

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.

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, 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 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 is essential reading for anyone doing IR on this incident.

Enormous thanks to:

  • Adnan Khan 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 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 that has become an essential reference for anyone tracking this campaign. Rami is on fire.