PyPI ‘telnyx’ Backdoored by TeamPCP: Hidden Payloads Inside Audio Files

The CyberSec Guru

Updated on:

PyPI telnyx Package Compromised TeamPCP Supply Chain Attack

If you like this post, then please share it:

Buy me A Coffee!

Support The CyberSec Guru’s Mission

🔐 Fuel the cybersecurity crusade by buying me a coffee! Why your support matters: Zero paywalls: Keep the content 100% free for learners worldwide, Writeup Access: Get complete writeup access within 24 hours

“Your coffee keeps the servers running and the knowledge flowing in our fight against cybercrime.”☕ Support My Work

Buy Me a Coffee Button

If you use the Telnyx Python SDK and you installed it today, stop what you’re doing. Two versions pushed to PyPI in the early hours of March 27 – 4.87.1 and 4.87.2 are malicious. The malware runs the moment you type import telnyx. No setup hooks, no prompts, no warning. It just runs.

Downgrade to telnyx==4.87.0 immediately, then keep reading.

What Happened

At 03:51 UTC this morning, someone pushed two poisoned versions of the Telnyx SDK to PyPI. The package gets about 742,000 downloads a month – contact centers, voice platforms, comms-heavy SaaS. These aren’t hobbyist installs.

The group behind it is TeamPCP. They’ve been running the same playbook for three weeks: compromise a trusted security tool, drain its credentials, use those credentials to push malware into whatever that tool had access to, collect new credentials from the next wave of victims, repeat.

The Telnyx attack is their fifth public move in nine days.

Telnyx Logo
Telnyx Logo

What’s different this time and worth paying attention to is how the payload gets delivered. TeamPCP doesn’t fetch a raw binary or a Python script. They fetch a .wav file. A structurally valid audio file. The malware is hidden inside the audio frame data using XOR obfuscation. It passes MIME-type checks. It’ll slip through URL filters that allow .wav downloads. You won’t catch it with standard static analysis unless you know specifically to decode the WAV frames.

They first used this technique five days ago in a Kubernetes wiper. It went from experiment to production PyPI attack in less than a week.

Who Did This, and Why Does It Keep Happening?

The group behind this is TeamPCP. This is their fifth public attack in nine days, and it’s not random targeting. They’ve been running a deliberate credential harvesting chain since March 19, they started compromising one tool, steal its secrets, use those secrets to push malware into whatever that tool had access to, collect new credentials from the next wave of victims, and repeat.

March 19 – Trivy (CVE-2026-33634, CVSS 9.4)

Aqua Security’s open source vulnerability scanner got backdoored. Every CI/CD pipeline running Trivy without version pinning had its secrets exfiltrated. API tokens, cloud credentials, package registry keys – all of it. TeamPCP also renamed 44 Aqua Security GitHub repos with the prefix tpcp-docs- and changed their descriptions to “TeamPCP Owns Aqua Security.” Subtle.

March 20 – CanisterWorm across npm

Using stolen npm tokens from Trivy victims, TeamPCP deployed CanisterWorm: a worm that takes a single stolen token, enumerates every package that token can publish, bumps the version, and injects malicious code across the entire scope. Sixty seconds. Forty-six-plus packages compromised, including @EmilGroup and @opengov.

March 22 – WAV steganography appears

Researchers spotted TeamPCP using the WAV frame trick for the first time in a Kubernetes wiper variant. It looked like an experiment. It wasn’t.

March 23 – Checkmarx

kics-github-action and ast-github-action were compromised, along with two OpenVSX extensions. The C2 domain was checkmarx[.]zone deliberately chosen to look like the real company. Thirty-five git tags hijacked in under four hours. Cleaned up three hours later.

March 24 – LiteLLM

LiteLLM is a proxy that sits in front of your OpenAI, Anthropic, AWS Bedrock, and GCP VertexAI keys. Basically a master keyring for an organization’s entire AI stack. Versions 1.82.7 and 1.82.8 were published using credentials from LiteLLM’s own CI/CD pipeline, which was running unpinned Trivy. PyPI quarantined them after about three hours. Roughly 95 million downloads a month the window was brief, the exposure wasn’t.

March 27 – Telnyx

Two malicious versions pushed overnight, no corresponding GitHub tags or releases. They didn’t come from Telnyx’s own build process.

How They Got In

The Telnyx GitHub repository shows no signs of compromise at all. Every recent push was from stainless-app[bot], the automated SDK generation platform that manages the pipeline. No force pushes, no unknown contributors, no sketchy PRs.

The last legitimate PyPI publish was v4.87.0 on March 26, through the proper publish-pypi.yml GitHub Actions workflow. No workflow run exists for 4.87.1 or 4.87.2. The attacker uploaded the malicious wheels directly to PyPI – manually, from their own machine.

