메뉴
HN
Hacker News 19일 전

TanStack npm 공급망 공격 사후 분석 보고서

IMP
9/10
핵심 요약

2026년 5월 11일, 공격자가 깃허브 액션스(GitHub Actions)의 취약점을 악용하여 TanStack의 42개 npm 패키지에 멀웨어를 배포하는 공급망 공격을 감행했습니다. 이 악성 코드는 패키지 설치 시 사용자의 클라우드(AWS, GCP 등) 및 SSH 인증 정보를 탈취하여 외부로 유출하며, 감염된 계정을 통해 추가적으로 자가 전파되는 특징을 가집니다. 외부 보안 연구원에 의해 20분 만에 신속히 탐지되었으나, 해당 시간대에 영향을 받은 버전을 설치한 개발자는 즉시 설치 환경을 격리하고 모든 핵심 인증 정보를 교체해야 합니다.

번역된 본문

TanStack Search... K 자동 로그인 Start RC Start RC Router Router Query Query Table Table DB beta DB beta AI alpha AI alpha Form new Form new Virtual Virtual Pacer beta Pacer beta Hotkeys alpha Hotkeys alpha Store alpha Store alpha Devtools alpha Devtools alpha CLI alpha CLI alpha Intent alpha Intent alpha 라이브러리 더보기 라이브러리 더보기 Builder Alpha Builder Alpha 블로그 블로그 유지보수자 유지보수자 파트너 파트너 쇼케이스 쇼케이스 학습하기 NEW 학습하기 NEW 통계 통계 유튜브 유튜브 디스코드 디스코드 굿즈 굿즈 지원 지원 깃허브 깃허브 이념 이념 원칙 원칙 브랜드 가이드 브랜드 가이드 블로그 이 페이지에서 사후 분석: TanStack npm 공급망 침해 사건 페이지 복사 작성자: Tanner Linsley, 2026년 5월 11일. 마지막 업데이트: 2026-05-11

TL;DR # 2026년 5월 11일 19:20 UTC부터 19:26 UTC 사이에 공격자는 다음 기법들을 조합하여 42개의 @tanstack/* npm 패키지에 걸쳐 84개의 악성 버전을 게시했습니다. pull_request_target을 활용한 'Pwn Request(악의적 PR)' 패턴, 포크(fork)↔베이스(base) 간 신뢰 경계를 넘어선 깃허브 액션스 캐시 오염(Cache Poisoning), 깃허브 액션스 러너 프로세스의 런타임 메모리에서 OIDC 토큰을 추출하는 기법이 사용되었습니다. npm 토큰은 탈취되지 않았으며 npm 게시 워크플로우 자체도 침해되지 않았습니다.

이 악성 버전들은 stepsecurity 소속 외부 보안 연구원인 ashishkurmi에 의해 20분 만에 공개적으로 탐지되었습니다. 영향을 받은 모든 버전은 더 이상 사용되지 않도록(deprecated) 조치되었으며, npm 레지스트리에서 파일을 강제로 삭제하기 위해 npm 보안팀과 협력 중입니다. npm 자격 증명이 탈취되었다는 증거는 없지만, 2026년 5월 11일에 영향을 받은 버전을 설치한 사용자는 설치한 호스트에서 접근 가능한 AWS, GCP, Kubernetes, Vault, GitHub, npm 및 SSH 자격 증명을 모두 교체(rotate)할 것을 강력히 권장합니다.

추적 이슈: TanStack/router#7383 깃허브 보안 권고문: GHSA-g7cv-rxg3-hmpx

영향 범위 # 영향을 받은 패키지 # 42개 패키지, 84개 버전 (패키지당 2개씩, 약 6분 간격으로 게시됨). 전체 목록은 추적 이슈에서 확인하십시오. 확인 결과 안전한 패키지 군: @tanstack/query*, @tanstack/table*, @tanstack/form*, @tanstack/virtual*, @tanstack/store, @tanstack/start (메타 패키지로서, @tanstack/start-* 제외).

멀웨어의 행동 방식 # 개발자 또는 CI(지속적 통합) 환경이 영향을 받는 버전에 대해 npm install, pnpm install 또는 yarn install을 실행하면, npm은 악성 optionalDependencies 항목을 해석하고, 포크 네트워크에서 분리된 페이로드 커밋(orphan payload commit)을 가져와서 prepare 라이프사이클 스크립트를 실행하고, 영향을 받은 tarball 안에 밀반입된 난독화된 약 2.3 MB 크기의 router_init.js를 실행합니다.

