top of page

A Poisoned SubQuery Package Was Off npm in 50 Minutes. Our Install Guardrail Was Still Saying 'Allow' Ten Hours Later.

Writer: Patrick Duggan
Patrick Duggan
45 minutes ago
5 min read

At 11:56 UTC this morning someone published a poisoned version of @subql/common, a building block of the SubQuery web3 data-indexing framework, to npm. It steals credentials and opens a remote shell on any developer laptop or build server that installs it. The security community moved fast. A researcher filed a public warning 18 minutes later. npm pulled the version at 50 minutes. The open-source malware database OSV listed it at 50 minutes.


Our part was slower, and we want to show you exactly how much slower. Our feed had the attacker's domain and file hashes 9 hours and 27 minutes after the publish. Our check-package tool, the guardrail AI coding agents call before running npm install, was still answering "allow" for this exact version at 22:27 UTC, 10 hours and 31 minutes after the publish. It will keep saying that until our next scheduled pull from OSV, tomorrow at 08:55 UTC.



What happened


The malicious release was @subql/common version 5.8.3. Version 5.8.2, from November 2025, is clean. The package had about 16,000 downloads in the last month, and the security firm StepSecurity counted 19 other SubQuery packages, including @subql/cli and @subql/node, that had a dependency path to it.


The trick was in the build, not the source code. A change to SubQuery's release workflow added one step just before publishing: download a ready-made package file from ci-artifacts.dev and publish that instead. No checksum, no signature. The code on GitHub stayed clean. The package on npm did not. The change sat on a temporary branch that was created and deleted twice and never merged.


Two details show planning. The domain ci-artifacts.dev was registered on October 4 at 16:09 UTC through Njalla, a privacy-focused registrar, about 20 hours before the attack. And 32 minutes before the poisoned release, a prerelease called 5.8.3-onf-rt1 went up under the npm tag "redteam". It contained no payload. It looks like a test run to confirm the publish path worked, dressed up with a label that would make anyone glancing at it shrug. We are inferring that. Nobody has said so.



What the payload does


According to StepSecurity's analysis, its GitHub issue and the OSV entry, MAL-2026-17571, installing 5.8.3 runs a hidden script on install. It unpacks a compressed, obscured second stage and runs it. That stage collects .npmrc and .env files, AWS, Google Cloud and Azure credentials, SSH keys, Kubernetes and Vault secrets, crypto wallets and AI-agent configuration files. On GitHub Actions runners it reads the runner's memory for secrets, and it can write new workflow files to repositories the stolen token can reach. Data goes to ci-artifacts.dev/router, and an implant beacons to the same host for remote commands.





The clocks, side by side


October 4, 16:09 UTC: ci-artifacts.dev registered.


October 5, 11:24 UTC: payload-free prerelease 5.8.3-onf-rt1 published under the "redteam" tag.


11:56 UTC: malicious 5.8.3 published.


12:14 UTC: StepSecurity opens issue 3047 on SubQuery's GitHub: "Malicious release @subql/[email protected] (postinstall credential stealer)."


12:46 UTC: npm's registry record shows 5.8.3 gone, and the latest tag points back to 5.8.2. OSV publishes MAL-2026-17571 thirty seconds later.


21:23 UTC: we ingest ci-artifacts.dev and three SHA-256 hashes into our feed.


22:27 UTC: our check-package tool, asked about @subql/common 5.8.3, answers verdict allow.


October 6, 08:55 UTC: our next scheduled pull of OSV's malicious-package list.



Why our guardrail missed it


Our malicious-package deny-list is built from OSV's curated advisories, more than 215,000 named npm and PyPI packages. We pull it once a day. When we built that sync, the typical malicious package stayed on the registry for days, and a daily pull was plenty. That assumption is out of date. In this case the registry removed the package before our pull would have mattered. The danger window was 50 minutes, and a daily pull cannot cover a 50-minute window.


So who is still exposed? Anyone whose build pulled 5.8.3 in those 50 minutes. Anyone behind a private npm mirror or cache that kept a copy after npm removed it. And anyone whose agent asks us, today, whether the version is safe. Our tool says allow because it has not heard otherwise, and it warns that absence is not proof of safety. A warning nobody acts on is not a control.


The fix is to stop pulling a whole snapshot once a day and start picking up OSV's changes as they happen, on the order of hourly. OSV publishes a list of recently changed entries for exactly this. That change has not shipped. When it does, we will post the date and measure this same clock again on the next incident.



What we put in the feed


Four indicators, source manual-batch-subql-npm-compromise-2026-10, all credited to StepSecurity's research. They are not our discovery. The domain ci-artifacts.dev at confidence 85, and three SHA-256 hashes at 85: the poisoned package file, the loader script manifest-cache.js, and the decoded second stage. All four were read back from our index by ID, and we confirmed the domain is in our live domains.csv and the hashes in hashes.csv.


What we left out, on purpose. ci-artifacts.dev currently resolves to 1.1.1.1, which is Cloudflare's public DNS resolver. Blocking that address would break DNS for anyone who uses it, so it is not in the feed. We did not feed the GitHub account that pushed the workflow change. It belongs to someone on the project, and until anyone shows otherwise, its owner is a victim whose access was used, not the attacker. And we did not hand-write a package entry for 5.8.3 into our deny-list ahead of the OSV sync. A hand entry that matched every version of a nearly six-year-old legitimate package would block thousands of clean installs. Tomorrow's sync will add it correctly, scoped to the one bad version.



What to do today


Step one: search your lockfiles for @subql/common at version 5.8.3. That means package-lock.json, yarn.lock and pnpm-lock.yaml in every repository, and in any private registry or cache you run. If it is not there, you were not hit by this one.


Step two: if it is there, treat the machine as compromised. Rotate every credential the build or laptop could see: npm tokens, GitHub tokens, cloud keys, SSH keys, Kubernetes and Vault secrets, and wallet keys. Rotate first, investigate second.


Step three: check your repositories for workflow files you did not write. StepSecurity reported the payload writing a workflow named codeql_analysis.yml and using a branch named dependabot/github_actions/format/setup-formatter. Both names are chosen to look routine.


Step four: block ci-artifacts.dev at your DNS or proxy, and look for any past connection to it.


Step five, for every project: make CI publish only what CI built. A release step that downloads a finished package from anywhere, without checking a hash, hands your package to whoever controls that download. Pin your GitHub Actions to commit hashes, use npm's trusted publishing so tokens are short-lived, and alert on any new workflow file.



The opinion


The defenders won this one on speed. A researcher, a registry and a public database all reacted inside an hour, which is how it should work and still rarely does. Our piece of that chain was the slowest link today, and we would rather tell you than let a green "allow" sit in an agent's install step looking like an answer. A supply-chain guardrail lives or dies on freshness. We hold that with about 95 percent confidence; the rest is that the hourly pull will expose a different bottleneck we have not measured yet.


Was this useful? Rate this post. The widget is at the bottom of the page, and we read every response.




Every indicator in this post is in the feed. Free.

1.58M+ IOCs, STIX 2.1 / TAXII, 88% novel vs ThreatFox, exploited-CVE leads ahead of CISA. No credit card — a free API key in 30 seconds, and you can audit every claim above against the live endpoints.



Was this useful? Thirty seconds, no cookies, no tracking, no third parties, your address hashed and never stored. If the box below does not load, the same question lives at https://analytics.dugganusa.com/nps.html?post=a-poisoned-subquery-package-was-off-npm-in-50-minutes-our-install-guardrail-was-still-saying-allow



Comments

Rated 0 out of 5 stars.
No ratings yet

Add a rating
bottom of page