Back to Feed
Supply ChainMay 30, 2026

Malicious npm packages abuse dependency confusion to profile developer environments

Microsoft discovers 33 malicious npm packages using dependency confusion to profile developer environments.

Summary

Microsoft Threat Intelligence uncovered an active supply chain attack involving 33 malicious npm packages registered under organizational scopes that mirror real corporate namespaces, exploiting dependency confusion to deploy reconnaissance payloads. The attack, attributed to a single operator using three maintainer aliases (mr.4nd3r50n, ce-rwb, t-in-one), was published in two bursts on May 28-29, 2026, and silently collects system information, hostnames, environment variables, and credentials during npm install via obfuscated postinstall hooks. The campaign includes a RECON_ONLY flag enabling server-side toggling for follow-on exploitation and targets nine organizational scopes including Sberbank's payment infrastructure.

Full text

Share Link copied to clipboard! Tags npm Content types Research Products and services Microsoft Defender Topics Actionable threat insightsDefending against advanced tactics Microsoft Threat Intelligence has uncovered an active supply chain attack involving malicious npm packages registered under organizational scopes that mirror real internal corporate namespaces, employing dependency confusion technique to deploy an obfuscated reconnaissance payload. On May 28 and May 29, 2026, a threat actor operating under three maintainer aliases mr.4nd3r50n (mr.4nd3r50n@yandex[.]ru), ce-rwb (ogvanta@yandex[.]ru), and t-in-one (t-in-one@yandex[.]ru) published malicious packages across two publishing bursts. The packages impersonate internal corporate packages across nine different organizational scopes using a dependency confusion technique, and several spoof internal enterprise infrastructure URLs (GitHub Enterprise, Jira, documentation portals) in their package.json to appear legitimate. Once installed, the packages download and execute an obfuscated reconnaissance payload from an attacker-controlled command-and-control (C2) server. All packages in the cluster ship the same heavily obfuscated postinstall stager and connect to the same C2 endpoint, a ~17 KB JavaScript dropper used for for environment fingerprinting and credential reconnaissance. The payload runs silently during npm install and operates in “reconnaissance-only” mode, collecting system information, hostnames, environment variables, and developer context. The architecture includes a RECON_ONLY flag that can be toggled server-side for full exploitation in follow-on attacks. Based on our investigation and feedback to the npm team these repos and users were taken down. Key capabilities observed in the campaign include automatic execution through npm lifecycle hooks, obfuscator.io-style anti-analysis techniques, platform-specific payload delivery (Windows, macOS, Linux), continuous integration and continuous delivery (CI/CD) environment detection and bypass, cache-based deduplication to evade repeated-execution monitoring, and a two-phase attack design (reconnaissance now, exploitation later). Attack chain overview The campaign spans dozens of scoped packages published under three npm maintainer accounts that our forensic analysis attributes to a single operator (detailed in the Attribution section below). The attack proceeds through: Publication of dependency confusion packages under three actor identities across nine organizational scopes Automatic payload execution through a postinstall hook during npm install Execution chain: npm install → postinstall → scripts/postinstall.js (obfuscated) → HTTPS GET to C2 → write payload to tmpdir → spawn detached process Environment reconnaissance with credentials and context exfiltration using environment variables passed to the spawned payload Figure 1. Dependency confusion attack flow. The lure: Dependency confusion and spoofed internal metadata The actor adopted three social-engineering techniques designed to drive installs through misconfigured package managers or developer trust transference: Namespace squatting The attacker registered packages under organizational scopes that mirror real internal corporate namespaces: @cloudplatform-single-spa, @wb-track, @data-science, @ce-rwb, @payments-widget, @travel-autotests, @t-in-one, @capibar.chat, and @sber-ecom-core. Package names like svp-baas, enterprise, monitoring, ssh-keys, shared-front, payments-widget-sdk, add_application_service_token, ui-kit, and sberpay-widget target specific internal services — the last of which directly impersonates Sberbank’s SberPay payment widget. Spoofed enterprise metadata Every package sets its package.json homepage, repository, bugs, and author fields to fabricated but realistic-looking internal infrastructure URLs. For example: Repository: git+https://github[.]cloudplatform-single-spa[.]io/platform/svp-baas.git Homepage: https://docs[.]cloudplatform-single-spa[.]io/platform/svp-baas Bugs: https://jira[.]cloudplatform-single-spa[.]io/projects/PLATFORM Author: Cloudplatform-Single-Spa Platform Engineering <platform@cloudplatform-single-spa[.]io> These URLs follow the pattern of enterprise GitHub, Jira, and documentation portals, lending an air of legitimacy designed to evade casual inspection during code review. Inflated version numbers mr.4nd3r50n uses version 100.100.100, an absurdly high version number designed to win npm’s server resolution against any real internal package version. ce-rwb uses a more realistic 3.5.22 to blend in with legitimate release histories. t-in-one mixes both tactics: the ten @t-in-one packages ship at 5.7.1, while @capibar.chat/ui-kit (99.5.7) and @sber-ecom-core/sberpay-widget (99.5.8) use inflated numbers — and both of the latter scopes were pre-staged with 99.0.7 releases on 2026-05-04, weeks before the main bursts. Figure 2. The malicious package.json. The postinstall hook gains code execution on every npm install. Version 100.100.100 ensures the malicious package wins dependency resolution over any real internal version. Execution: npm lifecycle hook abuse Every package in the cluster declares an automatic install-time hook in package.json: "scripts": { "build": "tsc --noEmit || true", "test": "node test/index.test.js", "postinstall": "node scripts/postinstall.js", "prepublishOnly": "echo 'Building...'" } The malicious code executes the moment a victim runs npm install; no require() from victim code is needed. The build and test scripts are cosmetic, designed to make the package appear to have a legitimate development workflow. Stager: Obfuscated JavaScript dropper scripts/postinstall.js is approximately 7 KB of heavily obfuscated JavaScript using obfuscator.io-style techniques: String array encoding: All meaningful strings (URLs, function names, environment variable keys) are stored in a rotated array and decoded at runtime through a custom Base64 variant Control flow flattening: Logic branches are obscured through computed dispatch tables Dead code injection: Anti-analysis noise makes manual review prohibitively time-consuming Self-defending code: Anti-tampering checks detect modifications to the obfuscated code Figure 3. Obfuscated postinstall.js. After deobfuscation, the payload reveals the C2 URL, platform detection logic, and file-drop/spawn execution pattern. Execution flow: from npm install to detached payload The deobfuscated execution flow proceeds through eight distinct stages: CI detection bypass: The stager checks for the CI environment variable (or scope-specific equivalents like CLOUDPLATFORM_SINGLE_SPA_NO_TELEMETRY). If detected, execution silently aborts. This avoids triggering alerts in monitored CI/CD pipelines where security tooling is more likely to detect anomalous behavior. Node.js version validation: The stager verifies process.versions.node >= 16.0. Older Node.js versions are skipped, likely because the payload depends on modern APIs. Cache deduplication: A cache directory is created at ~/.cache/<scope>_init/ (for example, ~/.cache/._cloudplatform-single-spa_init/). The stager generates a hash key from the package name, version, and project root path. If a cache entry exists and hasn’t expired, the stager exits. This prevents the payload from re-running on every npm install in the same project, reducing the chance of detection through repeated network connections. Project root detection: The stager walks up the directory tree from process.cwd() looking for package.json, yarn.lock, or .git to identify the project root. This context is incorporated into the cache key and passed to the payload. Platform detection: os.platform() determines the target OS variant (win32 → win, darwin → mac, default → linux). Payload download: An HTTPS GET request is made to the C2 server at https://oob.moika[.]tech/payload/<platform> with a 30-second timeout. The response is a binary payload. Payload drop: The downloaded binary is written

Indicators of Compromise

  • email — mr.4nd3r50n@yandex.ru
  • email — ogvanta@yandex.ru
  • email — t-in-one@yandex.ru
  • malware — npm postinstall reconnaissance stager

Entities

npm (technology)Microsoft (vendor)Sberbank (vendor)dependency confusion (technology)Malicious npm namespace squatting campaign (campaign)