Launch Week Day 1: Announcing Security Design Review
CRITICAL npm Malware

Malicious code in class-synth (npm)

MAL-2026-5730

Published ยท Modified

Description


__

Source: amazon-inspector (1aa63407d7400b4819d0739dedad0a32d9ae29b18509693c2e8763cf30275271)

class-synth is advertised as a small class/style/date utility library, but its main entry (dist/index.js) contains a hidden top-level async IIFE (__init) that fires whenever the package is required or imported. The IIFE dynamically imports node:fs, node:path, node:child_process, node:crypto, and node:https using base64-encoded module names joined at runtime to evade string scanners, and acquires process indirectly via new Function('return typeof process!== "undefined"? process: null;'). It then recursively walks process.cwd() looking for any .css file containing an @sri-hash: marker, base64-decodes that marker, and AES-256-CBC-decrypts it with a hardcoded key (split across an array of hex chunks ['a7b80b01','7e76fb52','fa527621','f76027d2','19014dfc','a59b49ae','3db97ff3','ab4a72fa']) to recover an attacker-controlled URL. The decrypted URL is fetched over HTTPS and the response body is piped directly into child_process.spawn('node', ['-'], {windowsHide: true, stdio: ['pipe','ignore','ignore'], detached: true}), so attacker-supplied JavaScript executes in the developer/CI Node process with no on-disk artifact, suppressed stdio, and a detached/unref'd child. The bundle is padded with ~750 decoy near-duplicate exports (isWithinBoundary1..200, applyPreset1..150, createSequenceStep1..250, mapOperation1..250, checkConstraint1..250) to bury the dropper near the end of the file. The C2 URL is delivered out-of-band via a planted.css file, which defeats URL-based scanning of the package itself. The combination of base64-hidden Node built-ins, split/encrypted C2 location, indirect process access, detached stdin-piped code execution, and large-scale decoy padding leaves no plausible benign reading.

Ready to move

Start Securing

Free, no credit card | First findings in minutes