해당 스크립트는 다음과 같은 일반적인 위치에서 자격 증명을 수집합니다: AWS IMDS / Secrets Manager, GCP 메타데이터, Kubernetes 서비스 계정 토큰, Vault 토큰, ~/.npmrc, GitHub 토큰 (환경변수, gh CLI, .git-credentials), SSH 개인 키 Session/Oxen 메신저의 파일 업로드 네트워크(filev2.getsession.org, seed{1,2,3}.getsession.org)를 통해 데이터를 외부로 유출합니다. 종단 간 암호화가 적용되어 공격자가 제어하는 C2(명령 제어) 서버가 없으므로 IP/도메인을 차단하는 것이 유일한 네트워크 완화 방법입니다. 자가 전파(Self-propagates): registry.npmjs.org/-/v1/search?text=maintainer:를 통해 피해자가 유지보수하는 다른 패키지를 열거한 뒤 동일한 악성 코드를 주입하여 재게시합니다. 페이로드가 npm install 라이프사이클의 일부로 실행되기 때문에, 2026년 5월 11일에 영향을 받은 버전을 설치한 사람은 누구나 설치 호스트가 침해되었을 가능성이 있다고 간주해야 합니다.

타임라인 # 모든 시간은 UTC 기준입니다. 깃허브 API 및 npm 레지스트리의 로컬 타임스탬프.

공격 전 (캐시 오염 단계) # 시간 / 이벤트 2026-05-10 17:16 / 공격자가 github.com/zblgg/configuration 포크를 생성합니다. (TanStack/router를 포크한 것으로, 포크 목록 검색을 피하기 위해 고의로 이름이 변경됨) 2026-05-10 23:29 / 조작된 신원인 claude claude@users.noreply.github.com가 포크에 악성 커밋 65bf499d16a5e8d25ba95d69ec9790a6dd4a1f14을 작성합니다. (약 30,000줄의 번들링된 JS 페이로드인 packages/history/vite_setup.mjs를 추가함). 푸시 이벤트 시 CI가 실행되지 않도록 커밋 메시지에 [skip ci]를 접두어로 붙임. 2026-05-11 ~10:49 / zblgg가 TanStack/router#main에 대해 "WIP: simplify history build"라는 제목으로 PR #7378을 엽니다. 2026-05-11 10:49 이후 / bundle-size.yml 및 labeler.yml (모두 pull_request_target)이 자동으로 실행됨

