main logo icon

Published on

August 8, 2026

|

10 min read

The Build System Is the Initial Access: Inside the Self-Propagating TanStack-to-Nx Compromise

A defender's reading of the May 2026 TanStack npm compromise (CVE-2026-45321) and the Nx Console compromise it caused (CVE-2026-48027): why lockfile pinning did not help, what the payloads took, how to detect it, and what to test now.

Arafat Afzalzada

Arafat Afzalzada

Founder

AdvisoriesWeb App Security

Summarize with AI

ChatGPTPerplexityGeminiGrokClaude

TL;DR

On 11 May 2026, between 19:20 and 19:26 UTC, an attacker published 84 malicious versions across 42 legitimate `@tanstack/*` npm packages using the project's own GitHub Actions OIDC trusted-publisher binding, and TanStack's advisory states that the publish workflow itself was not modified. Because these were the real packages under the real maintainer identity with valid provenance, pinning to an official package name gave no protection; the one control that would have worked, a minimum release age, was set in the repository where the install happened but silently ignored by a pinned package-manager version that did not implement the key. The payload self-propagated by enumerating the packages each victim maintained and republishing them with the same injection, which makes this worm behaviour rather than a one-shot compromise. Seven days later a credential stolen in that compromise was used to publish a weaponised Nx Console extension, live for roughly 18 minutes on the Visual Studio Marketplace and 36 minutes on Open VSX; the registries recorded 28 installs and 41 downloads while the vendor's own telemetry showed roughly 6,000 activations. Nrwl's postmortem states that between the credential theft on 11 May and the marketplace publish on 18 May the attacker was active in its GitHub repositories for seven days without detection, and that it did not detect the intrusion through any system it had built. Both records reached the CISA Known Exploited Vulnerabilities catalog on 27 May 2026 flagged for known ransomware campaign use, but only one is filed under the catalog's "Embedded Malicious Code" name, so an intake filter built on that string misses the other. The scope change that follows is not another application test: it is treating the release pipeline, the registry publish path and the developer endpoint as initial-access targets in their own right.

