Safeguard
Supply Chain Security•

Poisoned bee-js 13.3.0 and Swarm Desktop 0.56.2: a hijacked release account, a wallet stealer, and npm loaders that need no install script

On 9 November 2026 a hijacked release identity published a wallet-stealing loader as @ethersphere/bee-js 13.3.0 and Swarm Desktop 0.56.2. Here is the minute-by-minute timeline, what the payload did, who is exposed, the indicators to hunt, and why the next wave of npm loaders will not need an install script at all.

Safeguard Security Team
36 min read

Shortly after midnight UTC on 9 November 2026, someone holding the Ethersphere project's release identity published two poisoned builds: @ethersphere/bee-js 13.3.0 on npm, the JavaScript client for the Swarm decentralised storage network, and Swarm Desktop 0.56.2, the app that runs a Swarm node, shipped through GitHub releases and its auto-updater. Both carried a loader that fetched attacker code, which stole the node's wallet keystore and configuration and installed a backdoor: a Go remote shell on Linux and macOS, a hidden ScreenConnect client on Windows.

The npm release was live for about 75 minutes. The desktop release was public for about eight and a half hours. If you did not install either version in those windows, this release did not reach you. If you did, the guidance from the GitHub Advisory Database entry GHSA-hh7j-5m3j-23pf is blunt: "Any computer that has this package installed or running should be considered fully compromised."

This post covers what is confirmed and what is unknown, then widens the lens. The bee-js loader used an npm postinstall hook, the route npm 12 now switches off by default. PolinRider, a campaign reported the same week, ships npm loaders with no install hook at all. That pattern defeats most of the defences teams added over the last year, and it is the part worth planning around.

What happened, in one minute

  • What was poisoned: @ethersphere/bee-js 13.3.0 on npm and Swarm Desktop 0.56.2 on GitHub. The clean versions are bee-js 13.2.0 and Swarm Desktop 0.56.3.
  • How: a malicious commit and release were made under bee-worker, the project's release automation account, and the project's own publish workflow built and shipped them. Researchers report that a long-time maintainer account was also used. How either credential was stolen is not known.
  • What it did: stole the Swarm node wallet keystore and configuration, then installed persistent remote access on Linux, macOS and Windows.
  • Who is exposed: anyone who installed bee-js 13.3.0 with install scripts enabled between 00:09:50 and 01:24:58 UTC on 9 November, directly or through a dependent package whose version range allowed it, and anyone who ran Swarm Desktop 0.56.2.
  • What to do: confirm whether either version reached a machine, treat any machine that ran one as compromised, move node funds to a new key, rotate credentials, and block the versions in your caches and mirrors.

Timeline

All times are UTC. Each line names where it comes from. Where sources disagree, the table says so.

Date and time (UTC)EventSource
14 Mar 2026The Ethereum contract later used to store the backdoor's hub address is deployedSafeDep
6 Nov 2026The domain frameworkmetrics[.]xyz is registeredStepSecurity, OX Security
6 Nov 2026Workflows meant to harvest CI credentials are pushed to Ethersphere repositories, including swarm-desktop and swarm-cli. Researchers say these attempts failedSocket, SafeDep
8 Nov, 23:30The operator sets the current hub address in the contractSafeDep
8 Nov, 23:59:54Commit de2f74e ("chore(upcoming): release 13.3.0") is authored by bee-worker. It is unsigned and sits on no branchGitHub commit API, bee-js issue 1295
9 Nov, 00:06:01A GitHub release for v13.3.0 is published, which triggers the npm publish workflowSocket, StepSecurity
9 Nov, 00:09:50@ethersphere/bee-js 13.3.0 appears on npm under the latest tagnpm registry metadata, Socket
9 Nov, 00:36:31Commit 2de7270 ("chore(master): release 0.56.2") is authored by bee-worker in swarm-desktop. The release is described as an "emergency patch release"GitHub commit API, swarm-desktop issue 581
9 Nov, 00:41 to 00:44Swarm Desktop 0.56.2 build, signing, notarisation and upload complete. Windows x64 and Apple Silicon installs become eligible for the update through the auto-updaterSocket
9 Nov, 00:53:54First public report filed in the bee-js repositorybee-js issue 1294
9 Nov, 01:24:58npm removes 13.3.0, and latest returns to 13.2.0npm registry metadata, Socket
9 Nov, 07:00:25An issue titled "Swarm Desktop 0.56.2 is compromised" is opened in swarm-desktopswarm-desktop issue 581
9 Nov, 09:04The v0.56.2 tag is deleted. The release is no longer public by about 09:14Socket
9 NovGitHub publishes GHSA-hh7j-5m3j-23pf, "Malware in @ethersphere/bee-js", affecting version 13.3.0, CWE-506GitHub Advisory Database
9 Nov, 12:52:07Swarm Desktop 0.56.3 is published as a security release that replaces 0.56.2swarm-desktop v0.56.3 release
9 Nov, 13:52bee-js puts its npm publish environment behind DevOps approval, v* tags onlybee-js pull request 1296

