Last week it was LiteLLM. This week it’s the telnyx Python package. The actor is the same, the RSA key infrastructure is the same, and the goal is the same: read your environment variables and send them to an attacker-controlled server. If you use either package and haven’t rotated credentials, do that before reading the rest of this.
What Happened
Versions 4.87.1 and 4.87.2 of the telnyx PyPI package were found to contain malicious code injected by a threat actor tracked as TeamPCP. Researchers confirmed the same RSA key used in the LiteLLM supply chain attack was present here, which makes attribution fairly clean: same actor, coordinated campaign, systematically targeting AI and communications tooling.
The malicious versions were published to PyPI and would have been pulled in by any project with a loose version pin (e.g., telnyx>=4.0.0) or an unpinned install. The legitimate telnyx maintainers have since removed the versions and published a clean release.
The Attack Pattern
The injected code follows a simple, effective pattern. On import or initialization, it scans os.environ for anything that looks like a credential: API keys, tokens, secrets, database connection strings. It then exfiltrates whatever it finds over an outbound HTTPS request to a server the attackers control.
No fancy exploits. No shellcode. Just os.environ and requests.post.
This pattern works because:
- Python packages run with the same privileges as your application
- Environment variables are a flat, easily enumerable namespace
- Most services accept credentials from anywhere in the world, so stolen keys work immediately
The attacker doesn’t need to compromise your infrastructure. They just need your credentials to land in a package they’ve backdoored, and then they wait for you to deploy.
The Real Problem Is Static Keys
Here’s the thing most post-mortems miss: the package was the delivery mechanism, not the vulnerability. The vulnerability is that a static API key sitting in TELNYX_API_KEY (or OPENAI_API_KEY, or ANTHROPIC_API_KEY) is a credential that works forever, from anywhere, for anyone who has it.
When a static key gets exfiltrated:
- The attacker has it immediately
- It works until you rotate it
- You probably won’t know it was stolen until something breaks or your bill spikes
- Rotation helps, but only after the fact
If your entire security posture against supply chain attacks is “rotate keys when notified,” you’re permanently playing defense. The next backdoored package resets the clock.
Static keys also tend to be over-scoped. A key you set up to “just test something” six months ago often has full account access because that was the path of least resistance. Supply chain attackers benefit from this directly. They don’t need your most powerful key; any key with real permissions is valuable.
What Short-Lived Scoped Tokens Change
Short-lived tokens don’t eliminate the theft. They shrink the blast radius to near zero.
If your application uses tokens that expire in 15 minutes and are scoped to a single operation, an attacker who exfiltrates one gets a credential that’s probably already invalid by the time they try to use it. Even if they catch it live, the window for abuse is minutes, not weeks.
Scope matters too. A token that can only make outbound calls on a specific Telnyx number can’t be used to enumerate your account, add phone numbers, or access your billing data. The exfiltrated credential is worth almost nothing.
This is the phantom token pattern applied to supply chain risk. You’re not trying to prevent the theft. You’re making the stolen credential worthless.
Practical Steps
Right now: If you have telnyx==4.87.1 or telnyx==4.87.2 in any environment, rotate every credential that was present in that environment’s process space. Don’t just rotate the Telnyx key. Rotate everything, because the malicious code read all of os.environ, not just Telnyx-specific variables.
This week:
- Pin your dependencies to exact versions and use a lockfile (
pip-compile,poetry.lock,uv.lock) - Audit which services still accept static long-lived keys from your applications
- For AI provider keys especially, check whether the provider offers scoped or short-lived token options
Structurally:
- Move to a proxy layer that issues short-lived tokens to your application at runtime. The application never holds a long-lived credential; it gets a scoped token valid for the current request or session
- Apply least-privilege to API keys. If a service only needs to send SMS, create a key that can only send SMS
- Add outbound egress filtering to your container/runtime environment. Malicious packages need to make outbound connections; blocking unexpected destinations stops the exfiltration even if the code runs
The proxy approach is the most durable fix. Even if another backdoored package lands in your dependency tree, the credential it can steal is a short-lived token scoped to one operation. That’s a very different outcome than handing over a full-access key.
Context and Further Reading
This attack follows the same playbook as the LiteLLM incident from last week. If you haven’t read the breakdown of that attack and how phantom tokens apply to it, start there: LiteLLM Supply Chain Attack and the Env File Problem.
For the architectural solution, the phantom token pattern for production AI agents is covered in detail here: Phantom Token Pattern for Production AI Agents.
Supply chain attacks on Python packages are increasing in frequency, and AI/communications tooling is a high-value target because those packages tend to run with rich environment access. The attack surface isn’t going away. What you can control is what an attacker gets when they succeed.