How do we know? The upload tool fingerprint gives it away. The legitimate Telnyx CI pipeline uses rye publish. The PyPI metadata for 4.87.2 shows the upload client as twine/6.2.0 CPython/3.14.3. That mismatch means someone used a stolen API token to push these versions outside of any automated process.

Telnyx doesn’t use PyPI’s trusted publisher (OIDC) feature, which would have bound uploads to a specific GitHub repository and workflow. Without that, a stolen token is a master key. Anyone holding it can push any version from anywhere.

The most likely explanation: the PYPI_TOKEN was stolen in a prior credential harvesting operation, possibly through the same chain that started with Trivy. If your CI/CD pipeline ran Trivy anytime after March 19, your secrets may have already been exfiltrated.

What the Malware Actually Does

The injected code lives in telnyx/_client.py. That file is part of the package’s normal initialization, so import telnyx is all it takes. Two functions get called at module scope with zero exceptions handling around them:

setup()
FetchAudio()

Version 4.87.1 had a bug in Setup() (capital S) vs setup() (lowercase) that caused a NameError and actually prevented either attack path from running. Version 4.87.2 fixed the typo. Treat both versions as malicious regardless.

Windows: Startup Persistence

The setup() function only runs on Windows (os.name == 'nt'). It:

  1. Downloads hangup.wav from 83[.]142[.]209[.]203:8080
  2. Reads the WAV file’s audio frames
  3. Base64-decodes the frame data
  4. Takes the first 8 bytes as an XOR key and decrypts the rest
  5. Writes the resulting PE executable to the Windows Startup folder as msbuild.exe – named to blend in with a legitimate Microsoft tool
  6. Launches it silently using creationflags=0x08000000 (no console window)

A .lock file enforces a 12-hour cooldown between re-drops, keeping C2 traffic low enough to avoid timing-based detection. The lock file is hidden using attrib +h.

Linux/macOS: Three-stage infostealer

FetchAudio() runs on everything that isn’t Windows. It decodes a 4,436-character base64 blob hardcoded at line 459 of _client.py and runs it in a detached subprocess (start_new_session=True), so credential theft continues even if the importing process exits.

That decoded payload is an 83-line credential harvester. Here’s what it does:

1: Downloads ringtone.wav from the same C2 server. Extracts a second-stage harvester script from the audio frames using the same XOR-over-base64 technique. Pipes that script into a fresh Python interpreter and captures its output i.e. your credentials to a temp file.

2: Encrypts the stolen data. Generates a random 32-byte AES session key, encrypts the collected credentials with AES-256-CBC, then wraps the session key itself with the attacker’s RSA-4096 public key using OAEP padding. Both encrypted files go into a tarball.

3: POSTs the tarball to 83[.]142[.]209[.]203:8080 with the header X-Filename: tpcp.tar.gz.

The hybrid encryption means that even if you intercept the traffic, you can’t read what was stolen. You’d need the attacker’s RSA private key.

The WAV trick

This is the part that changes things for defenders.

TeamPCP isn’t downloading raw binaries or Python scripts. They’re downloading .wav files which are basically structurally valid audio containers. The payload is stuffed inside the audio frame data and is base64-encoded with XOR-obfuscation. The WAV header is legitimate. The file passes MIME-type checks. It clears URL filters that allow audio downloads. Standard static analysis tools won’t catch it unless someone specifically knows what to look for in the audio frames.

The extraction logic is simple:

with wave.open(wf, 'rb') as w:
b = base64.b64decode(w.readframes(w.getnframes()))
s, m = b[:8], b[8:]
payload = bytes([m[i] ^ s[i % len(s)] for i in range(len(m))])

The code reads the frames. Base64-decode them. First 8 bytes become the XOR key. The rest is the payload. That’s it.

What makes this particularly annoying from a defense standpoint is that a communications SDK downloading an audio file is completely normal traffic. There’s nothing to flag. This is what makes it difficult to detect with traditional techniques. TeamPCP tested this technique on March 22 and deployed it at scale five days later. Expect it to stick around.

How do we know that This Is Definitely TeamPCP?

Three things tie this attack to the same group behind the LiteLLM compromise:

  1. The RSA-4096 public key matches byte for byte. The key embedded in the decoded _p payload is identical to the one in LiteLLM 1.82.8’s Stage 1 orchestrator. Only whoever holds the private key can decrypt the exfiltrated data.
  2. The exfil header is identical. Both attacks use X-Filename: tpcp.tar.gz. “TPCP” is TeamPCP’s own initials. They’re not hiding the attribution.
  3. The encryption scheme is the same. Both attacks use the exact same sequence: openssl rand, then openssl enc -aes-256-cbc, then openssl pkeyutl -encrypt with OAEP padding.

Only one change was noticed – the LiteLLM attack used a compromised domain for C2 but this one uses a raw IP. Also, the WAV delivery mechanism is new in compared to all their previous attacks.

