npm package [email protected] — marketed as a Node.js integration layer for Autodesk Forge — is a multi-platform RAT and infostealer. This technical breakdown walks through the postinstall kill chain, the LaunchAgent/systemd/Task Scheduler persistence primitives, the .env and shell-history harvesting modules, and the AES-256-GCM-obfuscated C2 blob, with SecureNexus SOVA analysis evidence.
Inside forge-jsx: Anatomy of a Multi-Platform npm RAT Masquerading as an Autodesk Forge SDK
By the SecureNexus SOVA research team — April 20, 2026
npm package [email protected] claims to be a Node.js integration layer for Autodesk Forge. It isn't. Multiple supply-chain scanners have independently flagged it as malicious; SecureNexus SOVA's autonomous pipeline concurs with a verdict of malicious at 0.97 confidence and an overall recommendation of block. This post walks through the evidence SOVA surfaced — the code paths, signals, and runtime behavior that make this package a purpose-built RAT.
If your CI pipeline ran npm install forge-jsx at any point in the last 13 days, skip to the response section first, then come back.
What SecureNexus SOVA's Pipeline Saw
SOVA enriches every published package through multiple stages of static analysis, behavioral signal extraction, and automated reasoning. The raw numbers on [email protected] were loud on their own.
| Dimension | Observation |
|---|---|
| SOVA verdict | malicious (confidence 0.97) |
| Risk score | 32 |
| Files scanned | 103 |
| Files flagged | 71 |
| Capability buckets flagged malicious | network, filesystem, shell, code_execution, crypto, install_hooks, env_read |
| Maintainer trust score | 0.541 |
| Overall recommendation | block — malicious code detected |
| Publisher | johnceballos0716 (Gmail), single maintainer, no repository |
Ten SOVA signals lit up simultaneously: crypto, env_read, filesystem, network, shell, dynamic code execution, endpoint inventory, hardcoded endpoint, hidden dependency, and npm install-hook presence. A legitimate SDK typically lights one or two of those — a thin HTTP client might tick network and env_read for config. Seven capability buckets with simultaneous malicious-intent classification is a pattern SOVA only sees in RATs and infostealers.

The Install-Time Trigger
The entire attack chain starts in package/package.json line 17:
"postinstall": "node scripts/postinstall-clipboard-event.mjs && node scripts/ensure-dist.mjs && node scripts/postinstall-bootstrap.mjs && node scripts/postinstall-agent.mjs"SOVA flagged this immediately as an install-time code-execution pattern. Four chained postinstall scripts are already unusual; pairing them with spawnSync and spawn from node:child_process imports in every one of those scripts — ensure-dist.mjs:12, postinstall-agent.mjs:14, postinstall-bootstrap.mjs:16, restart-agent.mjs:11 — is not how node-gyp rebuild works. This is a pre-main code-execution chain that runs before the developer has looked at a single file.
Multi-Platform Persistence
The strongest tell that this is a RAT, not a developer convenience, is how it plants itself on each OS. SOVA surfaced these paths under filesystem capability with malicious intent.
macOS — package/dist/autostart/darwin.js:54
return path.join(process.env.HOME || os.homedir(),
"Library", "LaunchAgents",
constants_1.MACOS_PLIST_FILENAME);The package writes a LaunchAgent plist into the user's ~/Library/LaunchAgents/ directory. LaunchAgents start automatically at login and run under the user's identity — a classic user-land persistence primitive.
Linux — package/dist/autostart/linux.js:54
const home = process.env.HOME || process.env.USERPROFILE || os.homedir();The Linux path installs a systemd user unit under $XDG_CONFIG_HOME/systemd/user/. The companion module autostart/agentEnvFile.js reads XDG_RUNTIME_DIR, DISPLAY, WAYLAND_DISPLAY, DBUS_SESSION_BUS_ADDRESS, and XAUTHORITY — everything it needs to attach to an active graphical session.
Windows — handled via a Task Scheduler registration using the same spawnSync pattern from the postinstall chain. The process.env.SystemRoot || C:\Windows reference in dist/clipboardExec.js:46 confirms Windows as an intended target.
All three persistence primitives are silent, survive reboot, and do not require administrator rights.