One discrepancy is worth flagging. OX Security's write-up lists 0.56.1 as the fixed Swarm Desktop version. That was the last clean release before the attack. The project then shipped 0.56.3, which The Hacker News describes as the same code as 0.56.1, signed with a newly issued Developer ID certificate. Use 0.56.3, because the auto-updater moves 0.56.2 installs to it.

How the project's own release pipeline was turned against it

Nothing in the public record says npm was breached or that an npm token was stolen. The poisoned packages came out of Ethersphere's normal release path, which is why they looked normal.

According to Socket's analysis, two GitHub accounts were in the attacker's hands. One was a long-time maintainer with force-push rights to an unprotected master branch. The other was bee-worker, the release automation identity that normally creates release commits, tags and releases. Socket writes that "the origin of both compromises predates November 6 and remains unexplained", and that the 6 November credential-harvesting attempt did not supply them. The 9 November compromise "used access already held".

The sequence then went like this:

  1. A commit under bee-worker renamed the prepare script in package.json, which runs only when developers install the repository itself, to postinstall, which runs on every consumer install. It added the scripts folder to the published files and rewrote the version generator script inside it into a loader. The real 13.3.0 release pull request was still open, according to the issue that reported it.
  2. A GitHub release for v13.3.0 was created from that commit.
  3. The publish workflow, which listens for release events, built the package and pushed it to npm.

Socket notes that the workflow accepted release events without checking that the commit was on a protected branch, and that the publish environment had no required reviewers or tag policy. SafeDep adds that the workflow ran no tests on this path, because tests only ran on branch pushes.

The result had valid npm provenance. Socket verified the attestation offline and found it correctly named the workflow, the malicious commit and the release trigger. The legitimate 13.2.0 attestation had the same structure. As Socket puts it, provenance "proves where a package was built, not that the source was reviewed." npm's own documentation says the same thing in different words: provenance "does not guarantee the package has no malicious code."

About 30 minutes later the same identity produced Swarm Desktop 0.56.2, with the loader in the app's main process file (src/index.ts). The 0.56.3 release notes state it directly: "Each time 0.56.2 started, it fetched code from api.frameworkmetrics.xyz and ran it." The build was signed and notarised as usual. SafeDep: "Apple notarization, npm provenance and a valid ScreenConnect signature do not make a release safe."

What the payload does

This section stays at a conceptual level. It describes behaviour so defenders know what to look for. It does not reproduce code.

Stage one, the loader. In bee-js 13.3.0, the loader ran from the postinstall hook. StepSecurity notes that importing or using the library was not required, so installing was enough. The loader requested JavaScript from the attacker's domain and ran it with the installing user's permissions. It ignored errors and timeouts, so a clean-looking install log proves nothing. The request carried a gate header (X-Ethersphere: 1) and the operating system name. Socket reports that requests without the header got a harmless decoy, which is why some researchers saw empty or 404 responses after about 00:31 UTC. In Swarm Desktop 0.56.2, the same kind of loader sat in the app's startup code and ran at every launch.

Stage two, wallet theft. The fetched code read the Swarm node's configuration file and its encrypted wallet keystore, config.yaml and swarm.key, and posted them to a /v1/swarm/report endpoint. SafeDep reports that the Swarm Desktop configuration stores the keystore password in plain text. If that is true for your install, the attacker holds everything needed to open the node's private key and move its funds and stake.

Stage three, persistence and remote access. The server then delivered a platform-specific backdoor.

  • On Linux and macOS, a Go remote shell that OX Security names "evjawbreaker". It installs under ~/.solidity/ and persists as a systemd user unit or a LaunchAgent named to look like Solidity tooling. Socket reports that it exits if it detects a debugger or a virtual machine.
  • On Windows, the payload attempts privilege elevation, adds a Microsoft Defender exclusion and silently installs a legitimate ScreenConnect client that runs as SYSTEM.

Why an Ethereum contract. The Linux and macOS backdoor does not hard-code where to connect. It reads its hub address from an Ethereum smart contract. The operator can move the hub at any time by writing a new value to the contract, with no new domain and no new build. Blocking today's hub IP does not stop the backdoor from learning tomorrow's. This "dead drop" technique also appears in the PolinRider loaders described below. That is a shared technique, not evidence of a shared operator.

Attribution is a reported claim. Socket links the infrastructure to a campaign called SleepyDuck or EtherDuck, based on another firm's analysis, and The Hacker News reports that this firm ties the activity "with high confidence" to the group behind earlier malicious Solidity extensions on Open VSX.

Who is exposed

Direct installs of bee-js

You are exposed if a machine resolved @ethersphere/bee-js 13.3.0 between 00:09:50 and 01:24:58 UTC on 9 November and ran its install scripts. That covers npm install or npm update in a project with a caret range, and CI jobs building without a lockfile.

