BETA — Сайт у режимі бета-тестування. Можливі помилки та зміни.
UK | EN |
LIVE
Технології 🇺🇸 США

Postmortem: TanStack npm supply-chain compromise

Hacker News varunsharma07 1 переглядів 7 хв читання
TanStackSearch... KAutoLog InStartRCRouterQueryTableDBbetaAIalphaFormnewVirtualPacerbetaHotkeysalphaStorealphaDevtoolsalphaCLIalphaIntentalphaMore LibrariesBuilderAlphaBlogMaintainersPartnersShowcaseLearnNEWStatsYouTubeDiscordMerchSupportGitHubEthosTenetsBrand GuideBlogOn this pagePostmortem: TanStack npm supply-chain compromiseCopy page

by Tanner Linsley on May 11, 2026.

On 2026-05-11 between 19:20 and 19:26 UTC, an attacker published 84 malicious versions across 42 @tanstack/* npm packages by combining: the pull_request_target "Pwn Request" pattern, GitHub Actions cache poisoning across the fork↔base trust boundary, and runtime memory extraction of an OIDC token from the GitHub Actions runner process. No npm tokens were stolen and the npm publish workflow itself was not compromised.

The malicious versions were detected publicly within 20 minutes by an external researcher ashishkurmi working for stepsecurity. All affected versions have been deprecated; npm security has been engaged to pull tarballs from the registry. We have no evidence of npm credentials being stolen, but we strongly recommend that anyone who installed an affected version on 2026-05-11 rotate AWS, GCP, Kubernetes, Vault, GitHub, npm, and SSH credentials reachable from the install host.

Tracking issue: TanStack/router#7383 GitHub Security Advisory: GHSA-g7cv-rxg3-hmpx

42 packages, 84 versions (two per package, published roughly 6 minutes apart). See the tracking issue for the full table. Confirmed-clean families: @tanstack/query*, @tanstack/table*, @tanstack/form*, @tanstack/virtual*, @tanstack/store, @tanstack/start (the meta-package, not @tanstack/start-*).

When a developer or CI environment runs npm install, pnpm install, or yarn install against any affected version, npm resolves the malicious optionalDependencies entry, fetches the orphan payload commit from the fork network, runs its prepare lifecycle script, and executes a ~2.3 MB obfuscated router_init.js smuggled into the affected tarball. The script:

Because the payload runs as part of npm install's lifecycle, anyone who installed an affected version on 2026-05-11 must treat the install host as potentially compromised.

All times UTC. Local timestamps from GitHub API and npm registry.

Three vulnerabilities chained together. Each is necessary for the attack; none alone is sufficient.

bundle-size.yml ran pull_request_target for fork PRs and, inside that trigger context, checked out the fork's PR-merge ref and ran a build:

on: pull_request_target: paths: ['packages/**', 'benchmarks/**'] jobs: benchmark-pr: steps: - uses: actions/checkout@v6.0.2 with: ref: refs/pull/${{ github.event.pull_request.number }}/merge # fork's merged code - uses: TanStack/config/.github/setup@main # transitively calls actions/cache@v5 - run: pnpm nx run @benchmarks/bundle-size:build # executes fork-controlled codeon: pull_request_target: paths: ['packages/**', 'benchmarks/**'] jobs: benchmark-pr: steps: - uses: actions/checkout@v6.0.2 with: ref: refs/pull/${{ github.event.pull_request.number }}/merge # fork's merged code - uses: TanStack/config/.github/setup@main # transitively calls actions/cache@v5 - run: pnpm nx run @benchmarks/bundle-size:build # executes fork-controlled code

The author of the workflow attempted a trust split (the comment-pr job is separate from benchmark-pr, with a comment in the YAML noting the intent to keep benchmark-pr "untrusted with read-only permissions"). The split is correct in spirit but missed two facts:

The malicious vite_setup.mjs was specifically designed to write data into the pnpm-store directory under a key the legit release.yml workflow would compute and look up: Linux-pnpm-store-${hashFiles('**/pnpm-lock.yaml')}. When the benchmark-pr job ended, actions/cache@v5's post-step saved the (now-poisoned) pnpm store to that exact key. When release.yml next ran on a push to main, its Setup Tools step restored the poisoned entry — entirely as designed.

