Yaamlabs

Supply chain

@subql/common 5.8.3: a credential stealer on npm

A malicious @subql/common release ran on install and on import, stealing CI and cloud secrets. Who pulled it, how to check, and what to rotate.

On October 5, 2026 at 11:56 UTC a new version of @subql/common, 5.8.3, appeared on npm carrying a hidden credential stealer. @subql/common is a shared library in SubQuery, an open source framework for indexing blockchain data, so it sits underneath the SubQuery command line tool, the query service and the chain-specific indexer nodes. StepSecurity and Amazon Inspector both flagged it as malware the same day, and GMO Flatt Security published its own analysis of the payload the next morning.

The bad version was live for roughly 50 minutes before it was removed from the registry around 12:46 UTC, and latest now points back at 5.8.2. That window covers any continuous integration (CI) pipeline or developer laptop that resolved SubQuery dependencies fresh during it. The payload runs on install and again whenever the package is imported, so a pipeline that installs with --ignore-scripts but then builds or tests the project still ran it.

How the release was poisoned

The SubQuery repository publishes to npm from GitHub Actions using trusted publishing, where npm accepts a short-lived OpenID Connect (OIDC) identity token from the workflow instead of a stored publish token. That removes the long-lived token an attacker would normally steal, but it also means anyone who can change the publish workflow can publish.

According to StepSecurity and Flatt Security, someone with push access created a temporary branch, first ran a harmless "red team canary" build that went out as 5.8.3-onf-rt1 under a redteam tag at 11:24 UTC, then pushed a second commit (506863d) to the same branch. That commit edited the publish workflow so that, just before npm publish, it deleted packages/common and replaced it with files downloaded from ci-artifacts.dev. Neither commit was merged into main, yet the package came out of SubQuery's own pipeline with valid provenance. How the attacker got push access has not been published, and SubQuery has not yet responded publicly in the GitHub issue StepSecurity opened.

How the payload runs

The tarball adds one file, dist/project/readers/manifest-cache.js, and two hooks. The first is a postinstall script in package.json: node ./dist/project/readers/manifest-cache.js. The second is an export added to dist/project/readers/index.js, so a plain require('@subql/common') loads the file too. When it is loaded as a library rather than run directly, it spawns itself as a detached child process, so the build finishes normally while the worker keeps going.

The file holds about 60 KB of base64 chunks (Flatt counts a 548-line loader). The loader XORs them with a rolling key starting at 0x5a, gunzips the result and runs it with new Function(). Inside are further layers: strings encrypted with PBKDF2-derived keys and smaller scripts sealed with AES-256-GCM. Amazon Inspector also noticed that the source map shipped with the package was trimmed to show only a harmless ManifestCacheReader class, so anyone reading the mapped source would not see the loader.

Both StepSecurity and Flatt describe the same evasion: the worker exits if the system locale is Russian, disables TLS certificate checks for its own connections, and writes a lock file at /tmp/tmp.ts018051808.lock so only one copy runs.

What it takes

Flatt and StepSecurity list what the decoded stealer collects:

  • Local files: .npmrc, .env and its variants, ~/.aws/credentials and ~/.aws/config, SSH keys under ~/.ssh/, ~/.kube/config, ~/.docker/config.json, gcloud and Azure CLI caches, crypto wallets and AI tool configs such as ~/.claude.json.
  • The whole process environment, plus the output of gh auth token.
  • On Linux GitHub Actions runners, it uses sudo python3 to find the Runner.Worker process and read its memory for values marked as secrets. Masked secrets that a job never passes to the install step can still leak this way.
  • With any AWS credentials it finds, including the instance metadata service and Kubernetes IAM Roles for Service Accounts (IRSA) tokens, it lists and decrypts AWS Systems Manager (SSM) Parameter Store values and Secrets Manager secrets across 17 regions. It also reads Kubernetes secrets from inside a cluster and HashiCorp Vault KV mounts.
  • With a stolen GitHub token, it pushes a workflow disguised as CodeQL analysis into repositories the token can reach. That workflow writes ${{ toJSON(secrets) }} to format-results.txt and uploads it as a build artifact for the attacker to download, which turns one leaked token into every Actions secret in those repositories.

