TL;DR
For roughly two months, a single npm infostealer has resurfaced under a rotating series of package names while shipping a nearly unchanged payload. It began openly as forge-jsxy, spawned forge-jsx3 / forge-jsx4 siblings, then dropped the "forge" branding entirely to pose as developer infrastructure — first zredis-typed, and most recently pinokio-redis.
Every variant carries the same internal toolchain: files named forge-jsx-explorer-*, a relayPackageServe module that serves a forge-jsxy-package.tgz, and a durable runtime hidden under a dotted directory so it survives deletion of the npm package.
The payload reads cryptocurrency wallet material and developer credentials from the host and transmits them to Discord webhooks, HuggingFace Hub uploads, and a hardcoded C2 endpoint. It installs OS-level autostart so it runs again after reboot, and it captures clipboard, keyboard, and screen activity. The latest variant, pinokio-redis, describes itself as an "Autodesk Forge" integration — a description unrelated to what the code does.
| Ecosystem | npm |
| Payload class | multi-stage infostealer + remote-control agent (RAT) |
| Primary targets | crypto wallets, browser wallet extensions, developer/cloud credentials |
| Persistence | hidden durable runtime + OS autostart (survives package removal) |
| Exfiltration | Discord webhooks + relay, HuggingFace Hub, hardcoded C2 on port 9877 |
| Observed span | May 2026 – July 2026 (ongoing) |
| Latest name | pinokio-redis:1.0.127 |
Attack Anatomy
Every variant in this family installs through the same shape: a package that looks inert on its listing page but declares a postinstall hook, so the payload runs the moment the package is installed as a dependency. The hook drives a five-stage chain.
- Stage 1 — materialize. The install script decodes a bundled, encoded blob into a set of runtime scripts and writes them into a hidden directory under the platform config location (the forge-jsxy versions used a dotted .forge-jsxy/runtime/v<version>/ path). Because this copy lives outside node_modules, removing or unpublishing the npm package does not remove the running agent.
- Stage 2 — persist. The chain registers an OS autostart entry so the agent relaunches on reboot. On Windows this runs through a hidden PowerShell diagnostics script; the agent process is spawned detached and with windowsHide set so no console window appears.
- Stage 3 — evade. The runtime checks for continuous-integration markers and quietly exits when it detects a CI environment, so automated build sandboxes see nothing. In the forge-jsxy versions, a source comment stated that a visible forgeInstall field was deliberately omitted from package.json “because that field would be visible on npm” — the operator kept the trigger metadata off the public listing on purpose.
- Stage 4 — collect. Once running, the agent harvests, in one pass:
- Crypto wallet material. A bundled secret_filename_patterns.json target list drives a filesystem sweep for wallet.dat, *.mnemonic /.seed / *.phrase files, keypair.json, Ethereum UTC– keystores, and *.p12 / *.pfx / *.jks key stores. Wallet-derivation libraries (bip39, secp256k1, base58check, and Solana helpers) are present in the bundle.
- Browser wallet extensions. A chromiumExtensionDbHarvest module reads the local extension-storage databases of Chromium browsers, the location where browser-based wallet extensions keep their vault data.
- Credentials and secrets. The agent scans shell history and secret-bearing files and reads developer/cloud credentials, then encodes HuggingFace credentials for upload.
- Live input. Clipboard contents, keyboard input, and screenshots are captured on an ongoing basis.
Stage 5 — exfiltrate. Collected data leaves the host through three channels operating in parallel: Discord webhooks (discordWebhookPost) and a Discord relay uploader that talks to `discord.com/api/v10` (discordRelayUpload), HuggingFace Hub uploads (hfUpload), and a hardcoded C2 endpoint reached over port 9877. A relayPackageServe module additionally serves a forge-jsxy-package.tgz back out — the same artifact name across every rename.
The important structural point is that stages 1–3 run at install time with no user interaction, and stage 1 places the payload somewhere the package manager does not track. A developer who notices the odd dependency and runs npm uninstall removes the listing but not the agent.
The payload also resists a quick read. The runtime scripts are not shipped in the clear: an encode-deployment step keeps the operational logic as an encoded blob that only expands into readable scripts once the install chain decodes it into the hidden runtime directory. The files that are visible on the listing — the thin postinstall shim and a dist-materialization helper — reveal little about what ultimately runs. This encoded-delivery design has had a measurable effect on triage: across the family’s lifespan, multiple confirmed variants were scored safe by automated classification, and the payload has been opaque enough to stall automated review outright. The behaviors that give the family away are visible only after the encoded stage is expanded, and the runtime is read directly.
Timeline
The family’s payload has stayed remarkably stable; what changes is the name on the box. The arc runs from open “forge” branding toward disguises as unrelated developer tools.
| Date (2026) | Package : version | Note |
|---|---|---|
| May (early) | forge-jsxy:1.0.81, :1.0.90 | Earliest confirmed variants — RAT with AES-256-GCM-encrypted C2 |
| May 19 | forge-jsxy:1.0.92 / 1.0.105 / 1.0.107 / 1.0.108 | Campaign in active publishing; 1.0.92 was initially mislabeled safe and reversed on code re-review |
| Late May | forge-jsx3 / forge-jsx4:1.0.122 / 1.0.123, zod-pino | Sibling package names, same toolchain |
| Jun 4 | forge-jsxy:1.0.120 | Continuation; classified safe by the ML model (a miss) |
| Jun 21–23 | forge-jsxy:1.0.121 | Module set unchanged from 1.0.120 |
| Jul 6 | zredis-typed:1.0.127 | "forge" dropped from the name; still ships forge-jsxy-package.tgz and forgeAgent* config keys |
| Jul 7 | pinokio-redis:1.0.127 | Same toolchain; new C2; describes itself as an "Autodesk Forge" integration |
Two things stand out across the timeline. First, the version number 1.0.127 is reused verbatim by both zredis-typed and pinokio-redis — the naming shifts but the build lineage does not reset. Second, the shift away from “forge-*” names coincides with a shift toward names that read as ordinary infrastructure (redis, pinokio), lowering the chance a developer skims the name and hesitates.
Indicators of Compromise
IOCs below are defanged. The C2 host rotated between the forge-jsxy line and the latest variant while keeping the same port.
| Indicator | Value | Seen in |
|---|---|---|
| C2 endpoint | 212.193.3[.]61:9877 | pinokio-redis:1.0.127 |
| C2 endpoint | 79.108.162[.]160:9877 | forge-jsxy line, zredis-typed:1.0.127 |
| Exfil channel | discord.com/api/webhooks/… (webhook post) | all variants |
| Exfil channel | discord.com/api/v10 (relay upload) | all variants |
| Exfil channel | HuggingFace Hub upload | all variants |
| Served artifact | forge-jsxy-package.tgz (via relayPackageServe) | all variants |
| Toolchain files | chromiumExtensionDbHarvest, discordRelayUpload, discordWebhookPost, hfUpload, encode-hf-credentials, forge-jsx-explorer-* | all variants |
| Target list | secret_filename_patterns.json (wallet.dat, .mnemonic/.seed/.phrase, keypair.json, UTC--, .p12/.pfx/*.jks) | all variants |
| Hidden runtime | dotted …/.forge-jsxy/runtime/v<version>/ config-dir path | forge-jsxy line |
| Windows persistence | hidden PowerShell "forge diagnostics" autostart script | all variants |
| Install trigger | postinstall hook invoking a dist-materialization script chain | all variants |
The most durable detection signal is not any single host or webhook — those rotate — but the toolchain filenames and the forge-jsxy-package.tgz artifact, which have persisted verbatim through every rename.
Attribution & Observed Behavior
The packages were published under more than one npm account. The forge-jsxy line used the publisher jacksonkaandorp2 (jacksonkaandorp2@outlook[.]com); the latest pinokio-redis variant was published by donimique (donimiqueakers@gmail[.]com). Neither account carried a verified email or a linked source repository. The through-line across accounts is the toolchain, not the publisher identity — the same module names, the same served .tgz, and the same exfil design recur regardless of who published.
Several observable behaviors indicate deliberate concealment rather than accidental over-collection:
The runtime is placed outside node_modules in a hidden directory, so it outlives the package that delivered it.
The agent bails out when it detects a CI environment, limiting exposure in automated analysis sandboxes.
The agent process is launched detached and window-hidden.In the forge-jsxy versions, a comment tied the omission of a visible install field to that field being “visible on npm.”
The public package descriptions are unrelated to the code — pinokio-redis presents as an “Autodesk Forge” integration.
One recurring detection gap is worth noting for defenders relying on automated classification: multiple confirmed variants in this family were scored as safe by an ML classifier before code review corrected the verdict. The stable elements a reviewer can anchor on — the install-time chain, the hidden runtime, the toolchain filenames — are exactly the ones that survive the renames.
We describe the above as observed behavior. We do not attribute the campaign to any named actor, and we make no claim about the operator’s identity or objectives beyond what the code and infrastructure demonstrate.
Impact, Trends & Defender Guidance
Who is exposed. The collection set is squarely aimed at developer machines: cryptocurrency wallets and keystores, browser wallet extensions, shell history, and cloud/service credentials. Anyone who installs an affected package — directly or as a transitive dependency — during the window it was live is in scope, and the persistence design means the exposure does not end when the package is removed.
Why the persistence matters. Because stage 1 copies the payload into a hidden runtime directory and stage 2 registers autostart, the usual remediation instinct (uninstall the bad dependency, delete node_modules, reinstall) does not evict the agent. Incident response has to look for the out-of-tree runtime and the autostart entry, not just the package.
The trend. This family illustrates a pattern worth watching: a payload that is stable and mature, wrapped in a stream of disposablepackage names that migrate away from any name a prior takedown made recognizable, toward names that read as ordinary infrastructure. Name-based blocklists and publisher reputation age out quickly against this; behavior- and artifact-based signals do not.
For developers and consumers
Treat install-time scripts as the primary risk surface. Install with scripts disabled where feasible and review any dependency that declares a postinstall. Pin and vet transitive dependencies; this class of malware arrives as easily through a sub-dependency as a direct one.
On a suspected host, hunt beyond the package: look for a hidden runtime directory in the platform config location, an unexpected autostart entry, and outbound connections to Discord webhook or :9877 endpoints.
Assume any wallet seed phrase, keystore, or credential present on an affected host during the exposure window is compromised, and rotate.
For registries and defenders
Alert on the durable artifacts (forge-jsxy-package.tgz, the forge-jsx-explorer-* / chromiumExtensionDbHarvest / discordRelayUpload filenames) rather than the package name, which changes.
Flag the structural combination the family relies on — a postinstall that materializes scripts into a hidden config-dir path plus an OS autostart registration — independent of the specific strings involved.
The name on the package has changed at least four times; the crate inside has not. Detection built on what the code does has outlasted every relabeling so far.