This is the class of attack documented by Adnan Khan in 2024. It's not a TanStack-specific bug; it's a known GitHub Actions design issue that requires conscious mitigation.

release.yml declares id-token: write (legitimately needed for npm OIDC trusted publishing). When the poisoned pnpm store is restored on the runner, attacker-controlled binaries are now on disk and get invoked during the build step. Those binaries:

This is the same memory-extraction technique (and verbatim Python script, with attribution comment) used in the tj-actions/changed-files compromise of March 2025. The attacker did not invent novel tradecraft; they recombined published research.

The chain only works because each vulnerability bridges the trust boundary the others assumed: PR fork code crossing into base-repo cache, base-repo cache crossing into release-workflow runtime, and release-workflow runtime crossing into npm registry write access.

Detection was external. carlini opened issue #7383 ~20 minutes after the publish, with full technical analysis. Tanner received a phone call from Socket.dev just moments after starting the war room confirming the situation.

In any @tanstack/* package's manifest:

"optionalDependencies": { "@tanstack/setup": "github:tanstack/router#79ac49eedf774dd4b0cfa308722bc463cfe5885c" }"optionalDependencies": { "@tanstack/setup": "github:tanstack/router#79ac49eedf774dd4b0cfa308722bc463cfe5885c" }
  • File: router_init.js (~2.3 MB, package root, not in "files")
  • Cache key: Linux-pnpm-store-6f9233a50def742c09fde54f56553d6b449a535adf87d4083690539f49ae4da11
  • 2nd-stage payload URLs: https://litter.catbox.moe/h8nc9u.js, https://litter.catbox.moe/7rrc6l.mjs
  • Exfiltration network: filev2.getsession.org, seed{1,2,3}.getsession.org
  • Forged commit identity: claude <claude@users.noreply.github.com> (note: not the real Anthropic Claude — fabricated GitHub no-reply email)
  • Real attacker accounts: zblgg (id 127806521), voicproducoes (id 269549300)
  • Attacker fork: github.com/zblgg/configuration (fork of TanStack/router renamed to evade fork searches)
  • Orphan payload commit (in fork network): 79ac49eedf774dd4b0cfa308722bc463cfe5885c
  • Workflow runs that performed the malicious publishes:

Lessons learned

What went well

  • External researchers noticed and reported with full technical detail within ~20 min of the incident
  • Maintainer team coordinated immediately and effectively across many timezones
  • The detection community already had a clear public IOC pattern within hours

What could have been better

  • No internal alerting. We learned about the compromise from a third party. We need monitoring on our own publishes. We'll be working closely with security researcher firms in the ecosystem that have the ability to detect these issues very quickly, potentially even in-house, and making the feedback loop even tighter.
  • pull_request_target workflows had not been audited despite being a long-known dangerous pattern
  • Floating refs (@v6.0.2, @main) on third-party actions create standing supply-chain risk independent of this incident
  • Unpublish was unavailable for nearly all affected packages because of npm's "no unpublish if dependents exist" policy. We have to rely on npm security to pull tarballs server-side, which adds hours of delay during which malicious tarballs remain installable
  • The 7-maintainer list on the npm scope means seven separate credential-theft targets for the same blast radius
  • OIDC trusted-publisher binding has no per-publish review. Once configured, any code path in the workflow can mint a publish-capable token. We need either (a) move to short-lived classic tokens with manual review, or (b) add provenance-source-verification to detect publishes from unexpected workflow steps

What we got lucky on

  • The attacker chose a payload that broke tests, which made the publish step (which would have produced cleaner-looking tarballs) skip — meaning the attack was loud enough to detect quickly. A more careful attacker who didn't break tests could have published silently for hours longer
  • The attacker reused public tradecraft (verbatim memory-dump script with attribution comment) instead of writing novel code — making the IOC-matching faster

Open questions

These need answers before we close the postmortem.

See the GitHub Security Advisory for the full list of affected versions: GHSA-g7cv-rxg3-hmpx

Поділитися

Схожі новини