@marketfront: 25 npm Packages Reuse a Known Lure

11 min read

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.

active campaign25 npm packages compromised.
25 pkgs compromised

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 @marketfront name 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 version 7.0.0, npm user marketfront ([email protected]), published 2026-07-01
  • Sibling scope published the same day: @tqm-mfe/main at versions 5.4.7 and 5.5.0, npm user t.tqm.mfe ([email protected]), a fake @tinkoff/eslint-config Angular tooling cover
  • README marker: Internal package — Platform Engineering Team
  • package.json author: <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-Secret header 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.md
const { 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...'"
},
"author": "Emcd-Vue Platform Engineering <[email protected]>",
"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
## Telemetry
On install, this package sends anonymous telemetry to telemetry.emcd-vue.io
for 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:

WaveScope(s)Version patternPayload behavior
May 27, 2026@cloudplatform-single-spa, @mlspace, @car-loans and others99.99.99Cleartext constants, downloads a second stage, POSTs full process.env to oob.moika.tech/report
May 29, 2026@t-in-one, @capibar.chat, @sber-ecom-core5.7.1, 99.5.xSame behavior, three-layer obfuscation, functional kill switch
June 1, 2026@emcd-vue6.4.8, 7.1.7WaCk/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.xTargeted 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.js shipping a single-line obfuscator.io payload, next to a dist/index.js that re-exports a source file absent from the tarball
  • an author field ending in Platform Engineering with github.<scope>.io, jira.<scope>.io, and docs.<scope>.io decoy domains
  • a description drawn from the fixed “Internal … loader / logger / client” pool
  • outbound install-time traffic carrying an X-Secret header

Mitigations

  1. 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.
  2. If any @marketfront package at 7.0.0, or @tqm-mfe/main at 5.4.7 or 5.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 ~/.env or the shell environment.
  3. Check network logs for install-time HTTPS POSTs carrying an X-Secret header to a /api/v1/events path, and for unexpected DNS resolver activity during npm install.
  4. Add the metadata and behavioral fingerprint above to standing triage rules so the next scope rotation is flagged automatically rather than rediscovered by hand.
  5. Run vet against 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.

marketfront-campaign-packages.csv
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
25 rows
| 6 columns
  • npm
  • malware
  • supply-chain
  • dependency-confusion

Author

Kunal Singh

Kunal Singh

safedep.io

Share

The Latest from SafeDep blogs

Follow for the latest updates and insights on open source security & engineering

AsyncAPI Packages Compromised with Miasma RAT

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,...

SafeDep Team
Background
SafeDep Logo

Ship Code.

Not Malware.

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