---
title: "@marketfront: 25 npm Packages Reuse a Known Lure"
description: "On July 1, 2026, npm user marketfront batch-published 25 packages carrying the same README lure SafeDep has tracked across four earlier accounts (mr.4nd3r50n, pik-libs, t-in-one, emcd-vue): \"Internal package — Platform Engineering Team\". The marker is a reusable dependency-confusion template. It stayed constant across every wave while the payload behind it evolved from a process.env beacon into a credential-file harvester with an RC4-hidden C2."
url: "https://safedep.io/blog/marketfront-dependency-confusion-campaign"
author: "Kunal Singh"
published: "2026-07-02"
---

# @marketfront: 25 npm Packages Reuse a Known Lure

## 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](https://safedep.io/blog/oob-moika-tech-dependency-confusion-campaign), 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.

Affected packages: [CSV](https://safedep.io/data/blogs/marketfront-campaign-packages.csv)

**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`
  (`marketfront@tutamail.com`), 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` (`t.tqm.mfe@proton.me`), 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](https://safedep.io/blog/oob-moika-tech-dependency-confusion-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/auth@7.1.0`, `@emcd-vue/loans@7.2.0`, `@emcd-vue/b2b-pay-form@5.8.0`).
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`
(`t.tqm.mfe@proton.me`), 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
`marketfront@tutamail.com` 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:

```js
// @tqm-mfe/main@5.5.0 — 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 <platform@tqm-mfe.io>`, 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`:

```json
// @emcd-vue/auth@7.1.0 — 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 <platform@emcd-vue.io>",
  "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:

```javascript
// @emcd-vue/auth@7.1.0 — 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:

```javascript
// @emcd-vue/auth@7.1.0 — 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.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`](https://github.com/safedep/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](https://safedep.io/data/blogs/marketfront-campaign-packages.csv)