Everything is encrypted and sent to ci-artifacts.dev over HTTPS. The same server hands out commands: the worker polls it for instructions and can run single shell commands or open an interactive shell, so an infected runner or laptop is also a foothold.

Who is exposed

You only got 5.8.3 if something resolved it between 11:56 and about 12:46 UTC on October 5, or later from a cache or mirror that kept the tarball. The risk is wider than direct users of @subql/common, because current SubQuery releases allow the patch upgrade. We checked the registry: @subql/cli 6.6.3 and @subql/node-core 19.3.1 depend on ~5.8.2, @subql/query 2.25.0 on ~5.8.1, and @subql/node-ethereum 6.5.0 and @subql/common-ethereum 4.10.1 on ^5.8.2. Every one of those ranges matches 5.8.3. A project installing any of them without a lockfile, or a npx @subql/cli run in that window, would have pulled it. A committed lockfile that pinned 5.8.2 and an install with npm ci would not.

What to do

  1. Find it. Search lockfiles and build logs for the version: grep -rn "@subql/common" package-lock.json yarn.lock pnpm-lock.yaml | grep 5.8.3. Check CI logs from October 5 for installs of any @subql/ package, and search local npm caches and internal registry mirrors (Artifactory, Nexus, Verdaccio) for the 5.8.3 tarball. Its SHA-256 is 031267ee37c5a84c25cb0542cbfeb49f30d5604305b0bdccdeafbedcbbe6849b, and the loader file's is f0c8b0cde86b98a2869a22fd43ffcf61f1dcca252729e2be291e590e3dc5f49a. Purge it from any mirror.
  2. Pin @subql/common to 5.8.2 with an override (overrides in npm, resolutions in Yarn, pnpm.overrides in pnpm) and commit the regenerated lockfile.
  3. Block ci-artifacts.dev at DNS and the egress proxy, then search DNS and proxy logs for any past lookup of it. A hit tells you which host ran the payload.
  4. On any host that ran it, look for a running worker (ps aux | grep manifest-cache.js) and the lock file /tmp/tmp.ts018051808.lock. A missing lock file does not prove the worker is gone. Treat the machine as compromised: isolate it, keep evidence, and rebuild it. A GitHub-hosted runner is discarded after the job, but anything it could reach is not.
  5. Rotate from a clean machine: npm tokens, GitHub personal access tokens and OAuth tokens, every Actions secret in repositories that machine's tokens could reach, AWS access keys and any SSM or Secrets Manager values in that account, Kubernetes service account tokens, Vault tokens and the secrets they could read, SSH keys, Docker registry credentials and anything in the .env files.
  6. Hunt in GitHub. Look in the organization audit log and in each repository for branches and workflow files you did not create, especially a CodeQL-looking workflow added on October 5 or later, workflow runs that produced an artifact called format-results.txt, and runs or branches that were deleted shortly after creation.

The lesson for release pipelines

Trusted publishing and provenance told everyone that 5.8.3 came from SubQuery's repository, and it did. They cannot tell you the workflow was the one maintainers reviewed. Protect the publish workflow itself: require review for changes under .github/workflows/, restrict which branches can run the publish job through a protected GitHub environment, and make npm's trusted publisher configuration name that environment. On the consuming side, a release-age delay such as pnpm's minimumReleaseAge would have kept a 50-minute-old version out of most builds.

Our red team and code review work covers CI workflows and release paths like this one, and our cloud and Kubernetes security team can scope what a leaked runner credential reaches in AWS or a cluster. Open the chat and Yaali, our AI agent, will pass your question to an engineer.


Sources: StepSecurity, GitHub issue subquery/subql #3047, GMO Flatt Security, OSV MAL-2026-17571 (Amazon Inspector), GitHub advisory GHSA-9333-3c4x-x3h5, npm registry metadata for @subql/common.

Back to the blog, or read this post on the full site.