Indicators of Compromise

Indicators of Compromise

CategoryIndicator
Malicious packagetelnyx==4.87.1
Malicious packagetelnyx==4.87.2
SHA256 (4.87.1)7321caa303fe96ded0492c747d2f353c4f7d17185656fe292ab0a59e2bd0b8d9
SHA256 (4.87.2)cd08115806662469bbedec4b03f8427b97c8a4b3bc1442dc18b72b4e19395fe3
C2 server83[.]142[.]209[.]203:8080
Windows payload URLhxxp://83[.]142[.]209[.]203:8080/hangup.wav
Linux/Mac payload URLhxxp://83[.]142[.]209[.]203:8080/ringtone.wav
Exfil endpointhxxp://83[.]142[.]209[.]203:8080/ (POST)
Exfil headerX-Filename: tpcp.tar.gz
Windows persistence%APPDATA%\Microsoft\Windows\Start Menu\Programs\Startup\msbuild.exe
Windows lock file%APPDATA%\Microsoft\Windows\Start Menu\Programs\Startup\msbuild.exe.lock
Injection filesrc/telnyx/_client.py (line 459)

What You Need to Do Now

Fix the package first

pip install telnyx==4.87.0

Then purge 4.87.1 and 4.87.2 from every virtualenv, Docker image, and pipeline. Check your requirements.txt, pyproject.toml, and Pipfile.lock for unpinned telnyx. If you have a private mirror (Artifactory, Nexus, etc.), pull the malicious versions from there too.

If it was installed, assume the machine is compromised

Don’t try to clean the environment. Rebuild from a known good image.

Then rotate everything:

  • API keys and service tokens
  • SSH keys in ~/.ssh/ or held by ssh-agent
  • Cloud credentials (AWS IAM, GCP service accounts, Azure service principals)
  • Anything stored in a secrets manager that was accessible from that machine and audit the access logs

On Windows: check for msbuild.exe in your Startup folder. If it’s there, delete it and the .lock file. Then search egress logs and the filesystem for tpcp.tar.gz.

Detection rules to add

  • Block 83.142.209[.]203 at your firewall and proxy
  • SIEM alert: HTTP requests with header X-Filename: tpcp.tar.gz
  • Alert on Python processes spawning child processes via sys.executable with stdin piping
  • Alert on msbuild.exe launching from the Startup folder (unless you have a legitimate use for it there)
  • Run pip-audit or check your SBOM against telnyx==4.87.1 and telnyx==4.87.2

Longer term

Pin your dependencies to exact versions in production. Use pip install --require-hashes. Set up PyPI trusted publisher (OIDC) for any packages you maintain. It makes stolen tokens useless outside the specific GitHub workflow they’re tied to. If you ran Trivy in CI/CD and haven’t audited your secrets since March 19, do it now.

The Bigger Picture

Credential chaining is what makes this campaign work. Each victim organization hands TeamPCP new keys, and those keys open new targets. The difficulty doesn’t go up as the attack scales – if anything it gets easier because the credential pool keeps growing.

Here’s the uncomfortable part: if your organization used Trivy, Checkmarx tools, LiteLLM, and Telnyx in the same CI/CD pipeline, you may have contributed credentials to this pool more than once without knowing it.

The WAV delivery technique will probably show up again. It has showed up before too. Most detection tooling watches for binary downloads, Python script fetches, and raw shellcode. An audio file download from a communications SDK is exactly the traffic that doesn’t trip any of those checks. TeamPCP ran a test on March 22 and liked the results enough to ship it at scale five days later.

Also, the import-time execution problem isn’t going away. pip audit and safety check package names against CVE databases. They don’t execute anything. Source review of your full dependency tree is the only way to catch this kind of injection, and almost nobody does that systematically on third-party packages.

The safe version of the Telnyx SDK is 4.87.0. Stay there until the maintainers confirm the compromised credentials are rotated and push a verified clean release.

Buy me A Coffee!

Support The CyberSec Guru’s Mission

🔐 Fuel the cybersecurity crusade by buying me a coffee! Your contribution powers free tutorials, hands-on labs, and security resources.

Why your support matters:
  • Writeup Access: Get complete writeup access within 24 hours
  • Zero paywalls: Keep the content 100% free for learners worldwide

Perks for one-time supporters:
☕️ $5: Shoutout in Buy Me a Coffee
🛡️ $8: Fast-track Access to Live Webinars
💻 $10: Vote on future tutorial topics + exclusive AMA access

“Your coffee keeps the servers running and the knowledge flowing in our fight against cybercrime.”☕ Support My Work

Buy Me a Coffee Button

If you like this post, then please share it:

News

Discover more from The CyberSec Guru

Subscribe to get the latest posts sent to your email!

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.

Discover more from The CyberSec Guru

Subscribe now to keep reading and get access to the full archive.

Continue reading