Supply chain guidance assumes the attacker tricks you into installing something you did not ask for. These two incidents broke that assumption: the packages were real, the publisher identity was real, and the provenance was valid. On 11 May 2026, 84 malicious versions of 42 genuine @tanstack/* packages went to npm inside a six-minute window. One developer who installed one was an Nx contributor, and the credential taken from that laptop published a malicious Nx Console extension seven days later. Both now sit in the CISA Known Exploited Vulnerabilities catalog, flagged for known ransomware campaign use.

Classification first, because "compromise" is doing too much work

Five categories separate the events reported under this kind of headline: A, a guardrail bypass where no system is touched; B, an attacker using a vendor's product against third parties; C, a product vulnerability disclosed with a CVE; D, a breach of the vendor's own systems or release channel by an outsider; and E, model-initiated action during evaluation, where the vendor's own model acted against a real third party in an authorised test with no attacker involved.

Nothing here is A, B or E: every action below was taken by a human attacker. Each incident is a D at the project and a C downstream, and a process that sees only the C patches a version number without asking what still holds stolen credentials.

Not a typosquat: why pinning to the official package name did not help

TanStack's advisory, published 11 May 2026 at 22:40 UTC, states that between approximately 19:20 and 19:26 UTC, 84 malicious versions across 42 @tanstack/* packages went to npm, authenticated via the legitimate GitHub Actions OIDC trusted-publisher binding for TanStack/router, and that "the publish workflow itself was not modified" (GHSA-g7cv-rxg3-hmpx).

Every consumer-side defence in common use then falls away: name and scope allowlisting were correct, lockfile pinning helps only if the lockfile already pointed at an older version, and provenance attestation helps not at all because the artefact went through the genuine binding. Nrwl's postmortem states the consequence: those versions were indistinguishable from legitimate releases, and "the only consumer-side defense is delay-based" (Nx postmortem).

A minimum release age means you install nothing published in the last N hours or days. It was set in the repository where the install happened, to seven days, and it did nothing: the pinned package-manager version predated support for the key, so the setting was silently ignored. Nrwl's verdict: "The malicious version was 77 minutes old at install time, which any non-zero release-age policy would have blocked." A configuration key is only a control if the runtime implements it.

Three weaknesses chained, and no workflow file was touched

The advisory names three known classes chained: a pull_request_target "Pwn Request" misconfiguration, GitHub Actions cache poisoning across the fork to base trust boundary, and runtime memory extraction of the OIDC token from the runner process. TanStack's postmortem adds that none alone is sufficient, and that each bridges the trust boundary the next assumed was intact (TanStack postmortem).

At the level a defender needs: pull_request_target gives a workflow the base repository's permissions and secrets rather than the fork's, unsafe once it executes pull request code. Caches are scoped per repository and shared across trigger types, so a low-trust job can write entries a production workflow later restores. And an OIDC request mints a publish-capable credential inside the runner process, reachable by any code executing there.

No workflow file in the release path was modified, so a diff review or protected-branch rule would have passed: the attacker changed what the release workflow found on disk, not what it did. The tradecraft was borrowed too: "The attacker did not invent novel tradecraft; they recombined published research."

Worm behaviour: how one compromise enumerated its way into the next project

The advisory also describes the payload enumerating the packages the victim maintains through the registry's maintainer search and republishing them with the same injection. That is worm behaviour: the blast radius of one install is every package that developer can publish, and every consumer of those. It makes "how many people installed the bad version" the wrong first question; the right one is "which of them can publish."

The Nx incident is that mechanism running through a human credential, and Nrwl states the link outright: "One of our developers was compromised by a recent supply-chain compromise on Tanstack, which leaked their GitHub credentials through the GitHub CLI (gh). This allowed the attacker to run workflows on our GitHub repository as a contributor" (GHSA-c9j4-9m59-847w).

Their timeline puts the install at 20:43:01 UTC on 11 May, the stolen token used against the GitHub API within 74 seconds, the first attacker commit at 20:43:51, bulk deletion of workflow runs within minutes, and the token reappearing on 15 May from different client tooling. Their summary: "the attacker was active in our GitHub repos for seven days without detection." That is a detection finding, not a supply chain finding.

Eighteen minutes, 28 installs, thousands of users: why counts misled everyone

Exposure and counts. The Nx advisory states 18.95.0 was published at 12:30 UTC and removed at 12:48, roughly 18 minutes on the Visual Studio Marketplace, and was on Open VSX from 12:33 to 13:09, roughly 36 minutes. The postmortem says around 11 minutes "until we patched it": both are the vendor, measuring public exposure and time from detection. Microsoft and Open VSX recorded 28 and 41, yet maintainers believe impact "two orders of magnitude higher, with thousands of affected users," and the postmortem reports roughly 6,000 activations. An install count is a reporting proxy, not an incident metric.

The date. Two primary records disagree: Nrwl's postmortem and GitHub's advisory metadata place the publish on 2026-05-18, while NVD's record for CVE-2026-48027 opens with "On 19 May 2026" (NVD). The vendor's advisory cannot predate the event it describes, so 18 May is better supported; triage should anchor on the version string anyway.

Record

Advisory secondary score

NVD primary score

KEV added

KEV due

Ransomware

CVE-2026-45321 (TanStack)

CVSS 3.1 9.6 Critical (GitHub)

None assigned

2026-05-27

2026-06-10

Known

CVE-2026-48027 (Nx Console)

CVSS 4.0 9.3 Critical (GitHub)

CVSS 3.1 9.8

2026-05-27

2026-06-10

Known

CVE-2026-33634 (Trivy)

CVSS 4.0 9.4 Critical (GitHub)

CVSS 3.1 8.8

2026-03-26

2026-04-09

Unknown

NVD carries no primary score for the TanStack record; the 9.6 is GitHub's secondary metric (NVD), so a threshold reading only NVD primary scores drops it silently. At catalogVersion 2026.08.07 KEV holds eight entries named "Embedded Malicious Code," five added since December 2025, including Nx Console and the earlier Trivy entry, another C record over a build-chain compromise (CISA KEV). TanStack is not among them: its entry is "TanStack Unspecified Vulnerability" with no CWE in KEV, and NVD carries CWE-506 only as a secondary entry from GitHub, so automation matching that name or CWE misses the entry that caused the other.

What the payloads went after

Both payloads targeted the credentials a build host or workstation accumulates.

Target class

TanStack advisory

Nx records

Cloud, cluster and secret managers

AWS instance metadata and Secrets Manager, GCP metadata, Kubernetes service-account tokens, Vault tokens

The same, plus Parameter Store, Web Identity tokens and any active 1Password CLI session

Registry and source control

npm tokens from npm configuration; GitHub tokens from environment variables, gh CLI configuration and git credentials

The same, plus OIDC token exchange and process memory

Keys and configuration

SSH private keys

The same, plus environment files, GCP default credentials, Docker config

Persistence

Not described

Artefacts on macOS, Linux and Windows, plus an attempted sudo change on Linux

The Nx payload established persistence, so removing the malicious version does not end it. Both went after the credential with the widest reach on a developer machine: Nrwl notes the gh CLI writes a long-lived token any process running as the user can read, and now bans direct use of it.

Exfiltration that does not look like exfiltration

TanStack states the harvested data left over the Session and Oxen messenger file-upload network, end-to-end encrypted with no attacker-controlled command and control, so "blocking by IP or domain is the only network mitigation." The Nx records add HTTPS, the GitHub API and DNS-based covert channels.

None of that trips a typical egress policy: a messenger network looks like consumer chat, GitHub API traffic from a build runner is what that host exists to generate, DNS is the least inspected channel, and the self-propagation used npm itself. Restrict runner egress to an allowlist, alert on volume and pattern rather than destination alone, log DNS from build networks, and treat a publish as egress.

Detection: publish events, credential origin, and the audit trail you probably do not keep

Neither project caught this with tooling it had built. TanStack heard from an external researcher 26 minutes after the first publish: "No internal alerting. We learned about the compromise from a third party." Nrwl's detection was a publisher-notification email reaching a maintainer not expecting a release: "We did not detect this through any system we built."

  • Publish and release events. Every publish to a public registry or marketplace should alert a human who reconciles it against a release decision.

  • Source control audit log. Nrwl names workflow-run deletions specifically, because the attacker swept them in bulk within minutes of taking the token. Those, workflows appearing outside the review path, and commits whose author does not match the actor are cheap to alert on.

  • Credential use from a new origin. The token reappearing on 15 May from different client tooling was the missed signal; a change of user agent, source network or geography separates days of dwell time from same-day containment.

  • Runner behaviour. Because no workflow file changed, the anomalies live at runtime: processes inspecting other processes, destinations outside the build allowlist, cache entries that do not match the dependency state, and publishes off the approved path.

  • Endpoint and package indicators. The Nx records publish an indicator set covering persistence artefacts on macOS, Linux and Windows, a daemonisation environment variable and unexpected privileged sudo entries; TanStack's malicious manifests carry an undeclared payload file.

Remediation and rotation order after a pipeline compromise

Rotation without sequencing hands a fresh credential back to a live implant.

  1. Stop execution first. Update and terminate components running the malicious version, which Nrwl stresses keep exfiltrating while alive.

  2. Remove persistence before rotating, unloading the macOS launch agent rather than only deleting the file; a credential rotated while an implant is live is one you just disclosed.

  3. Rotate by blast radius. Source control and registry publishing credentials first, because those turn your incident into your customers', then cloud credentials, cluster and secret-manager tokens, SSH keys and environment files, including anything those CLIs minted during the window.

  4. Rebuild dependency state, do not patch it. Remove installed dependencies and the lockfile, reinstall clean, and pin to versions published before 11 May 2026 19:00 UTC, TanStack's stated cut-off.

  5. Treat affected runners and endpoints as compromised hosts, rotate every secret they could reach, and consider rebuilding. Nrwl advises others in the same upstream chain to review their audit logs for the pattern.

Then fix the pipeline, reading the two change lists separately, because they are not the same list. Nrwl's postmortem records three: an approval now required to publish Nx Console, enforced through GitHub Actions environments with required reviewers where the reviewer cannot be whoever triggered the workflow; monitoring of its GitHub audit log for events such as workflow-run deletions; and Action SHAs pinned instead of floating refs. It notes those practices already existed on its main repositories and were simply not applied everywhere. TanStack's list is different: caches removed from the release pipeline and from Actions, every action in the organisation pinned to a commit SHA, non-SMS 2FA enforced across npm and GitHub, every use of pull_request_target removed, and every repository on pnpm 11 to inherit install-cooldown behaviour (TanStack hardening).

The overlap is narrow, and the gap is instructive: both pinned actions to commit SHAs and both now lean on a release-age delay, but neither control that would have shortened the Nx timeline is on TanStack's completed list. Its postmortem files both as open, stating the "OIDC trusted-publisher binding has no per-publish review" and that it needs "monitoring on our own publishes." Publish approval and publish alerting are the costly ones, and they remain outstanding at the project hit first.

What a red team should now cover

Two asset classes usually written out of scope were the initial access.

Asset

What to test for

Why scope misses it

Workflow definitions and actions

Trigger contexts exposing base repository permissions to fork code; floating action tags rather than immutable references

Reviewed as code quality, not attack surface

Build cache and artefact stores

Whether entries written by low-trust jobs can be restored by high-trust jobs

A performance feature, not a trust boundary

Release and publish paths

Whether one identity can publish without a second approval; whether publish tokens are reachable from job code

Owned by engineering, rarely scoped

Developer endpoints

Long-lived source control, cloud and registry credentials readable by any process running as the user

In the endpoint programme, not pentest scope

Detection coverage

Whether a publish, workflow-run deletion or token use from a new origin alerts anyone

Validating a detection is rarely commissioned

If you take one scoping decision from this article, make it the last row: neither project was watching the record it already had.

Stingrai has run offensive testing from Toronto and London since 2021, is a CREST-accredited penetration testing service provider at firm level with testers holding CREST CRT, and has 18 published CVEs. Pipeline and endpoint work of this kind is human-led; Snipe, our autonomous agent, is scoped to web application testing and priced on our pricing page, and our guide to CI/CD pipeline penetration testing scope covers adjacent decisions.

Frequently Asked Questions

What happened in the TanStack npm supply chain attack?

On 11 May 2026 between roughly 19:20 and 19:26 UTC, an attacker published 84 malicious versions across 42 legitimate @tanstack/* npm packages using the project's own GitHub Actions OIDC trusted-publisher binding, and advisory GHSA-g7cv-rxg3-hmpx states the publish workflow itself was not modified. The payload harvested cloud, Kubernetes, Vault, npm, GitHub and SSH credentials from any machine that ran an install, then republished other packages the victim maintained. It is CVE-2026-45321, in CISA KEV since 27 May 2026.

Was the Nx Console extension compromised?

Yes. Nx Console 18.95.0 for Visual Studio Code was published maliciously and was available roughly 18 minutes on the Visual Studio Marketplace and 36 minutes on Open VSX; it is CVE-2026-48027, patched in 18.100.0. Nrwl states the attacker got in because one of its developers was compromised by the TanStack compromise, which leaked their GitHub credentials through the GitHub CLI. The Nx CLI, official plugins and Nx Cloud were not affected.

Does pinning package versions protect against supply chain attacks?

No. Pinning protects against resolving to an unexpected version, but the attacker published malicious versions of the correct packages under the real publisher identity with valid provenance, so a fresh resolve inside the window pinned to malware. It helps only if the pinned version predates the compromise, which is why TanStack's workaround was to pin to versions published before 11 May 2026, 19:00 UTC.

How do attackers steal GitHub Actions secrets?

In this case they did not steal a stored secret at all. TanStack's advisory describes three known weaknesses chained: a workflow trigger that ran fork-supplied code with the base repository's permissions, cache poisoning letting a low-trust job write entries a high-trust release job later restored, and extraction of the short-lived OIDC token from the runner process. A credential need not be stored to be stolen.

What is a pull_request_target Pwn Request?

The pull_request_target trigger in GitHub Actions runs a workflow with the base repository's permissions and secrets rather than the restricted context a fork pull request normally receives, which is appropriate for trusted operations such as labelling or commenting. It becomes the Pwn Request pattern when the workflow then checks out and executes code from the pull request, so contributor-controlled code runs in a trusted context. It is a documented configuration hazard, not a product bug.

How do you detect a compromised CI/CD pipeline?

Look at runtime and identity behaviour rather than file diffs, because no workflow file was modified here and a diff review would have passed. The detectable signals were bulk deletion of workflow runs, commits whose author did not match the authenticated actor, a stolen token used from different client tooling, and a publish nobody had decided to make.

Which credentials should you rotate after a build system compromise?

Stop execution and remove persistence first, because rotating while an implant is live simply hands over the new credential. Then rotate in blast-radius order: source control and registry publishing credentials, then cloud credentials, cluster and secret-manager tokens, SSH private keys and environment files. Include anything those CLIs could have minted during the window, not only what sat on disk.

Should CI/CD pipelines be in scope for penetration testing?

Yes, and here the pipeline and the developer endpoint were the initial access while the production application was never touched. A useful scope covers workflow trigger contexts that expose privileged permissions to untrusted code, build caches shared across trust boundaries, whether one identity can publish without a second approval, and whether long-lived credentials are readable by any process on a developer machine.

References

0 views

0

X

Related reading

How Big Is an Authorization Fix? Patch Size by Weakness Family
Web App SecurityAdvisories

How Big Is an Authorization Fix? Patch Size by Weakness Family

We measured 3,806 GitHub Advisory Database fix commits. Access control patches change 64 lines to injection's 39, but the code only gap is 2 lines.

17 min read

Who Actually Bans AI-Written Bug Reports: A Census of Disclosure Program Policies
AdvisoriesWeb App Security

Who Actually Bans AI-Written Bug Reports: A Census of Disclosure Program Policies

We coded 53 published bug bounty and disclosure policies on AI written reports. Zero ban them outright, 36 of 53 say nothing, 13 attach a condition.

16 min read

Curl Killed Its Bug Bounty Over AI Slop: How to Triage Real AI Findings From Noise
AdvisoriesWeb App Security

Curl Killed Its Bug Bounty Over AI Slop: How to Triage Real AI Findings From Noise

Curl ended its bug bounty after AI slop pushed confirmed vulnerabilities below 5 percent of reports. Here is how to triage real AI findings from noise.

11 min read

Contents

X