Part of Five Supply Chain Attacks. One Blind Spot. — a series on the structural gap that appeared across every major CI/CD compromise in March 2026.
The Telnyx SDK provides voice, messaging, and networking APIs, with roughly 742k monthly downloads. TeamPCP uploaded versions 4.87.1 and 4.87.2 at 03:51 UTC, injecting a backdoor into _client.py that fires at import time. According to Aikido and Socket, the same RSA public key and exfiltration headers from the LiteLLM breach were reused, and the payload concealed later stages within WAV audio files via steganography. PyPI quarantined both versions the same day.
What Garnet observed
Method: detonation of both compromised telnyx versions inside a GitHub Actions runner instrumented with Garnet's eBPF sensor. The workflow extracted the malicious packages from Datadog's malicious-software-packages-dataset, installed telnyx from clean source, overlaid the backdoored _client.py, and triggered import telnyx.
The attack chain
Execution lineage
Run 23662517211 · jadoonf/pypi-analysis-feed
Analyse local PyPI archives
4.87.1 | 4.87.2 | |
|---|---|---|
| Import / trigger | Crashes on NameError (setup vs Setup) | Backdoor runs at import |
| Child process | None | Base64-decoded Python subprocess |
| Network | None observed | TCP to 83.142.209.203:8080 |
| Orphaning / persistence | N/A | Child reparented under systemd |
Version 4.87.1 crashed immediately — the malware author defined the entry function as setup() but called it as Setup() at line 7823, producing a NameError before any payload could execute. Garnet fired code_on_the_fly and execution_from_unusual_directory on the import, but no child process or network activity followed.
Version 4.87.2 is where the attack succeeds. The _client.py backdoor fires at import time and spawns a child process that decodes and executes a base64 payload. Garnet captured the truncated blob in the command args — partial decoding reveals import subprocess, import tempfile, import os, import base64, import s... (truncated at Garnet's field-length limit). These imports are consistent with a network-capable payload that stages files to disk.
Garnet fires three signals on the child process: code_on_the_fly (dynamic code execution via base64 decode), execution_from_unusual_directory (running from hostedtoolcache), and shell_spawned_by_language_interpreter (Python spawning a subprocess). The flow data shows 6 entries from 10.1.0.106:50584 → 83.142.209.203:8080/TCP, all sharing a single source port — one persistent connection observed across Garnet's polling intervals. Status: "ongoing" (TCP established, not just a SYN).
Then the parent exits. python3.11(2657) (the import process) terminates, but the malicious child python3.11(2659) continues running, reparented to systemd(1). Garnet sees this directly in the ancestry shift: at 18:55:47Z the process sits under the full Runner.Worker chain; by 18:55:55Z it reports to systemd. This is a textbook persistence technique for CI/CD — the workflow step appears to complete, but the payload lives on.
The lineage above shows the full ancestry for both versions side by side — the 4.87.1 crash (no child processes, no network) and the 4.87.2 attack chain through the orphaned base64 child connecting to 83.142.209.203:8080.
After ~8 seconds, python3.11(2659) exits. Garnet recorded no file writes, no credential file access, and no additional outbound flows from this process — the C2 was reachable but was not serving payloads at replay time. External reporting documents that the intended next stages were WAV steganography download, XOR decode, credential harvesting, AES-256-CBC + RSA-4096 encryption, and tpcp.tar.gz exfiltration — none of which produced Garnet events in this replay.
Real-world impact
The telnyx package has 742k monthly downloads. The _client.py backdoor fires on any import telnyx, making every downstream consumer a potential victim. Version 4.87.1's NameError crash means only 4.87.2 installations would have executed the full payload — but both versions were live on PyPI for the same window. The reuse of the same RSA key and tpcp.tar.gz exfiltration pattern from the LiteLLM compromise confirms a single actor operating across multiple PyPI packages in rapid succession.
Explore the run profile above, or start observing your own workflows with Garnet.