원문 보기
원문 보기 (영어)
TanStack Search... K Auto Log In Start RC Start RC Router Router Query Query Table Table DB beta DB beta AI alpha AI alpha Form new Form new Virtual Virtual Pacer beta Pacer beta Hotkeys alpha Hotkeys alpha Store alpha Store alpha Devtools alpha Devtools alpha CLI alpha CLI alpha Intent alpha Intent alpha More Libraries More Libraries Builder Alpha Builder Alpha Blog Blog Maintainers Maintainers Partners Partners Showcase Showcase Learn NEW Learn NEW Stats Stats YouTube YouTube Discord Discord Merch Merch Support Support GitHub GitHub Ethos Ethos Tenets Tenets Brand Guide Brand Guide Blog On this page Postmortem: TanStack npm supply-chain compromise Copy page by Tanner Linsley on May 11, 2026. Last updated: 2026-05-11 TL;DR # 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 Impact # Packages affected # 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-* ). What the malware does # 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: Harvests credentials from common locations: AWS IMDS / Secrets Manager, GCP metadata, Kubernetes service-account tokens, Vault tokens, ~/.npmrc , GitHub tokens (env, gh CLI, .git-credentials ), SSH private keys Exfiltrates over the Session/Oxen messenger file-upload network ( filev2.getsession.org , seed{1,2,3}.getsession.org ) — end-to-end encrypted with no attacker-controlled C2, so blocking by IP/domain is the only network mitigation Self-propagates: enumerates other packages the victim maintains via registry.npmjs.org/-/v1/search?text=maintainer:<user> and republishes them with the same injection 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. Timeline # All times UTC. Local timestamps from GitHub API and npm registry. Pre-attack (cache poisoning phase) # Time Event 2026-05-10 17:16 Attacker creates fork github.com/zblgg/configuration (a fork of TanStack/router, deliberately renamed to evade fork-list searches) 2026-05-10 23:29 Malicious commit 65bf499d16a5e8d25ba95d69ec9790a6dd4a1f14 authored on the fork by fabricated identity claude <claude@users.noreply.github.com> . Adds packages/history/vite_setup.mjs (a ~30,000-line bundled JS payload). Commit message prefixed with [skip ci] to suppress CI on the push event 2026-05-11 ~10:49 PR #7378 opened against TanStack/router#main titled "WIP: simplify history build" by zblgg 2026-05-11 10:49 onwards bundle-size.yml and labeler.yml (both pull_request_target ) auto-run for the PR — no first-time-contributor approval required because pull_request_target bypasses that gate. pr.yml (which uses pull_request ) does NOT run, blocked pending approval that never came 2026-05-11 11:01–11:11 Multiple force-pushes by zblgg to the PR head, each triggering more pull_request_target runs 2026-05-11 11:11 Force-push lands 65bf499d (the malicious commit) on the PR head. bundle-size.yml 's benchmark-pr job checks out refs/pull/7378/merge , runs pnpm install + pnpm nx run @benchmarks/bundle-size:build — this executes vite_setup.mjs 2026-05-11 11:29 Cache entry Linux-pnpm-store-6f9233a50def742c09fde54f56553d6b449a535adf87d4083690539f49ae4da11 (1.1 GB) saved to GitHub Actions cache for TanStack/router , scope refs/heads/main — keyed to match what release.yml will look up on the next push to main 2026-05-11 11:31 Attacker force-pushes the PR back to current main HEAD ( b1c061af ), making the visible PR a 0-file no-op. PR closed and branch deleted in the same minute. Cache poison persists. Detonation (publish phase) # Time Event 2026-05-11 19:15 Manuel merges PR #7369 (Shkumbin's CSS.supports fix) → push to main triggers release.yml . Workflow run 25613093674 starts (19:15:44), and fails. 2026-05-11 19:20:39 npm registry receives publish for @tanstack/history@1.161.9 and 41 sibling packages (~84 versions across 42 packages, but only ~half show this exact second; the remainder come during run #2). Publish is authenticated via OIDC trusted-publisher binding for TanStack/router release.yml@refs/heads/main — but it does not come from the workflow's defined Publish Packages step, which was skipped because tests failed. It comes from the malware running during the test/cleanup phase, which mints an OIDC token via the workflow's id-token: write permission and POSTs directly to registry.npmjs.org 2026-05-11 19:20:47 Run 25613093674 completes (status: failure) 2026-05-11 19:16 Manuel merges PR #7382 (jiti tsconfig paths fix) → second push to main triggers release.yml 2026-05-11 19:16:22 Workflow run 25691781302 starts. Same poisoned cache restored 2026-05-11 19:26:14 npm registry receives publish for the second-version-per-package set ( @tanstack/history@1.161.12 etc.). Same OIDC mechanism 2026-05-11 19:26:20 Run 25691781302 completes (status: failure) Detection and response # Time Event 2026-05-11 ~19:50 External researcher ( carlini ) opens issue #7383 with a complete writeup of the malicious optionalDependencies fingerprint and the package list (initially 14 of the 42) 2026-05-11 ~19:50 Researcher notifies npm security directly 2026-05-11 ~20:00 Manuel acknowledges in #7383 — incident response begins 2026-05-11 ~20:10 Manuel removes all other team push permissions on GitHub in case of user machines have been compromised 2026-05-11 ~20:30 Tanner emails security@npmjs.com with full IOC list and request to pull tarballs registry-side. Formal malware reports are submitted via npm 2026-05-11 ~21:00 Comprehensive scan of all 295 @tanstack/* packages confirms scope: 42 packages, 84 versions. Tanner begins npm deprecation process for all 84 affected packages. Public Twitter/X/LinkedIn/Bluesky disclosure from @tan_stack and maintainers 2026-05-11 21:30 Investigation identifies bundle-size.yml pull_request_target cache-poisoning vector and the zblgg/configuration fork. All cache entries for all TanStack/* GitHub repositories purged via API. Hardening PR merged: bundle-size.yml restructured, repository_owner guards added, third-party action refs pinned to SHAs. Official GitHub Security Advisory is published, CVE requested Root cause # Three vulnerabilities chained together. Each is necessary for the attack; none alone is sufficient. 1. pull_request_target "Pwn Request" pattern in bundle-size.yml # 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: yaml on : pull_request_target : paths : [ 'packages/**' , 'benchmarks/**' ] jobs : benchmark-pr :