The install-script condition matters. Socket reports that only installs with install scripts enabled ran the loader, and lists npm 12, pnpm 10 and later, Yarn 4.14.0 and later, Bun and Deno as skipping dependency scripts by default. The npm part is confirmed by GitHub's npm v12 changelog, which says npm install "will no longer execute preinstall, install, or postinstall scripts from dependencies" unless the project allows them. npm 12.0.0 reached the registry on 8 July 2026. Plenty of CI images and laptops still run npm 10 or 11, though, and on those versions dependency scripts run by default.

Transitive installs

Several published packages declare bee-js with a version range that allowed 13.3.0. A fresh resolution of any of these during the window could pull the bad version in, even if you never typed "bee-js".

Dependent packageVersions named by researchersRange for bee-jsSource
@ethersphere/swarm-cli3.6.0, 3.7.0^13.2.0 in 3.7.0npm registry, StepSecurity
@ethersphere/bee-dashboard0.37.0, 0.37.1 (older 0.33.0 to 0.36.2 indirectly)^13.0.0 in 0.37.1npm registry, Socket
@snaha/swarm-id0.5.0 to 0.8.0Allows 13.3.0Socket, OX Security
@solarpunkltd/swarm-id0.10.5Allows 13.3.0OX Security
@solarpunkltd/file-manager-lib1.1.0, 1.1.1Allows 13.3.0Socket, OX Security
@upcoming/bee-dashboard (through file-manager-lib)0.33.3Allows 13.3.0OX Security
bee-wasm-js0.1.0, 0.1.1Allows 13.3.0Socket, OX Security

None of these dependents was itself poisoned. The range made them a path, and real exposure depends on the version your install actually resolved. A committed lockfile pointing at 13.2.0 protected npm ci runs. A lockfile regenerated during the window did not.

Global installs and npx

This is the path most teams forget. npm install -g @ethersphere/swarm-cli and npx runs do not read your project lockfile. Anyone who installed or ran the Swarm CLI globally during those 75 minutes should check which bee-js version landed in the global tree.

Swarm Desktop users

Swarm Desktop 0.56.2 was public from about 00:36 to 09:14 UTC. Socket reports that the updater offered it to Windows x64 and Apple Silicon Macs within minutes. Intel Macs were never offered it, and Linux installs do not auto-update, so Linux users were exposed only through a manual download. The desktop loader ran at every app start, install scripts or not. Socket suggests checking bee-desktop.log for 0.56.2.

What the download numbers say, and do not say

OX Security puts bee-js and its affected dependents at over 16,800 monthly downloads combined. That is the reach of the package family, not the number of poisoned installs. The Hacker News reports Socket's estimate that 13.3.0 itself was downloaded about 10 times, scanners and mirrors included, and that Swarm Desktop 0.56.2 had 65 downloads by 08:27 UTC. No count of infected machines and no theft of funds had been reported at the time of writing. Small numbers still matter to the people hit: a node operator who auto-updated overnight lost their key, and the backdoor stays until someone removes it.

Technical details and indicators

Every value below was read in the source named next to it. Domains and IPs are defanged. SafeDep publishes a longer CSV with 65 entries.

Packages, commits and builds

ItemValueSource
Malicious npm package@ethersphere/bee-js@13.3.0GHSA-hh7j-5m3j-23pf
Clean npm version13.2.0 (current latest)npm registry metadata
Malicious desktop releaseSwarm Desktop v0.56.2swarm-desktop issue 581
Clean desktop releaseSwarm Desktop v0.56.3v0.56.3 release
bee-js malicious commitde2f74e784b2eafa0d66fbb6d88b42ea11ee34cfGitHub
swarm-desktop malicious commit2de727046706062d38edfbbf8340f32d1eb2bdfbGitHub
Publishing identitybee-worker (GitHub user ID 70210089)GitHub commit API, SafeDep
GitHub release ID438613465 (v13.3.0)SafeDep
Actions run that published 13.3.042538346313SafeDep, bee-js issue 1295
Loader filescripts/generate-version.cjs (bee-js), src/index.ts (swarm-desktop)GitHub commits above

Network indicators

IndicatorRoleSource
frameworkmetrics[.]xyz, api[.]frameworkmetrics[.]xyzLoader delivery and wallet exfiltration. Paths seen: /v1/analytics, /v1/electron, /v1/assets/, /v1/swarm/reportOX Security, StepSecurity
X-Ethersphere: 1 request header, User-Agent linux, darwin or win32Loader request signatureStepSecurity, OX Security
193[.]57[.]9[.]155:4912Current hub for the Go backdoorOX Security, Socket
185[.]232[.]84[.]169:8041ScreenConnect relayOX Security, Socket
0xf8a900Db50b3331be6B768bA460BB59f3E40C344Ethereum contract holding the hub addressOX Security, SafeDep
0x6a82595EEea0f4FDFB3aE13A2687BcbBE626e94BSecond contract listed by SocketSocket
0xFd3fc58bcbd8ccc77b6000201438eDfc636E7cA7, 0xA7B15A2B5F6992DC3BabB946375d6895fF1171BFOperator wallets listed by SocketSocket

Host indicators