Data Collection: What It Actually Steals
SOVA flagged 13 files under environment-read capability alone. The patterns are not the benign process.env.NODE_ENV or process.env.PATH accesses SOVA whitelists — they read very specific, non-standard variables that an SDK has no business touching.
// dist/autostart/agentEnvFile.js:205
const bundleKey = (process.env.FORGE_JS_BUNDLE_KEY || "").trim();
// dist/deploymentDefaults.js:51
const envKey = process.env.FORGE_JS_BUNDLE_KEY?.trim();
// dist/cli-agent.js:11
const have = (process.env.FORGE_JS_SYNC_URL || "").trim() ||
(process.env.CFGMGR_API_URL || "").trim();On top of that, three modules scan the host filesystem for credentials:
// dist/shellHistoryScan.js:97 — walks $HOME for .bash_history, .zsh_history, etc.
const docs = process.env.USERPROFILE || process.env.HOME || home;
// dist/envScan.js — recursive .env discovery; also tripped SOVA's dynamic-code rule
// dist/clientId.js:54 — user profile enumeration
const prof = (process.env.USERPROFILE || "").trim();And two modules — dist/clipboardEventWatcher.js and dist/clipboardNapi.js — implement clipboard capture. Both pull in transitive native modules that were not declared in package.json, which SOVA raised as a hidden-dependency finding. That pattern is textbook wallet-clipper / credential-grabber behavior.
C2 Obfuscation: AES-256-GCM Over Hardcoded Bytes
SOVA tagged package/scripts/encode-deployment.mjs:22 with crypto capability and flagged its intent as malicious. The module imports node:crypto and builds a blob SOVA identified as deploymentCipherData — an AES-256-GCM-encrypted structure containing the C2 host, port, and shared secret. The unwrap key is pulled from the environment at runtime:
// dist/deploymentDefaults.js:51
const envKey = process.env.FORGE_JS_BUNDLE_KEY?.trim();That design defeats naive static analysis: grepping the package for an IP or a domain returns nothing. The endpoint only materializes in memory after the crypto unwrap, keyed on an env var the attacker sets during the first-run handshake.
Exfiltration itself runs through two transports:
// dist/hfUpload.js:107
const res = await fetch(input, init);
// dist/relayServer.js — WebSocket relay (node:http + ws)The dist/agentRunner.js:113 string reveals the kill-switch intentionally built into the malware: setting FORGE_JS_DISABLE_SYNC=1 mutes the sync loop. That variable exists so the operator can selectively silence the agent during sandbox analysis.
// dist/agentRunner.js:113
"Set FORGE_JS_SYNC_URL (or CFGMGR_API_URL), and avoid FORGE_JS_DISABLE_SYNC=1 / "
Publisher and Manifest Red Flags
SOVA evaluates signals that have nothing to do with source code — publisher history, release cadence, manifest hygiene — and for forge-jsx the picture is damning:
| Signal | Finding |
|---|---|
| Maintainer concentration | Single maintainer (bus factor 1) |
| Source transparency | No declared repository — source cannot be independently verified |
| Install scripts | Four-command postinstall chain, not a native build |
| Release hygiene | No CHANGELOG, no LICENSE file, no SECURITY.md, no CODEOWNERS |
| Quality signals | No tests shipped |
SOVA's maintainer trust model returned 0.541 — pulled down by history 0.4, diversity 0.3, longevity 0.4, geography 0.3. Publisher country is unknown. Every maintainer-trust axis that SOVA measures is in the bottom third, and the package was published rapid-fire across twelve versions in thirteen days by an account with no prior ecosystem footprint.
The Brand-Jacking Angle
Autodesk retired the Forge brand years ago and rebranded to Autodesk Platform Services (APS). A package named forge-jsx in 2026 exploits two things at once: developers who still search for forge npm out of muscle memory, and the fact that there is no canonical npm package named @autodesk/forge today to collide against. The scoped twin @johntaohunter/forge-jsx publishes the same payload under a different namespace — a common tactic to survive takedowns on one of the two names.
Realistic Attack Scenario
Picture a DevOps engineer at a fintech working on a BIM-integration prototype. They remember using Forge APIs years ago, run npm install forge-jsx on their laptop, and move on. Within seconds:
- The postinstall chain drops a LaunchAgent into ~/Library/LaunchAgents/.
- shellHistoryScan.js walks ~/.zsh_history — capturing every aws configure, kubectl, ssh, and gh auth command they ever ran, along with the arguments.
- envScan.js recursively walks the home directory for .env files. It picks up the .env from a staging repo with AWS keys, and a .env.local from their personal side project with an OpenAI token.
- The clipboard watcher starts capturing anything copied — including 1Password unlock codes, AWS MFA tokens, and any API secret the engineer copies from a teammate's Slack.
- All of it is AES-256-GCM-encrypted and relayed over WebSocket to the operator.
- The LaunchAgent re-activates after every login. The engineer never sees a process name they don't recognize because cli-autostart.js hides under a generic node binary in their user session.
By the time the engineer reads a blog like this one, the credentials that matter are already elsewhere.
Detection and Response
Immediate containment
- npm uninstall forge-jsx and npm uninstall @johntaohunter/forge-jsx
- Grep every package-lock.json, pnpm-lock.yaml, yarn.lock, and npm-shrinkwrap.json across your org for the string forge-jsx
- Rotate AWS access keys, GitHub PATs, npm tokens, OpenAI/HF tokens, and any credential that passed through an affected shell
- On macOS, delete any plist from ~/Library/LaunchAgents/ that matches the MACOS_PLIST_FILENAME constant
- On Linux, run systemctl --user list-units and remove any unit not placed by your configuration management
- On Windows, audit Task Scheduler entries created in the install window
Operational detection at scale
SecureNexus SOVA customers had [email protected] marked block within minutes of the enriched scan completing, because the seven-capability pattern was already modeled in SOVA's default policy. Teams without that coverage typically detect postinstall RATs only after SIEM alerts on outbound WebSocket connections to new external IPs — by which point the credentials are gone.
Key Takeaways
- Seven malicious-intent capability buckets in one package is a RAT, not an SDK. One or two fire in legitimate packages; seven does not.
- Postinstall chains are pre-main code execution. Treat every postinstall-enabled dependency as supply-chain code that runs before your code review does.
- Static obfuscation is now standard in npm malware. forge-jsx keeps its C2 inside an AES-GCM blob unwrapped at runtime. Scanners that only grep for IPs and domains will miss it.
- Maintainer trust is a first-class signal. A 13-day-old account with no repo, twelve versions, and a trust score of 0.541 is the entire package review by itself.
- Brand-jacking retired names is a durable technique. Forge is gone from Autodesk's marketing but not from developer muscle memory. Expect more of these.
How SecureNexus SOVA Helps
Traditional SCA tools catch packages after a CVE or advisory is published. forge-jsx had neither. SOVA is built to catch this class of threat on day zero:
- Every published package is enriched within minutes — static analysis runs on the actual shipped code, not just the manifest.
- Capability-intent classification scores each file across crypto, network, shell, filesystem, env_read, install_hooks, and dynamic_code. Seven malicious buckets at once is a signature no real SDK produces.
- Install-hook policy blocks preinstall / install / postinstall scripts from low-trust maintainers at lockfile-resolution time — before npm install ever runs the hook.
- Maintainer trust scoring composes history, diversity, longevity, and geography signals. A 13-day-old account with twelve rapid versions is gated automatically.
- Typosquat and brand-jacking detection tracks retired brands and canonical namespaces — a forge-jsx with no @autodesk scope gets flagged on name alone.
- CBOM-continuous monitoring re-scores every dependency on every new version. Capability drift between 1.0.10 and 1.0.11 would have alerted before the update reached your build.
Any one of those layers would have stopped forge-jsx before it touched a SOVA-protected lockfile.
About the Author
Yash Kumar is a Lead in Research & Innovation, focused on exploring emerging technologies and turning ideas into practical solutions. He works on driving experimentation, strategic insights, and new initiatives that help organizations stay ahead of industry trends.
