Executive overview
Yesterday, Ossprey's threat-hunting system detected a pair of malicious packages(aurelia-type@0.5.1 ,selparsecss-selector@2.1.0 ) and that warranted further investigation. The package masked itself as a plugin for the widely used tailwindCSS, and provided real functionality, along side a suspicious block of code, hidden in one of the files, although not directly executed by either package.
Today, another update was published to the parent package, aurelia-type@0.5.2 which is a live, real supply chain attack. We detected this within around a minute of this being published, and quickly picked this up as it had gained our interest from the day before. Both versions of the package(s), used the same techniques to actually gain a reverse shell connection to the target's machine.
Possibly the most interesting part of this attack, is that the final payload is hidden behind a hash, built from the host's username and machine name. This means the actual payload is targeting a single machine, therefore it is either an incredibly targeted attack, or a guard added to prevent security companies inspecting the final payload, until the attacker removes it.
Technical Breakdown
Stage 0: Executing arbirtary code
Both versions of the package are doing the same basic thing, in different ways. The initial version we discovered yesterday, used a modified clone dependency selparsecss-selector to contain the payload, this is referenced in package.json of aurelia-type in version 0.5.1 and below. It contains a fake webpack.min.js which does not seem to be invoked by either package, however it is unmistakably a malicious loader (more on this in stage 2!). The aurelia-type package itself contained code that registered .jsc files with the runtime, and the downstream dependency then through a series of obfuscations, decodes, and smoke and mirrors, resolves to a file selectors/fragment.jsc meant to execute the malicious webpack bundle. More on this can be found in yesterday's blog
Today's version, 0.5.2, uses a more direct approach. Using String.fromCharCode across a series of number arrays, it constructs the following javascript to be executed at runtime (this happens when the plugin is registered)
Both payloads differ slightly, but are functionally the same, and lead us on to the next stage of the attack.
Stage 1: The first stage loader
Both versions of the package end up in this state, either via running the webpack.min.js or from downloading the config.js , so let's look at exactly what this script is doing.
Both scripts are heavily obfuscated, but once de-obfuscated a clearer picture emerges. 0.5.2 's script has some basic anti detection practice, it avoids executing on linux, docker, if it can detect it's running in Github CI/CD, or if a javascript debugger is attached. This is fairly basic anti-detection designed to avoid automated detection, and stay hidden. This seems to have had some success, as a lot of security venders still consider this package as safe, but once it's in front of a human, de-obfuscated, it becomes clear this is yet another malware dropper.
The main contents of the file are pretty straightforward once we discount the anti-detection. It loads one of two files from IPFS (interplanetary filesystem, distributed filestore on the blockchain), which we've seen deliver payloads recently in the aysncapi attack. This stage checks the user, to see where to persist to (admin goes system wide, otherwise local only). And creates a daemon that auto starts the payload on both windows and macOS.
Simplified the script looks something like this
Stage 2: Persistence achieved
The final stage has been downloaded, and is set to auto start, and persist restarts. This final stage is heavily encrypted, with several layers of encryption. The payload is an array of base64 strings, which is XOR-er with a a SHA-256 of COMPUTERNAME|USERNAME|"windows_system32&update" , even if we were able to get past this, and break this encryption, the resulting value is still encrypted with AES-256. Finally by that stage we'd still need to know that the contents need decompressed with GZIP. That's a lot of layers of encryption to try to obfuscate the actual payload.
Thankfully we were able to detect a weakness in how the values were encrypted, and get the SHA-256 key. Once we had this, the AES-256 key, was in the decoded output. After a few iterations we were finally able to get the GZIP compressed output, which finally gives us the payload.
This is a fairly straight forward reverse shell. It reaches out to the IP address above, on port 7331, it executes the bytes it receives, and gives back the response (This is what the IEX $dataline does). So after all these stages, there's finally a full reverse shell into the target computer.
What Should You Do
Remove
aurelia-typeandselparsecss-selectorfrom all projects and delete them fromnode_modules; searchpackage-lock.jsonandyarn.lockfor both package names including transitive entries.On macOS, inspect
/Library/LaunchDaemons/and~/Library/LaunchAgents/for any plist file containingupdatesystemand remove it, then runlaunchctl unloadon the plist path before deleting.On Windows, check
C:\Windows\System32\forupdatesystem32.ps1andupdatesystem32.vbs; review PowerShell execution logs and the Windows event log forInvoke-Expressionactivity.Audit
.bashrc,.zshrc,/etc/profile, and any other shell init files on affected developer machines for appended commands re-executing unknown scripts.Rotate all credentials accessible from affected developer machines, including cloud provider keys, npm tokens, SSH keys, and browser-stored credentials.
Block outbound connections to
ipfs.ioin developer environments, or add the two specific IPFS CIDs (QmVAin...andQmbZ3b...) to DNS/proxy blocklists.Audit npm dependency trees for any other packages authored by
pyramid.info@gmail.comor the npm account that published these packages.
Indicators of Compromise
Network
https://ipfs[.]io/ipfs/QmVAinwjG1oN8WittDB6hYqgo5mMYbpfLskq4LdT75Dzynhttps://ipfs[.]io/ipfs/QmbZ3biucSF61WnW3NdsXyak6Kk455dDGxLEGvinUycBLBhttps://ipfs[.]io/ipfs/QmbK1kopMyo6jFrc9c22YZKo2C8KLkAy67ySk4tELNzefwhttps://ipfs[.]io/ipfs/QmWZYPguySJgFYiRHEqJQeyQ3jvL2ujarwHrf68x7F1ZfR4b5ad122[.]jsdelivrnetnpm[.]pages[.]devremote-beige-sparrow[.]myfilebase[.]com(IPFS pinning gateway)185[.]139[.]214[.]52:7331
Filesystem
/tmp/updatesystem.sh/usr/local/bin/updatesystem.sh~/bin/updatesystem.sh/Library/LaunchDaemons/com.apple.updatesystem.plist~/Library/LaunchAgents/com.apple.updatesystem.plistC:\Windows\System32\updatesystem32.ps1C:\Windows\System32\updatesystem32.vbsAudit shell profiles
.bashrc .zshrc etc
Affected Versions
aurelia-typeALLselparsecss-selectorALL
MITRE ATT&CK
ID | Technique | Why it applies |
|---|---|---|
| Supply Chain Compromise: Compromise Software Supply Chain | Both packages were published to the npm registry as impersonators of popular CSS libraries, weaponizing developer trust in the ecosystem to deliver malware on install. |
| Obfuscated Files or Information |
|
| Command and Scripting Interpreter: PowerShell |
|
| Command and Scripting Interpreter: Visual Basic |
|
| Command and Scripting Interpreter: Unix Shell | The macOS dropper writes and executes |
| Create or Modify System Process: Launch Daemon | Root-level persistence is achieved by writing |
| Create or Modify System Process: Launch Agent | User-level persistence falls back to |
| Event Triggered Execution: Unix Shell Configuration Modification |
|
A Note from Ossprey
What's interesting about this attack is that it will only work if the machine name, and username result in the correct SHA-256 to decode the payload. This would imply that either the attack is testing this as a method (using their own machine details to obfuscate the final payload until they publish a new version). Or more likely this is targeting a single user, on a single host, this would mean the victim has already been decided, and the attacker has some degree of information on them.
The move from version 0.5.1 , where the payload is delivered via a malicious dependency, to 0.5.2 , where it is delivered via a downloaded JS file, does mean that it becomes slightly more trivial for the attacker to rotate the IPFS final payload, so both options remain a possibility.
Recently there has been conversation around minimum install age; however in this case the downstream dependency selparsecss-selector has been online for over 6 days. Hundreds of small malicious packages like this continue to live on the registry as more and more supply chain attacks slip through the cracks.
Ossprey scores open source packages on what they actually do, so you can approve install scripts with evidence instead of guesswork, and catch the malicious behaviour that slips past signatures and allowlists. It sits next to your existing tools without slowing your developers down, and only surfaces what's worth your time.
You don't have to take any of this on faith. Ossprey is in open beta, so you can point it at your own dependencies and see what turns up today: start with the open beta, or book a demo.

Real World Attacks

PseudoCSS: Targeted dropper Campaign Impersonates Tailwind Typography and PostCSS Selector Parser

Andy Blair
20 Jul 2026
Real World Attacks

M-RED-TEAM: Five @asyncapi Packages Trojanized via GitHub Actions PAT Theft

Valentino Duval
14 Jul 2026
Real World Attacks

How Ossprey uncovered a 300+ package DPRK Contagious-Interview campaign

Valentino Duval
2 Jul 2026