PlatformIndicatorSource
Linuxsystemd user unit solidity-langsupport-runner.service, files under ~/.solidity/SafeDep, StepSecurity
macOSLaunchAgent com.solidity.langsupport.runner, files under ~/.solidity/SafeDep, Socket
WindowsService ScreenConnect Client (5cbe79c9c25524df), a new Microsoft Defender exclusion, .stuffed- files in %TEMP%SafeDep, StepSecurity, Socket
AllSwarm wallet files config.yaml and swarm.key read and sent outStepSecurity

File hashes (SHA-256)

The bee-js loader script, reported by SafeDep:

ee2f762e9ed3fd89f106e0b80c2e923bf1aed9a280277d3d998a5902c4d9ec0f

The Go backdoor for Linux, reported by StepSecurity and also present in OX Security's list:

linux_amd64  9b73e7cd4e1425e770392549d8df46c706139ef476f4f7b9ac405165dc8d9696
linux_arm64  b601776817363b96295119f7221338a122d5247a35a77a64b04f286e1bbcc565

The full payload hash list published by OX Security:

735aba95e24f515a5cea4efea309f245952917d372b37b5fbea305986dbcd568
1f3a7afd2e43215f09f2a81063dc2796af556bae74aaf492fba2d66e5d65b14c
2496d74e6a913eba39c8537be31baeff9cf6ccdd8ee84a69475302815cfd4105
705b4aa2bc6fa35f8e6d448f3c6e193fac6027b9b5239e212e09732cc9fc0dba
37d14bff30762ca701e9c1f8bc6c57c065bb20ae3a2208cb7411a6530610f08e
7527e86d70e7c9493db4356f1c3775380431b310000f2602c1e993cf435d01da
9b73e7cd4e1425e770392549d8df46c706139ef476f4f7b9ac405165dc8d9696
b601776817363b96295119f7221338a122d5247a35a77a64b04f286e1bbcc565
80d2672e2599732d3c0ae2a4cd0d1e3fe4d555a60273ce33feb99db3f34d250f
fae61f31f00988fdc5cc9e7272b51e08af2e245d81003237875415930fd27358

StepSecurity also lists the SHA-1 of the malicious npm tarball as a205c8a5fed81e9a787645b2801f00be3db3348d. SafeDep lists a tarball SHA-256 of d01fdf241aa9e0a1ba6ebc19f8f667af1bf652745021c5050126cb7036f7ce58 and marks it unverified.

The wider pattern: attackers take identities the registry already trusts

The bee-js release fits a pattern that has been building for more than a year. The attacker takes over an identity that already has publish rights, a maintainer account, a bot or a CI token, and ships through the normal channel. The registry sees a known publisher, provenance is valid, and users see a routine version bump. Recent cases:

  • September 2025, Shai-Hulud. GitHub described it as "a self-replicating worm" spreading through compromised maintainer accounts. GitHub removed more than 500 packages and announced trusted publishing, shorter token lifetimes and stronger 2FA.
  • July 2026, npm 12. Dependency install scripts went off by default, per the GitHub changelog.
  • October 2026, GhostAction returns. Hijacked accounts pushed fake "security audit" workflows to steal CI secrets, as we covered in GhostAction returns.
  • 10 to 12 November 2026, the wfx wave. The Hacker News reports that stolen credentials for 4,837 GitHub accounts planted a CI secret stealer, 771 repositories receiving it as an npm postinstall hook. The common thread is identity. The answer is fewer standing credentials, approval gates on release environments, and consumers who do not take a new version the minute it appears.

PolinRider: loaders that never touch an install script

Everything in this section is reported by third-party researchers. We found no maintainer statements and have not analysed the packages ourselves. Read each line as "reported by".

On 4 November 2026, Sonatype's research team reported 12 malicious releases across 10 hijacked npm packages, tracked as sonatype-2026-010515. OX Security reported the same campaign on the same day and counted 17 packages, adding a set of Tailwind form packages to the list. GBHackers summarised the reporting on 9 November. Neither primary report explains how the packages were hijacked.

The releases named by Sonatype:

PackageAffected releasesConcealment reported
@adminide-stack/client-redux0.0.16-18Base64
@adminide-stack/health-api-server0.0.18-alpha.465Base64
@adminide-stack/user-activity-browser1.1.3-alpha.114Base64
@adminide-stack/utils0.0.16-129, 0.0.16-130Base64 (129), character permutation (130)
server-hemera-mongo0.0.15, 0.0.16Base64
cdeops13.5.31Character permutation
@adminide-stack/forms-core5.1.9-alpha.3Character permutation
@adminide-stack/yantra-help-browser12.0.194Character permutation
@cdmbase/wiki-browser12.0.18-alpha.53Character permutation
@pubngo-stack/help-browser12.0.23-alpha.3Character permutation

OX adds seven Tailwind form packages, among them tailwind-form-core and tailwind-forms-kit, reports all 17 as taken down, and puts combined weekly downloads at about 1,400, a figure that "reflects package download activity, not confirmed infections".

How the reported loader works

