@marketfront: 25 npm Packages Reuse a Known Lure
Table of Contents
TL;DR
On July 1, 2026, the npm user marketfront batch-published 25 packages into the @marketfront
scope within a roughly three-minute window, all at version 7.0.0. Every package carries a
postinstall credential harvester and an identical README line: Internal package — Platform Engineering Team. That line is not a signature of one account. It is a reusable dependency
confusion lure template SafeDep has tracked across a series of disposable npm accounts
(mr.4nd3r50n, pik-libs, t-in-one, emcd-vue), documented in the
oob.moika.tech campaign report, and marketfront
is the latest to reuse it. The marker repeats across every wave, but the payload behind it does
not. It has shifted from a broad process.env beacon into a targeted credential-file harvester
with an RC4-hidden command-and-control host. marketfront was not the only scope to appear that
day. A few hours earlier the npm user t.tqm.mfe published @tqm-mfe/main from the same template,
swapping the e-commerce cover for internal Angular ESLint tooling aimed at a Tinkoff-adjacent
namespace.
Impact:
- On install, the payload reads roughly 20 credential and secret files including
~/.ssh,~/.aws/credentials,~/.kube/config,~/.docker/config.json,~/.npmrc,~/.netrc,~/.pgpass,~/.git-credentials,~/.env, and shell history, then exfiltrates them. - Any developer workstation or CI runner that resolves an internal-looking
@marketfrontname from the public registry executes the harvester before control returns to the build. - The 25 packages use e-commerce and marketing frontend component names as cover, which points at a specific organization’s internal package namespace rather than a generic squat.
Indicators of Compromise:
- npm scope:
@marketfront, 25 packages at version7.0.0, npm usermarketfront([email protected]), published 2026-07-01 - Sibling scope published the same day:
@tqm-mfe/mainat versions5.4.7and5.5.0, npm usert.tqm.mfe([email protected]), a fake@tinkoff/eslint-configAngular tooling cover - README marker:
Internal package — Platform Engineering Team package.jsonauthor:<Scope> Platform Engineering <platform@<scope>.io>- Install hook:
postinstall: node scripts/postinstall.js, obfuscator.io-style single-line payload - Exfiltration: gzip-compressed HTTPS POST with an
X-Secretheader to the path/api/v1/events, plus a DNS resolver beacon - The command-and-control host sits behind an RC4 plus XOR layer and was not statically resolved. No C2 domain is published here because none was recovered without executing the payload.
The reused marker
SafeDep first documented this lure template in the
oob.moika.tech campaign, which tied together four
npm accounts (mr.4nd3r50n, pik-libs, t-in-one, emcd-vue) publishing across scopes such as
@cloudplatform-single-spa, @t-in-one, and @emcd-vue. Microsoft and Sonatype reported
overlapping waves of the same dependency-confusion activity, describing the same author strings
ending in “Platform Engineering” and the same X-Secret header convention.
The @marketfront scope, published July 1, is the next rotation. The registry has already pulled
the packages, so their pages return 404. The lure template survives in a place that is harder to
take down: the still-live @emcd-vue scope, the direct predecessor, republished with fresh
version numbers (@emcd-vue/[email protected], @emcd-vue/[email protected], @emcd-vue/[email protected]).
The @emcd-vue artifacts are byte-for-byte instances of the same template, so the metadata and
payload snippets below are pulled from that live source and cross-referenced against SafeDep’s
static analysis of the @marketfront batch.
The @marketfront wave
The 25 package names read like the internal frontend modules of an online marketplace:
@marketfront/header, @marketfront/footer, @marketfront/navbar, @marketfront/bannerpopup,
@marketfront/customdealsfeed, @marketfront/fashiononboardingpopup,
@marketfront/livestreampreviewpopup, @marketfront/designsystemdevtool. This is the same
targeting logic as the earlier waves. The names mirror what a specific organization’s private
package registry would contain, so a build that is missing the private version resolves the public
malicious one instead.
SafeDep’s analysis confirmed that the decoded payload is identical across the sampled packages in
the batch, consistent with a scripted publish rather than per-package development. The scope was
created on the same day at 2026-07-01T22:59:33Z and populated in one burst.
The @tqm-mfe scope, published the same day
Earlier the same day, at 2026-07-01T17:12:57Z, a different npm account, t.tqm.mfe
([email protected]), created the @tqm-mfe scope and published a single package,
@tqm-mfe/main, first as 5.4.7 and then bumped to 5.5.0 about two hours later. The maintainer
email is new. Every prior wave rotated to a fresh throwaway address, and this one moved from
[email protected] to a proton.me account. The template did not rotate with it.
The cover identity is what changed. Where @marketfront dressed itself as an online
marketplace’s frontend modules, @tqm-mfe/main presents as internal Angular ESLint tooling. Its
README Quick Start imports @tinkoff/eslint-config, which points the decoy at a Tinkoff-adjacent
internal namespace rather than a generic e-commerce target:
// @tqm-mfe/[email protected] — package/README.mdconst { eslintConfigAngular } = require('@tqm-mfe/main');
import tinkoffConfig from '@tinkoff/eslint-config/app';import angularConfig from '@tinkoff/eslint-config-angular';Underneath the new cover, the package is the same template. The manifest carries the
scope-parameterized author string Tqm-Mfe Platform Engineering <[email protected]>, the same
github.tqm-mfe.io, jira.tqm-mfe.io, and docs.tqm-mfe.io decoy domains, the same
postinstall: node scripts/postinstall.js hook, and the same dist/index.js stub that re-exports
a ../src/index.js absent from the tarball. The README repeats the Internal package — Platform Engineering Team marker, the registry=https://npm.tqm-mfe.io internal-registry instruction, and
the telemetry paragraph naming telemetry.tqm-mfe.io.
The install hook is the same single-line obfuscator.io payload class, at roughly 182 KB. Its
sensitive strings sit behind the same RC4 layer, so the credential-file paths and the exfiltration
endpoint do not fall out of a static string dump and the command-and-control host was not resolved
without executing the code. That places @tqm-mfe/main in the same family as the credential-file
harvester documented below rather than proving its endpoint independently. The payload differs
byte-for-byte between 5.4.7 and 5.5.0, and the 5.5.0 changelog claims added ARM64 support,
so the actor is still iterating on the binary behind a stable façade.
The difference worth noting is footprint. @marketfront was a 25-package batch that mirrors a
whole internal registry, while @tqm-mfe is a single precisely named package aimed at one
internal dependency. The same operator ran both cover identities from the same template on the
same day.
The metadata fingerprint
The lure lives entirely in the package metadata and README. Here is the manifest from the live
predecessor, unchanged in structure from what SafeDep analyzed in @marketfront:
// @emcd-vue/[email protected] — package/package.json{ "name": "@emcd-vue/auth", "version": "7.1.0", "description": "Internal structured logger with log levels, context and remote drain support", "main": "dist/index.js", "scripts": { "build": "tsc --noEmit || true", "test": "node test/index.test.js", "postinstall": "node scripts/postinstall.js", "prepublishOnly": "echo 'Building...'" }, "license": "UNLICENSED", "private": false, "repository": { "type": "git", "url": "git+https://github.emcd-vue.io/platform/auth.git" }, "bugs": { "url": "https://jira.emcd-vue.io/projects/PLATFORM" }, "homepage": "https://docs.emcd-vue.io/platform/auth"}The author, repository.url, bugs.url, and homepage fields are parameterized by scope name.
Swap emcd-vue for marketfront and the fields regenerate. The description strings are drawn
from a fixed pool that the earlier waves used verbatim, for example Internal structured logger with log levels, context and remote drain support and Internal configuration loader with env, vault and remote config support.
The README does the social engineering:
# @emcd-vue/auth — package/README.md> **Internal package** — Platform Engineering Team> Docs: https://docs.emcd-vue.io/platform/auth> Issues: https://jira.emcd-vue.io/projects/PLATFORM
## Installation# Make sure .npmrc points to the internal registry:# registry=https://npm.emcd-vue.io
## TelemetryOn install, this package sends anonymous telemetry to telemetry.emcd-vue.iofor environment compatibility monitoring.The .npmrc comment and the telemetry paragraph do the social engineering. The .npmrc comment
tells the developer to point at a private registry, which is the correct security practice, so the
package reads as a legitimate internal artifact that is already published where it should be. The
telemetry paragraph pre-explains the outbound network request an alert reviewer might notice during
install, framing exfiltration as authorized monitoring. The decoy domains (docs.emcd-vue.io, jira.emcd-vue.io,
npm.emcd-vue.io) do not resolve. They exist only to make the metadata look like real internal
tooling.
The shipped dist/index.js is a stub. It re-exports a source file the package does not include:
// @emcd-vue/[email protected] — package/dist/index.js'use strict';module.exports = require('../src/index.js');Requiring the package at runtime would throw, because ../src/index.js is not in the tarball. The
library is a decoy, and the payload runs entirely from the install hook.
The payload
Every active package declares the payload through the postinstall lifecycle hook shown in the
manifest above. npm runs scripts/postinstall.js immediately after the package is placed on disk,
before the developer or CI runner regains control.
The script is a single line of roughly 160 KB of obfuscator.io-style output. It opens with the standard string-table rotation preamble and a custom base64 alphabet:
// @emcd-vue/[email protected] — package/scripts/postinstall.js (excerpt)'use strict';(function(a,b){var a0b3={a:0x58,b:0xae,c:'\x28\x66\x31\x77',...};...while(!![]){try{var d=-parseInt(Y(0x135,'\x38\x35\x5a\x46'))/... if(d===b)break;else c['push'](c['shift']());}catch(e){c['push'](c['shift']());}}}(a0d,-0x22f4b+...));The alphabet embedded in the string decoder is abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRST UVWXYZ0123456789+/=, a lowercase-first reordering of standard base64. Statically base64-decoding
the string table with that alphabet recovers primitives such as charCodeAt and fromCharCode,
which confirms the runtime char-code string building, but the sensitive strings do not fall out.
They sit behind a second RC4 layer keyed per string, which is why the credential-file paths and
the exfiltration endpoint are not visible to a plain string dump. This matches the obfuscation
class SafeDep flagged on the @marketfront batch.
SafeDep decoded the behavior statically. The payload dynamically requires fs, os, http,
https, zlib, path, and dns, reads roughly 20 credential and secret files, and exfiltrates
them:
- SSH keys (
~/.ssh) - Cloud credentials (
~/.aws/credentials,~/.kube/config,~/.docker/config.json) - Registry and package tokens (
~/.npmrc) - Network and database secrets (
~/.netrc,~/.pgpass) - Git credentials (
~/.git-credentials) - Environment files and shell history (
~/.env,~/.bash_history)
The collected data is gzip-compressed and sent over an HTTPS POST with a custom X-Secret header
to the path /api/v1/events, alongside a DNS resolver beacon. The command-and-control host is
concealed behind an RC4 plus XOR layer around an embedded configuration blob. SafeDep did not
resolve it without executing the code, and this post does not fabricate one.
How the payload evolved under a constant lure
The lure template is the stable element across the campaign. The payload is not. Across the four waves, what runs behind the same README changed in a deliberate progression:
| Wave | Scope(s) | Version pattern | Payload behavior |
|---|---|---|---|
| May 27, 2026 | @cloudplatform-single-spa, @mlspace, @car-loans and others | 99.99.99 | Cleartext constants, downloads a second stage, POSTs full process.env to oob.moika.tech/report |
| May 29, 2026 | @t-in-one, @capibar.chat, @sber-ecom-core | 5.7.1, 99.5.x | Same behavior, three-layer obfuscation, functional kill switch |
| June 1, 2026 | @emcd-vue | 6.4.8, 7.1.7 | WaCk/JScrambler obfuscation, home-directory persistence, FUSION_ second-stage handshake |
| July 1, 2026 | @marketfront, @tqm-mfe, @emcd-vue (republished) | 7.0.0, 5.4.7/5.5.0, 7.x | Targeted credential-file harvester, gzip POST to /api/v1/events, RC4-hidden C2 |
The version numbers also matured. The first wave used the unmistakable 99.99.99. The
@marketfront wave uses 7.0.0, and the republished @emcd-vue packages use 7.1.0 and 7.2.0,
which read like ordinary releases from a maintained project. Version-anomaly heuristics that catch
triple-nines do not fire on these.
Two things held constant through all four waves: the Internal package — Platform Engineering Team README marker with its scope-parameterized decoy domains, and the X-Secret header on the
exfiltration request. The README string is the cheapest part of the operation to change, and the
actor never bothered with it, while the payload, the endpoint, the persistence strategy, and the
version scheme all rotated. That makes the marker a durable pivot for hunting even as everything
technical underneath it moves.
Detection guidance
The README marker is worth hunting on, but it is a weak primary detector. npm’s search index does not index README bodies, and it deprioritizes brand-new zero-download packages, which is how a freshly batch-published scope looks in its first hours. A full-text search for the marker will miss a scope published minutes ago.
The dependable detector is the metadata and behavioral fingerprint, which survives the payload rotation:
postinstall: node scripts/postinstall.jsshipping a single-line obfuscator.io payload, next to adist/index.jsthat re-exports a source file absent from the tarball- an
authorfield ending inPlatform Engineeringwithgithub.<scope>.io,jira.<scope>.io, anddocs.<scope>.iodecoy domains - a
descriptiondrawn from the fixed “Internal … loader / logger / client” pool - outbound install-time traffic carrying an
X-Secretheader
Mitigations
- Lock every internal scope to a private registry in
.npmrc. Without a scope-locked registry, npm resolves to the public version when the private one is unavailable, which is the entire mechanism this campaign depends on. - If any
@marketfrontpackage at7.0.0, or@tqm-mfe/mainat5.4.7or5.5.0, was installed on a workstation or CI runner, treat the host as credential-compromised. Rotate SSH keys, cloud credentials (~/.aws,~/.kube,~/.docker), npm tokens, git credentials, and any secrets present in~/.envor the shell environment. - Check network logs for install-time HTTPS POSTs carrying an
X-Secretheader to a/api/v1/eventspath, and for unexpected DNS resolver activity duringnpm install. - Add the metadata and behavioral fingerprint above to standing triage rules so the next scope rotation is flagged automatically rather than rediscovered by hand.
- Run
vetagainst your lockfiles to surface malicious packages before the next install cycle.
Affected packages
All 25 @marketfront packages were batch-published at version 7.0.0 and carry the credential
harvester. Use the checker above or the list below.
| ecosystem | name | version | npm_user | has_postinstall_payload | published | |
|---|---|---|---|---|---|---|
| 1 | npm | @marketfront/actualordersnippetpopup | 7.0.0 | marketfront | yes | 2026-07-01 |
| 2 | npm | @marketfront/advertisingdevtool | 7.0.0 | marketfront | yes | 2026-07-01 |
| 3 | npm | @marketfront/bannerpopup | 7.0.0 | marketfront | yes | 2026-07-01 |
| 4 | npm | @marketfront/baobabtech | 7.0.0 | marketfront | yes | 2026-07-01 |
| 5 | npm | @marketfront/basemarkettemplate | 7.0.0 | marketfront | yes | 2026-07-01 |
| 6 | npm | @marketfront/blenderdevtool | 7.0.0 | marketfront | yes | 2026-07-01 |
| 7 | npm | @marketfront/captchaservice | 7.0.0 | marketfront | yes | 2026-07-01 |
| 8 | npm | @marketfront/changefilter | 7.0.0 | marketfront | yes | 2026-07-01 |
| 9 | npm | @marketfront/commonecommerce | 7.0.0 | marketfront | yes | 2026-07-01 |
| 10 | npm | @marketfront/customdealsfeed | 7.0.0 | marketfront | yes | 2026-07-01 |
| 11 | npm | @marketfront/designsystemdevtool | 7.0.0 | marketfront | yes | 2026-07-01 |
| 12 | npm | @marketfront/devtoolsloader | 7.0.0 | marketfront | yes | 2026-07-01 |
| 13 | npm | @marketfront/digitalherobannercarousel | 7.0.0 | marketfront | yes | 2026-07-01 |
| 14 | npm | @marketfront/dynamicpageparams | 7.0.0 | marketfront | yes | 2026-07-01 |
| 15 | npm | @marketfront/errorcounter | 7.0.0 | marketfront | yes | 2026-07-01 |
| 16 | npm | @marketfront/fashiononboardingpopup | 7.0.0 | marketfront | yes | 2026-07-01 |
| 17 | npm | @marketfront/fingerprint | 7.0.0 | marketfront | yes | 2026-07-01 |
| 18 | npm | @marketfront/footer | 7.0.0 | marketfront | yes | 2026-07-01 |
| 19 | npm | @marketfront/gotoauthpopup | 7.0.0 | marketfront | yes | 2026-07-01 |
| 20 | npm | @marketfront/header | 7.0.0 | marketfront | yes | 2026-07-01 |
| 21 | npm | @marketfront/infopopup | 7.0.0 | marketfront | yes | 2026-07-01 |
| 22 | npm | @marketfront/livestreampreviewpopup | 7.0.0 | marketfront | yes | 2026-07-01 |
| 23 | npm | @marketfront/madvpopup | 7.0.0 | marketfront | yes | 2026-07-01 |
| 24 | npm | @marketfront/mychatspreloader | 7.0.0 | marketfront | yes | 2026-07-01 |
| 25 | npm | @marketfront/navbar | 7.0.0 | marketfront | yes | 2026-07-01 |
- npm
- malware
- supply-chain
- dependency-confusion
Author
Kunal Singh
safedep.io
Share
The Latest from SafeDep blogs
Follow for the latest updates and insights on open source security & engineering
@copilot-mcp/apex: A macOS Infostealer Re-Published on npm After Takedown
The npm security team removed the original @apexfdn/apex package for malicious code, and the operator re-published the same postinstall macOS infostealer as @copilot-mcp/apex about 11 hours later. It...
Official jscrambler npm Package Compromised Across Multiple Releases
The official jscrambler npm package (60K monthly downloads) was trojanized starting at 8.14.0 through an npm account or CI compromise. The attacker republished the same Rust infostealer across five...
AsyncAPI Packages Compromised with Miasma RAT
Four @asyncapi npm packages were published with obfuscated malware on July 14, 2026 via compromised CI workflows. The payload downloads Miasma RAT, a credential stealer targeting browsers, SSH keys,...
nodemon-sudo: an npm Backdoor With No Install Script
nodemon-sudo copies the real nodemon byte for byte, adds nothing malicious to its own code, and injects one extra dependency, tslint-conf, a repackaged pino logger carrying a backdoor. There is no...
Ship Code.
Not Malware.
Start free with open source tools on your machine. Scale to a unified platform for your organization.