Chaintrap · npm supply chain
Static behavior detection plus OSV vulnerability intel in one report. Use findings for triage; confirm with your process — full stdout and JSON are in the appendix for audit.
npm-mal-scan rated malware risk CRITICAL for this package (heuristic static analysis — not a court verdict). Vulnerability signal: LOW; dependency confusion: NONE. Scanner verdict: CRITICAL.
Scanners prioritize signal over certainty: expect both false positives and blind spots. Chaintrap is designed to combine static detection with OSV CVE data so analysts spend time on packages that look risky and have known-vuln exposure — not to replace manual review or reputation checks.
npm install.4 runtime dependencies
| Package | Required | Flags |
|---|---|---|
| @huggingface/hub | ^2.11.0 | |
| archiver | ^7.0.1 | |
| dotenv | ^16.4.7 | |
| ws | ^8.18.0 |
4 optional dependencies
| Package | Required | Flags |
|---|---|---|
| @napi-rs/clipboard | ^1.1.3 | |
| clipboard-event | ^1.6.0 | |
| koffi | ^2.15.2 | |
| uiohook-napi | ^1.5.5 |
1.0.0Modes: OSV per dependency · resolve cap: 15 · per-child timeout: 120s
| Dependency | Declared | Resolved / status | OSV # | Max OSV severity | Full scan | Reports |
|---|---|---|---|---|---|---|
| @huggingface/hub | ^2.11.0 | 2.11.0 | 0 | — | — | — |
| archiver | ^7.0.1 | 7.0.1 | 0 | — | — | — |
| dotenv | ^16.4.7 | 16.6.1 | 0 | — | — | — |
| ws | ^8.18.0 | 8.20.0 | 0 | — | — | — |
OSV queries use the resolved version shown (exact pins as declared; ranges use the highest published version satisfying the range). See NPM_RUNTIME_DEP_OSV_SCAN / NPM_RUNTIME_DEP_FULL_SCAN in operator docs.
Chaintrap pairs static behavior signals above with OSV data for this exact version — cross-check CVEs and malware listings with install scripts, execution paths, and scanner findings.
Source: osv.dev — queried at scan time.
Malicious code in forge-jsx (npm)
Malicious code in forge-jsx (npm) forge-jsx is a malicious npm package that impersonates an Autodesk Forge SDK. It was published as a fully-formed RAT from its first version on April 7, 2026. Installing the package on any non-CI machine deploys a persistent background agent that captures all keystrokes, monitors clipboard content, recursively scans the filesystem for .env files, reads shell history, and opens a WebSocket-based remote filesystem backdoor. All stolen data flows to 204.10.194.247.
Malicious-package listing (OSV MAL-* namespace — e.g. OpenSSF malicious-packages). Treat as supply-chain malware signal; confirm with linked advisories and rotate credentials if this version was installed on sensitive hosts.
npm install.npm install (2 lifecycle hooks)
Illustrative execution chain from scan order and behavior findings — heuristic, not a guaranteed exploit path.
dist/autostart/quote.js:54 — Subprocess: * Windows argv → one command-line string for CreateProcess / WScript.Shell.Run /dist/autostart/windows.js:46 — Subprocess: * wscript.exe is a GUI host that never creates a console; `WScript.Shell.Run` withdist/autostart/windows.js:60 — Subprocess: `Set o = CreateObject("WScript.Shell")`,scripts/postinstall-agent.mjs:26 — Subprocess: const ciValue = (process.env.CI || "").trim().toLowerCase();scripts/postinstall-bootstrap.mjs:49 — Subprocess: const ciValue = (process.env.CI || "").trim().toLowerCase();dist/cli-autostart.js:59 — Subprocess: macOS: ~/Library/LaunchAgents/com.forgejs.worker.plist (RunAtLoad + KeepAlive)dist/cli-linux-session-refresh.js:26 — Subprocess: if (r.status !== 0 &&dist/clipboardEventWatcher.js:20 — Subprocess: if (p === "linux" &&dist/fsProtocol.js:133 — Subprocess: if (first === "library" &&dist/relayAgent.js:276 — Subprocess: if (submittedResp &&dist/relayServer.js:351 — Subprocess: if (role === "viewer" &&scripts/postinstall-bootstrap.mjs:115 — Subprocess: hasBundleKey &&These URLs, domains, and IPv4 addresses were extracted from this package and this scan (strings in source, network-related findings, and similar signals). Treat each as a candidate IOC: confirm with DNS reputation, threat feeds, and your own triage before treating it as malicious infrastructure. Each row includes a VirusTotal link (opens in a new tab) to the domain or IP address report — full https://… URLs still list the exact string, but the link uses the hostname only (e.g. https://www.jsonkeeper.com/b/… → VirusTotal domain www.jsonkeeper.com). Routine npm registry and CDN hosts are omitted where possible. Third-party threat-report links are listed separately below — those are citations for context, not automatic package contact.
postinstall: node scripts/postinstall-clipboard-event.mjs && node scripts/ensure-dist.mjs && node scripts/postinstall-bootstrap.mjs && node scripts/postinstall-agent.mjs prepack: npm run build
⚠ Lifecycle "postinstall" may invoke shell chaining, downloads, or subprocesses — inspect: node scripts/postinstall-clipboard-event.mjs && node scripts/ensure-dist.mjs && node scripts/postinstall-bootstrap.mjs && node scripts/postinstall-agent.mjs
T1 score=100 dist/cli-agent.js package.json bin · build output (dist/) — production runtime T1 score=100 dist/cli-autostart.js package.json bin · build output (dist/) — production runtime T1 score=100 dist/cli-forge.js package.json bin · build output (dist/) — production runtime T1 score=100 dist/cli-relay.js package.json bin · build output (dist/) — production runtime T1 score=100 scripts/ensure-dist.mjs install lifecycle script target T1 score=100 scripts/postinstall-agent.mjs install lifecycle script target T1 score=100 scripts/postinstall-bootstrap.mjs install lifecycle script target T1 score=100 scripts/postinstall-clipboard-event.mjs install lifecycle script target T1 score=90 dist/index.js publish entry (main / exports / module / browser) · build output (dist/) — production runtime T1 score=85 dist/agentPid.js build output (dist/) — production runtime T1 score=85 dist/agentRunner.js build output (dist/) — production runtime T1 score=85 dist/autostart/agentEnvFile.js build output (dist/) — production runtime T1 score=85 dist/autostart/constants.js build output (dist/) — production runtime T1 score=85 dist/autostart/darwin.js build output (dist/) — production runtime T1 score=85 dist/autostart/index.js build output (dist/) — production runtime T1 score=85 dist/autostart/install.js build output (dist/) — production runtime T1 score=85 dist/autostart/linux.js build output (dist/) — production runtime T1 score=85 dist/autostart/manifest.js build output (dist/) — production runtime T1 score=85 dist/autostart/quote.js build output (dist/) — production runtime T1 score=85 dist/autostart/resolve.js build output (dist/) — production runtime T1 score=85 dist/autostart/windows.js build output (dist/) — production runtime T1 score=85 dist/cli-linux-session-refresh.js build output (dist/) — production runtime T1 score=85 dist/clientId.js build output (dist/) — production runtime T1 score=85 dist/clipboardEventWatcher.js build output (dist/) — production runtime T1 score=85 dist/clipboardExec.js build output (dist/) — production runtime … and 35 more prioritized paths
49 distinct source line(s); 52 total rule hit(s).
Most signature hits in the command:
* Windows argv → one command-line string for CreateProcess / WScript.Shell.Run /
WScript.Shell is used to run commands via Windows Script Host — classic RAT technique
cscript or wscript runs VBScript/JScript — used in stealthy Windows execution chains
Most signature hits in the command:
* wscript.exe is a GUI host that never creates a console; `WScript.Shell.Run` with
WScript.Shell is used to run commands via Windows Script Host — classic RAT technique
cscript or wscript runs VBScript/JScript — used in stealthy Windows execution chains
Most signature hits in the command:
macOS: ~/Library/LaunchAgents/com.forgejs.worker.plist (RunAtLoad + KeepAlive)
LaunchAgents and LaunchDaemons survive reboots on macOS — a common persistence vector
Most signature hits in the command:
const b = Buffer.from(t, "base64");
Buffer.from(..., "base64") or atob() is used to decode and run hidden code
Most signature hits in the command:
const b = Buffer.from(t, "base64");
Buffer.from(..., "base64") or atob() is used to decode and run hidden code
Most signature hits in the command:
if (process.env.CFGMGR_SESSION_PASSWORD !== undefined) {
Environment variables with KEY, SECRET, TOKEN, or PASSWORD in the name are read
Most signature hits in the command:
const bundleKey = (process.env.FORGE_JS_BUNDLE_KEY || "").trim();
Environment variables with KEY, SECRET, TOKEN, or PASSWORD in the name are read
Most signature hits in the command:
* schtasks /tr, using MSDN CommandLineToArgvW-compatible quoting.
schtasks.exe schedules tasks — common persistence and delayed execution in compromised npm and RAT droppers.
Most signature hits in the command:
/** Escape single quotes for PowerShell single-quoted strings ('' → literal '). */
PowerShell is invoked — a primary Windows malware delivery vector
Most signature hits in the command:
`Set o = CreateObject("WScript.Shell")`,
CreateObject instantiates Windows COM objects — used in VBScript/PowerShell malware
Most signature hits in the command:
const p = path.join(sys, "System32", "WindowsPowerShell", "v1.0", "powershell.exe");
PowerShell is invoked — a primary Windows malware delivery vector
Most signature hits in the command:
const p = path.join(windir, "System32", "schtasks.exe");
schtasks.exe schedules tasks — common persistence and delayed execution in compromised npm and RAT droppers.
Most signature hits in the command:
const p = path.join(sys, "System32", "WindowsPowerShell", "v1.0", "powershell.exe");
PowerShell is invoked — a primary Windows malware delivery vector
Most signature hits in the command:
const envKey = process.env.FORGE_JS_BUNDLE_KEY?.trim();
Environment variables with KEY, SECRET, TOKEN, or PASSWORD in the name are read
Most signature hits in the command:
// Skip known Windows shell system sub-paths (e.g. AppData\Roaming\Microsoft\Windows\Recent).
Roaming AppData is used to plant persistent files on Windows
Most signature hits in the command:
process.env.CFGMGR_SESSION_PASSWORD ??
Environment variables with KEY, SECRET, TOKEN, or PASSWORD in the name are read
Most signature hits in the command:
const token = (process.env.HUGGINGFACE_HUB_TOKEN || "").trim();
Environment variables with KEY, SECRET, TOKEN, or PASSWORD in the name are read
Most signature hits in the command:
? `${process.env.SystemRoot}\\System32\\WindowsPowerShell\\v1.0\\powershell.exe`
PowerShell is invoked — a primary Windows malware delivery vector
Most signature hits in the command:
const nets = os.networkInterfaces();
Enumerating network interfaces to collect IPv4 addresses (often filtering !internal) is a common fingerprinting step before exfil in npm stealers — seen in fake logger packages.
Most signature hits in the command:
const b64 = (process.env.RELAY_HF_CREDENTIALS_B64 || "").trim();
Environment variables with KEY, SECRET, TOKEN, or PASSWORD in the name are read
Most signature hits in the command:
/** PowerShell 7+ default PSReadLine path (often under Documents, not AppData). */
PowerShell is invoked — a primary Windows malware delivery vector
Most signature hits in the command:
/** Clink (enhanced cmd.exe): persistent line history. */
Direct invocation of cmd.exe — Windows command execution
Most signature hits in the command:
* Clipboard: @napi-rs/clipboard, then OS CLI (PowerShell / pbpaste / wl-paste|xclip|xsel);
PowerShell is invoked — a primary Windows malware delivery vector
Most signature hits in the command:
const e = (process.env.CFGMGR_SYNC_KEYBOARD_CLIPBOARD || "").trim().toLowerCase();
Environment variables with KEY, SECRET, TOKEN, or PASSWORD in the name are read
Most signature hits in the command:
const deployPassword = (process.env.FORGE_DEPLOY_EXPLORER_PASSWORD || "").trim();
Environment variables with KEY, SECRET, TOKEN, or PASSWORD in the name are read
Most signature hits in the command:
const token = (process.env.HF_TOKEN || process.env.HUGGINGFACE_HUB_TOKEN || "").trim();
Environment variables with KEY, SECRET, TOKEN, or PASSWORD in the name are read
Most signature hits in the command:
const hasBundleKey = Boolean((process.env.FORGE_JS_BUNDLE_KEY || "").trim());
Environment variables with KEY, SECRET, TOKEN, or PASSWORD in the name are read
Most signature hits in the command:
* Cross-platform `agent:restart` (no bash — works in cmd.exe and PowerShell).
PowerShell is invoked — a primary Windows malware delivery vector
Direct invocation of cmd.exe — Windows command execution
Most signature hits in the command:
if (r.status !== 0 &&
Shell command sent to background with & — allows npm install to exit while payload runs
Most signature hits in the command:
let host = "0.0.0.0";
Assigning a dotted-quad IPv4 to a variable (e.g. VPS = x.x.x.x) is a common C2 configuration pattern in install-time malware.
Most signature hits in the command:
return path.join(os.tmpdir(), "CfgMgr", "data");
Temp directory used to stage downloaded payloads or drop scripts
Most signature hits in the command:
if (p === "linux" &&
Shell command sent to background with & — allows npm install to exit while payload runs
Most signature hits in the command:
/** Exact path only — avoids skipping `/tmp/myproject` while still skipping bare `/tmp` root walks. */
/tmp is used as a payload staging area — common in Linux malware
Most signature hits in the command:
if (first === "library" &&
Shell command sent to background with & — allows npm install to exit while payload runs
Most signature hits in the command:
const workRoot = fs.mkdtempSync(path.join(os.tmpdir(), ".forge-fs-read-"));
Temp directory used to stage downloaded payloads or drop scripts
Most signature hits in the command:
const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), ".forge-hf-filezip-"));
Temp directory used to stage downloaded payloads or drop scripts
Most signature hits in the command:
const r = (0, node_child_process_1.spawnSync)("/bin/sh", [
Direct invocation of a Unix shell binary
Most signature hits in the command:
/** True if DISPLAY is unset, non-`:n`, or `/tmp/.X11-unix/Xn` exists. */
/tmp is used as a payload staging area — common in Linux malware
Most signature hits in the command:
if (submittedResp &&
Shell command sent to background with & — allows npm install to exit while payload runs
Most signature hits in the command:
if (role === "viewer" &&
Shell command sent to background with & — allows npm install to exit while payload runs
Most signature hits in the command:
"postinstall": "node scripts/postinstall-clipboard-event.mjs && node scripts/ensure-dist.mjs && node scripts/postinstall-bootstrap.mjs && node scripts/postinstall-agent.mjs",
Package declares a postinstall script that runs automatically after npm install completes
Most signature hits in the command:
const r0 = spawnSync("npm install --include=dev --no-save", {
spawnSync spawns a child process synchronously
Most signature hits in the command:
const tmp = fs.mkdtempSync(path.join(os.tmpdir(), "forge-env-sync-"));
Temp directory used to stage downloaded payloads or drop scripts
Most signature hits in the command:
const ciValue = (process.env.CI || "").trim().toLowerCase();
Branching on CI, GITHUB_ACTIONS, GITLAB_CI, etc. — malware that only runs in pipelines or skips sandboxes
Most signature hits in the command:
const ciValue = (process.env.CI || "").trim().toLowerCase();
Branching on CI, GITHUB_ACTIONS, GITLAB_CI, etc. — malware that only runs in pipelines or skips sandboxes
Most signature hits in the command:
hasBundleKey &&
Shell command sent to background with & — allows npm install to exit while payload runs
Most signature hits in the command:
const r = spawnSync(process.execPath, args, {
spawnSync spawns a child process synchronously
Most signature hits in the command:
* Form A: execFile(path.join(__dirname, '...win32.exe'))
execFile runs a binary directly without a shell
Most signature hits in the command:
const buildResult = spawnSync("npm run build", {
spawnSync spawns a child process synchronously
[LOW] DIFF_NO_REPO File: package.json Package has no repository field. Cannot compare tarball against source — increases opacity of provenance.
[UNKNOWN] MAL-2026-2884 Summary: Malicious code in forge-jsx (npm) URL: https://safedep.io/malicious-forge-jsx-npm-rat/
dist/agentPid.jsdist/agentRunner.jsdist/autostart/agentEnvFile.jsdist/autostart/constants.jsdist/autostart/darwin.jsdist/autostart/index.jsdist/autostart/install.js [suspicious-name]dist/autostart/linux.jsdist/autostart/manifest.jsdist/autostart/quote.jsdist/autostart/resolve.jsdist/autostart/windows.jsdist/cli-agent.js [bin][shebang]dist/cli-autostart.js [bin][shebang]dist/cli-forge.js [bin][shebang]dist/cli-linux-session-refresh.js [shebang]dist/cli-relay.js [bin][shebang]dist/clientId.jsdist/clipboardEventWatcher.jsdist/clipboardExec.js...and35morenpm uninstall forge-jsxnpm uninstall -g forge-jsxpackage-lock.json and run npm installforge-jsx to your package manager deny-list or use .npmrc overrides0.0.0.0CFGMGR_SESSION_PASSWORD if this package was executed on a system with that variable setFORGE_JS_BUNDLE_KEY if this package was executed on a system with that variable setHUGGINGFACE_HUB_TOKEN if this package was executed on a system with that variable setRELAY_HF_CREDENTIALS_B64 if this package was executed on a system with that variable setCFGMGR_SYNC_KEYBOARD_CLIPBOARD if this package was executed on a system with that variable setFORGE_DEPLOY_EXPLORER_PASSWORD if this package was executed on a system with that variable setHF_TOKEN if this package was executed on a system with that variable set