Sonatype's key sentence: "The manifests show no consumer install or postinstall hooks." The loader sits inside ordinary module code. As Sonatype puts it, "The code can run when an affected entrypoint is loaded." For server-hemera-mongo, it runs when the bundled application starts.

Once loaded, it reportedly does this:

  1. Query public Ethereum RPC endpoints, falling back to a Blockscout API, for the latest transaction from the wallet 0x1eef0849eda15d87f64e51c73c8442cf3c8bcef3.
  2. Read bytes of the transaction's recipient address as a primary and a secondary IPv4 address. Sonatype: "The Ethereum wallet acts as a dead-drop resolver."
  3. Request two stages, /0x/cls and /0x/ls, "over plain HTTP on TCP port 443", and XOR-decode them.
  4. Run /0x/cls both inside the application process and in a detached node -i process fed through standard input. Run /0x/ls only in a detached child. The children are unreferenced, so they keep running after the application exits.

According to GBHackers, a third research group reported that later stages install a Node remote-access trojan and persist through npm's own lib/cli.js and the startup files of desktop applications, including VS Code, Cursor, Discord and GitHub Desktop. The article's warning is the one to remember: that makes "dependency removal alone insufficient after execution."

Sonatype links the wallet and paths to PolinRider loaders another firm documented in compromised GitHub repositories, and says this "does not definitively establish" a shared operator with GhostAction. Some coverage calls PolinRider North Korean. Sonatype makes no such attribution, so we do not either. bee-js and PolinRider share a technique, Ethereum as a dead drop. Nothing published links their operators, and the overlap in time is only that.

Why install-script-free loaders defeat common defences

Most of the npm hardening teams did after Shai-Hulud assumed that malware runs at install time. PolinRider's reported design sidesteps that assumption. Here is how each common defence fares.

1. --ignore-scripts and npm 12's script default. These stop install hooks, so they would have stopped the bee-js 13.3.0 loader. They do nothing against a loader that runs when your code calls require() or import. As GBHackers puts it: "Disabling npm installation scripts therefore does not neutralize this threat."

2. Install-time sandboxes. A loader that does nothing at install time passes straight through, then runs later in your test runner, dev server or production process, none of which are sandboxed the same way.

3. Manifest diffing. "Did the new version add an install script?" catches bee-js 13.3.0, because the postinstall rename is visible in package.json. A PolinRider-style release shows no manifest change. The loader is buried in obfuscated JavaScript inside a file the package already shipped.

4. Lockfiles. A lockfile protects you from a bad version published after you locked. If the hijacked release is what you locked, npm ci reinstalls it on every build.

5. IP and domain blocklists. The loader asks a public blockchain where to go next. Sonatype: "Blocking an individual server therefore does not disable the loader's ability to discover a replacement." The same is true of the bee-js backdoor, which reads its hub from a contract.

6. Version-based indicators alone. Sonatype notes the operator can change the served JavaScript without a new npm release. The version tells you the door was open. Only host telemetry tells you what came through.

7. Provenance alone. bee-js 13.3.0 had valid provenance because the attacker used the real build path.

8. Laptop-only thinking. Import-time loaders run wherever your application runs, including production servers holding production credentials.

What still works does not depend on catching the install: a release-age cooldown, a proxy that decides whether a package arrives, egress control on build and application hosts, and process monitoring. The behaviour also gives defenders an opening. A detached node -i child fed by standard input is unusual in production, plain HTTP on port 443 is a mismatch egress proxies can flag, and a public Ethereum RPC call from a service unrelated to web3 deserves a look.

What changed since the first reports

  • npm and GitHub. The registry no longer lists 13.3.0, and GHSA-hh7j-5m3j-23pf lists it as malware with no patched version, since the fix is not to use it.
  • Swarm Desktop. The 0.56.3 security release replaces 0.56.2 and warns: "Updating to 0.56.3 removes the bad app but does not undo anything that code already did. If you ran 0.56.2, treat that machine and your Swarm node keys as compromised."
  • Ethersphere's release pipeline. Pull request 1296, merged on 9 November, puts the publish environment behind DevOps approval and accepts only v* tags. Open as of 13 November: 1297 moves to npm staged publishing, where an owner approves each version with 2FA, 1298 refuses to publish commits not on a release branch, and 1300 locks the actions the workflows use.

That list doubles as a checklist for any maintainer team.

Open questions

  • Initial access. How the maintainer and bee-worker credentials were stolen is unknown. Socket says the compromise predates 6 November.
  • The desktop stage. A commenter on swarm-desktop issue 581 notes that what the /v1/electron endpoint served to 0.56.2 is not documented, so check those machines for every persistence indicator above.
  • Windows and macOS payloads. StepSecurity notes that the full capabilities of the unretrieved Windows and macOS payloads are not established.
  • PolinRider's access path. Neither primary PolinRider report says how the npm packages were hijacked.

What would have helped, and where Safeguard fits

Nothing in this post says Safeguard detected, found or reported the bee-js, Swarm Desktop or PolinRider releases. The researchers credited above did that. Here is how Safeguard's products fit the controls the incident calls for.

