Poisoned Pipeline

Poisoned Pipeline / Incidents / AS301

AS301 · 2026-10

@subql/common 5.8.3 published after a CI artifact swap

npmGitHub ActionsGit / GitHubCI/CD SaaSDeveloper workstationLinux distro / packages

On 5 October 2026 StepSecurity flagged @subql/common@5.8.3, a shared
library in the SubQuery monorepo. At collection time npm's latest tag
pointed at 5.8.3, published 11:56 UTC. Version 5.8.2 (20 November 2025)
has no manifest-cache payload. A canary, 5.8.3-onf-rt1, published at
11:24 UTC, republished 5.8.2's application code under a red-team
description and does not carry the payload.

GitHub account ianhe8x created the short-lived branch chore/ci-audit-55
twice the same morning. Both commits are unsigned, name Ian He as
author and committer, and parent the same main commit. Main still
pointed at that parent when the evidence was collected, so this was
not a merge to main. StepSecurity does not establish whether the named
person controlled the account. The first commit replaced the publish
workflow with a workflow_dispatch canary that packed 5.8.2 and
republished it. That canary's provenance names Actions run 37302582874,
which was unavailable later. The second commit adds a step that
downloads an archive from ci-artifacts.dev, deletes packages/common,
and extracts the download with no hash check, immediately before the
existing publish step. 5.8.3 appeared on npm about two minutes after
that branch was deleted.

The published tarball adds a postinstall that runs
node ./dist/project/readers/manifest-cache.js, and the readers index
starts the same worker on import. Blocking install scripts is not
enough. The loader XOR-decodes and gunzips a seed into JavaScript and
runs it in a detached child. StepSecurity decoded that payload without
executing it. It reads credential files (.npmrc, .env, AWS credential
files, SSH keys, kubeconfig, wallet paths, and .claude.json), collects
the full process environment, and tries gh auth token. On a Linux
GitHub Actions runner it tries sudo python3 to read Runner.Worker
memory for values marked isSecret. It also contains collectors for
AWS Secrets Manager, in-cluster Kubernetes secrets, and Vault, and a
second workflow that would open a dependabot-looking branch and a
codeql_analysis.yml writing toJSON(secrets) into an artifact.
StepSecurity did not observe that second workflow on a victim
repository. Collection and a command channel use
https://ci-artifacts.dev/router. Nineteen other latest @subql package
versions could resolve 5.8.3 through an existing range — @subql/cli
6.6.3 depends on @subql/common ~5.8.2 — without those parents shipping
a new release. No confirmed downstream theft is in the report.
StepSecurity opened subquery/subql issue 3047 and called the
investigation ongoing. GHSA-9333-3c4x-x3h5 / MAL-2026-17571 cover
the published version.

Mapped techniques

supply-chain Publish workflow on a temporary branch

T0111 · Resource Development · Malicious code contribution to an open-source repository

Two unsigned commits on chore/ci-audit-55 changed
.github/workflows/publish.yml. They were not merged to main.
The account on the branch events is ianhe8x. Compromise of
that person is not established.

T0166 · Execution · Trigger pipeline execution

The release commit inserts an unverified curl of
ci-artifacts.dev into the publish job, then the existing
publish step runs. The canary workflow was workflow_dispatch.
StepSecurity does not have a still-available Actions log for
the final 5.8.3 publish.

T0109 · Resource Development · Publish malicious artifact

"@subql/common@5.8.3 was published at 11:56 UTC and was npm
latest at collection time. The canary 5.8.3-onf-rt1 is a
republish of 5.8.2 and is not this payload."

post-supply-chain Install and import of 5.8.3

T0151 · Execution · Installation scripts

package.json postinstall runs the manifest-cache reader.

T0159 · Execution · Malicious artifact execution

Importing the package also starts the detached worker, via
the readers index. Install-script blocking does not cover
that path. Parent packages that depend on ~5.8.2 can resolve
5.8.3 without a new release of their own.

T0140 · Credential Access · Harvest tokens from environment variables

The decoded payload collects the full process environment and
tries gh auth token. These are capabilities in the tarball,
not a named victim theft.

T0183 · Credential Access · Harvesting short-lived token

On Linux GitHub Actions runners the payload tries to read
Runner.Worker memory for values marked isSecret. It does not
contain a privilege-escalation exploit for that sudo python3
step.

T0184 · Credential Access · Harvesting sensitive information from files

File targets include .npmrc, .env, AWS credential files, SSH
keys, kubeconfig, wallet paths, and .claude.json. Windows
paths in the same list are not expanded and are not treated
as demonstrated access.

Framework gaps noted

Sources