Package Firewall: decide whether a package arrives at all

--ignore-scripts decides whether a package's hooks run. A proxy decides whether the package reaches the machine at all, which still matters when the loader has no hook.

Package Firewall runs as a local install-time proxy for npm and pip on developer machines and CI runners, and may block malicious, typosquatted and dependency-confusion packages before they install. The documentation says it sees "every fetch, including transitive dependencies", and lists checks for typosquatting, dependency confusion, known-malicious signatures or an explicit blocklist, and static inspection of the archive without running code. Modes are allow, warn, block, audit and quarantine. Start in audit mode, then move to warn or block. On a CI runner:

safeguard pkgfw start --mode quarantine --port 8899
npm config set registry http://127.0.0.1:8899/npm/

You can also check a single package and version on demand, which returns JSON and exits non-zero on a block:

safeguard pkgfw check --ecosystem npm --name @ethersphere/bee-js --version 13.2.0

For a version published minutes ago by a trusted identity, pair the proxy with a minimum release age, covered below.

SCA and SBOM monitoring: answer "where do we have it?"

Safeguard's software composition analysis resolves the full dependency graph from manifests and lockfiles, including deep transitive dependencies. Each scan produces an SBOM you can search, so "which repositories and builds resolved @ethersphere/bee-js, at which version?" is a search, not a week of asking every team. The same goes for the PolinRider package names. Continuous scanning scans repositories on every push and re-evaluates stored SBOMs as advisory feeds change, so the build that regenerated its lockfile at 00:30 UTC on 9 November shows up too.

Policies and gates: make the rules stick in CI

Policies and gates evaluate SBOMs on conditions such as severity, EPSS, license, component age, maintenance and SLSA level, and enforce them in CI with block, warn or notify actions. Exceptions need a justification, an expiry date and an approver. safeguard ci scan --wait --fail-on-gate <gate> fails the job when the gate fails, which catches pipelines that skipped your pins and cooldowns.

Gold: start from components that are already fixed

Here the "fix" is a known-good version, bee-js 13.2.0 and Swarm Desktop 0.56.3. The section below covers how autonomous remediation, Gold Open Source and Gold Registry move every project to the safe version, followed by the vendor-neutral steps every team should take this week.

Fix it without waiting for upstream

Whatever the vulnerability, most of the risk sits in two gaps. The first is the time between a fix being published and that fix actually running in your production builds. The second, and worse, is the time before any fix exists at all, while a maintainer works out a patch and a release schedule. Waiting through both gaps is a habit, not a requirement. Safeguard gives teams three ways to close them.

1. Autonomous remediation: pull requests, not tickets

When a fixed version exists, Griffin, Safeguard's security model, opens a policy gated pull request that moves the affected dependency to the safe version, and your own CI runs your tests on it before anyone merges. You can run it on one repository or in bulk across every project that carries the vulnerable version. Remediation categories include dependencies, licenses, security posture and findings, and each one is set separately. Reachability analysis ranks the vulnerable code paths your application actually calls first, so the first pull requests you merge are the ones that matter most. Auto-merge is a separate switch and stays off by default, so your team decides what merges on its own and what still needs a human approval.

2. Gold Open Source: the safe version in seconds, free

gold.safeguard.sh is free and needs no login. Search any CVE or package to see affected and fixed versions, CVSS, EPSS, CISA KEV status and a recommended safe version, or use the "Am I affected?" checker. The same data is available through a free read API, README status badges and RSS threat feeds.

3. Gold Registry: start from components that are already fixed

Safeguard Gold rebuilds patched versions of open-source packages and images from source, signs each build, attaches an SBOM, SLSA provenance and VEX, and serves them from registry.safeguard.sh to your organization. Every Gold artifact is held continuously to zero exploitable critical or high CVEs and a completed license check, with signed build provenance (signed with Safeguard's key and verifiable with cosign, SLSA Build Level 3 as the design target) and a public evidence page you can hand to an auditor. An artifact loses its Gold badge if it regresses. Teams can point their own package directory at the registry so hardened equivalents resolve without changing how developers install anything.

When upstream has no fix yet, or will not ship one, Safeguard first works to get the fix accepted upstream. If the issue is severe or upstream will not move, Safeguard builds a fork, fixes the vulnerability there and publishes a hardened Gold build, so you are not stuck on a maintainer's release schedule. You can also request a hardened build of a specific package. Ask which packages it covers before you plan around it.

Your situationWhat to use
A fixed version existsAutonomous remediation opens the upgrade pull request; Gold Open Source confirms the safe version
You need to know today whether you are affectedGold Open Source lookup and "Am I affected?" checker, free and without login
You want vulnerable versions to stop shippingSafeguard policies and gates in CI
Upstream has no fix yet, or will not ship oneA custom Gold request; fork and patch
You want to start clean on new projectsPull hardened components and images from Gold Registry

One honest limit: when the vulnerable code lives in a vendor appliance, a hosted service or an operating system runtime you do not build yourself, the fix still has to come from that vendor or distribution. The steps above apply to the open source dependencies and container images you build and ship, which is where most teams carry the largest and least tracked share of their exposure.

The incident playbook: eight steps for bee-js, Swarm Desktop and install-script-free loaders

These steps apply whether or not you use Safeguard. Work through them in order.

1. Pin and lock versions

Find every place bee-js resolves, then pin the clean version.

# npm: show every path to bee-js and the resolved versions
npm ls @ethersphere/bee-js --all

# pnpm and Yarn equivalents
pnpm why @ethersphere/bee-js
yarn why @ethersphere/bee-js

# Search lockfiles across a checkout of many repositories
grep -rn --include=package-lock.json --include=pnpm-lock.yaml --include=yarn.lock "bee-js" .
grep -rn "bee-js-13.3.0.tgz\|bee-js@13.3.0" .

# Global installs do not use your lockfile
npm ls -g --all | grep bee-js

Commit your lockfile, build with npm ci (or pnpm install --frozen-lockfile) in CI, and never let CI regenerate a lockfile silently. For the dependent packages that use caret ranges, force the clean version with an override:

{
  "overrides": {
    "@ethersphere/bee-js": "13.2.0"
  }
}

pnpm uses pnpm.overrides and Yarn uses resolutions for the same purpose. For Swarm Desktop, confirm the app reports 0.56.3, and on Linux replace the package by hand, since Linux installs do not auto-update.

2. Block the bad versions everywhere they could hide

npm removed 13.3.0, but your infrastructure may still hold it. SafeDep advises: "purge bee-js 13.3.0 from your mirrors, proxies and developer caches". Sonatype says the same for PolinRider. Add @ethersphere/bee-js@13.3.0 and every PolinRider release above to your internal registry's deny rules, clear CI caches keyed on lockfiles that may include them, and remove the Swarm Desktop 0.56.2 installer from internal download shares. On developer machines, grep -rl "bee-js-13.3.0" ~/.npm/_cacache/index-v5 shows whether the tarball was ever fetched.

3. Add a minimum release age (cooldown)

bee-js 13.3.0 was live for 75 minutes. A machine that refused versions younger than a day would never have installed it. Current npm documents a min-release-age setting: "only versions that were available more than the given number of days ago will be installed", with min-release-age-exclude for packages you must take immediately. pnpm has minimumReleaseAge, measured in minutes and applied to transitive dependencies too. Its documentation lists a default of 1440 minutes, one day. Check what your installed version actually does.

# .npmrc: refuse versions published less than 3 days ago
min-release-age=3
# pnpm-workspace.yaml: refuse versions younger than 3 days
minimumReleaseAge: 4320

A cooldown only helps when someone spots the bad release inside the window. For bee-js that took under an hour, for quieter loaders it may take days. Use several days for production builds and treat every exclusion as a reviewed exception.

4. Check npm provenance, and know what it proves

Run npm audit signatures to confirm that installed packages carry valid registry signatures and, where published, provenance attestations. It catches tarballs that did not come from the registry or the claimed build. But bee-js 13.3.0 carried valid provenance, because the attacker used the real workflow. Treat provenance as an input, and pair it with a check that the commit sits on a protected branch, which Ethersphere's pull request 1298 adds on the publisher side.

5. Use --ignore-scripts, and know where it stops

On npm 10 or 11, run CI installs with npm ci --ignore-scripts, or move to npm 12, where dependency install scripts are off unless allowed in the allowScripts field of package.json. npm's allow-scripts-pin setting, on by default, pins approvals to an exact version, so a new release does not inherit them. Keep it on, and avoid dangerously-allow-all-scripts.

This would have stopped the bee-js 13.3.0 loader. It would not have stopped Swarm Desktop 0.56.2, which ran at app start, or, by Sonatype's account, PolinRider, which runs at import. Script blocking is one layer, not the plan.

6. Route installs through a private registry or proxy

Point every developer machine and CI runner at one controlled source instead of the public registry. A private registry or caching proxy gives you one place to deny a version and an audit log that answers "did anyone fetch 13.3.0?" from records rather than guesswork. Package Firewall can sit on the same path on developer machines and CI runners. Enforce the setting in CI images, not only in a README.

7. Rotate keys and move wallet funds

If any machine ran bee-js 13.3.0 with scripts on, ran Swarm Desktop 0.56.2, or loaded a PolinRider release:

  • From a different, clean machine, as the GitHub advisory says, rotate every secret reachable from the affected machine: npm and GitHub tokens, SSH keys, cloud credentials, CI secrets and OIDC-based cloud roles.
  • Swarm node operators: assume the node password and key were stolen. Create a new key on a clean machine and move funds and stake to it. OX Security extends this to any crypto wallets and credentials reachable from that environment.
  • Rebuild, do not clean. Isolate the host, preserve evidence, rebuild from a trusted image. StepSecurity: "Replacing the npm dependency does not undo code that has already run or remove an installed service or LaunchAgent."

8. Hunt on endpoints and in network logs

Run these on any machine that might have been exposed.

Linux:

systemctl --user status solidity-langsupport-runner.service
ls -la ~/.config/systemd/user/ | grep -i solidity
ls -la ~/.solidity/ 2>/dev/null

macOS:

launchctl list | grep -i com.solidity.langsupport.runner
ls -la ~/Library/LaunchAgents/ | grep -i solidity
ls -la ~/.solidity/ 2>/dev/null

Windows (PowerShell):

Get-Service | Where-Object { $_.Name -like 'ScreenConnect Client*' -or $_.DisplayName -like 'ScreenConnect Client*' }
Get-MpPreference | Select-Object -ExpandProperty ExclusionPath
Get-ChildItem $env:TEMP -Filter '.stuffed-*' -Force

For PolinRider-style loaders, look for detached Node processes started by another Node process with piped input:

ps -eo pid,ppid,lstart,args | grep -E "node -i" | grep -v grep

In DNS, proxy and firewall logs, search for frameworkmetrics[.]xyz, the hub and relay addresses, the PolinRider addresses reported by OX Security (103[.]179[.]143[.]219, 166[.]88[.]134[.]75, 23[.]27[.]13[.]72, 95[.]135[.]181[.]151), plain HTTP on TCP port 443, and Ethereum RPC calls from services with no reason to make them. Sonatype's caution applies to both incidents: "A lockfile entry does not prove the malicious code ran, and an import error does not prove it didn't."

FAQ

Am I affected if I use bee-js 13.2.0?

No. 13.2.0 is the clean version and the current latest on npm, and StepSecurity confirms it "does not contain this malicious installation hook." You were exposed only if 13.3.0 was resolved and its install script ran between 00:09:50 and 01:24:58 UTC on 9 November 2026.

I never installed bee-js directly. Could I still have it?

Yes, through a dependent package whose range allowed 13.3.0, such as @ethersphere/swarm-cli 3.7.0 (^13.2.0) or @ethersphere/bee-dashboard 0.37.1 (^13.0.0). Run npm ls @ethersphere/bee-js --all and search your lockfiles and global installs.

Which Swarm Desktop version is safe?

0.56.3, the security release of 9 November 2026. Some coverage named 0.56.1, the last clean release before the attack, but 0.56.3 is the current fixed build and the one the auto-updater installs.

Does upgrading to Swarm Desktop 0.56.3 clean my machine?

No. The project's own release notes say updating "does not undo anything that code already did." Remove the persistence listed above, move your node funds to a new key from a clean machine, and rotate any credentials the machine held.

Would --ignore-scripts or npm 12 have protected me?

From bee-js 13.3.0, yes, because its loader ran from a postinstall hook. From Swarm Desktop 0.56.2, no, because it ran at app start. From PolinRider releases, no, because researchers report those loaders run when the module is imported and have no install hook.

Did npm provenance fail?

No, it did what it promises. It accurately recorded that 13.3.0 was built by the project's own workflow from a specific commit. It does not judge whether that commit was reviewed or safe.

Is PolinRider the same attacker as the bee-js compromise?

Nothing published says so. Both use Ethereum to publish server addresses, a shared technique, not shared attribution.

Did Safeguard detect this attack?

No. The analysis credited in this post comes from the project's own repositories and independent research teams. This post describes how Safeguard's products fit the controls the incident calls for.

See how Safeguard would have helped

These demos show real Safeguard sessions on other examples. None of them shows this incident. Each paragraph explains how the same flow applies to bee-js and Swarm Desktop.

The CLI demo shows a lodash fix done from the terminal, asking before any command that changes something. For this incident you would ask it to add an override that pins bee-js 13.2.0 and rerun the tests.

Click through it at your own pace, or open it full screen.

The desktop app demo shows the same kind of upgrade, where you pick the folder and then Approve or Deny each command and file edit. For a monorepo with several Swarm packages, that is each lockfile change approved by you.

Click through it at your own pace, or open it full screen.

The Claude demo shows Safeguard connected to Claude. During an incident like this one, you can ask in the chat you already use which of your repositories and builds carry bee-js or any of the PolinRider package names, and get the answer from your own Safeguard data.

Click through it at your own pace, or open it full screen.

Quick check

For how long was @ethersphere/bee-js 13.3.0 available on npm?

About 75 minutes, from 00:09:50 to 01:24:58 UTC on 9 November 2026.

Why does --ignore-scripts not stop PolinRider-style loaders?

Researchers report that those packages have no install or postinstall hooks. The loader runs when an affected module is imported or a bundled service starts.

Sources

Related reading on Safeguard: minimum release age and cooldown policies, npm 12 disabled install scripts and attackers adapted, the Shai-Hulud npm supply chain attack explained and npm provenance statements in practice.

Last reviewed: 13 November 2026. This is a developing story, so check the linked advisories for the latest status.

Never miss an update

Weekly insights on software supply chain security, delivered to your inbox.

We email you a link to confirm, and only then add you to the list. Unsubscribe in one click. Privacy

Self-healing security runs on Safeguard.

Your first fix PR is minutes away.

No sales call required, even your agent can complete the purchase over MCP.