tj-actions/changed-files 2025年 書き換えられたタグとCIの秘密

GitHub Actions のタグは、書き込みの権限があれば別のコミットへ付け替えられる。タグで呼んでいた CI は、付け替えた先のコードを、そのジョブの秘密ごと動かした。1つの部品の CI から漏れたトークンが次の部品のタグを書き換え、2万3千を超えるリポジトリが使う部品に届いた。

CVE-2025-30066。CVSS 3.1 の基本値は8.6、CWE-506 埋め込まれた悪意あるコード[1][2]。影響は v1 から v45.0.7 のタグで、2025年3月14日と15日に悪いコミットを指した。修正は 46.0.1[1][2]。前段の reviewdog/action-setup の件は CVE-2025-30154[5]。保守者は2025年3月15日に、悪いコミットを全部のタグと枝から除き、タグの保護と署名つきのコミットの必須を入れたと書いた[4]。2026年11月時点の公開資料で書いています。

要点

  • 悪いコミットは、CI の実行機のプロセスのメモリから秘密を読み、2重に Base64 にしてジョブのログへ出した。公開のリポジトリでは、ログを誰でも読めた。
  • 付け替えに使われたのは、tj-actions の bot のアカウントの個人用のアクセストークンで、CI の秘密として置かれていた。
  • その前に reviewdog/action-setup の v1 のタグが3月11日に約2時間書き換えられていた。tj-actions の CI は、それを中で呼ぶ部品を使っていた。
  • タグではなくコミットのハッシュで固定していれば、changed-files の付け替えは届かない。ただし、固定した部品が中で別の部品をタグで呼んでいれば、そこから入られる。
  • 根本の守りは、全部の外部のアクションを推移的な依存まで中身で固定し、CI に長く使える広いトークンを置かないこと。

何が起きたか

2024年12月の SpotBugs のトークンの漏れ、2025年3月11日の reviewdog のタグの書き換え、3月14日の changed-files のタグの書き換え、3月15日の修正の4つの段 1 最初の漏れ 2024年12月 別の OSS の CI Unit 42 の調べ 2 reviewdog 2025年3月11日 action-setup の v1 約2時間の書き換え 3 tj-actions 2025年3月14日 changed-files の 全部のタグ 4 発覚と修正 3月14日に報告 3月15日に CVE タグを戻す 2から3まで3日。3は同じ日のうちに報告された 1の時期と経路は Unit 42 の調べで、保守者の発表には無い
図1 最初の漏れから修正までの段
日付出来事
2024年11月28日、12月6日Unit 42 の調べ。SpotBugs の保守者が、ある CI の定義に個人用のアクセストークンを足し、その CI は pull_request_target の契機で動いていた。12月6日に、攻撃者がそのリポジトリへ変更の依頼を送り、トークンを取った[8]
2025年3月11日 10時52分 UTCUnit 42 の調べ。取ったトークンで SpotBugs の別のリポジトリに捨てのアカウントを招き、悪い CI を走らせて、reviewdog の保守者でもある人のトークンを取った[8]
2025年3月11日 18時42分から20時31分 UTCreviewdog/action-setup の v1 のタグが書き換えられる。秘密をログへ出すコードが足されていた。中でこれを v1 で呼ぶ reviewdog の他のアクションも、版や固定の仕方にかかわらず影響を受けた[5][6]
2025年3月14日changed-files の v1 から v45.0.7 のタグが、悪いコミット 0e58ed8 を指すよう書き換えられる[1]。Unit 42 は16時57分 UTC とし、コミットは更新の自動の bot を装っていたと書く[8]
2025年3月14日 20時52分 UTC利用者がリポジトリに、複数のタグが侵害されていると報告する。外への不審な通信を見つけた StepSecurity の記事を引いていた[3][2]
2025年3月15日CVE-2025-30066 が NVD と GitHub の勧告の一覧に載る[1][2]。保守者が、悪いコミットを全部のタグと枝から除き、検証されていないコミットを指すタグを押せない保護を入れ、GitHub が組織の公開を戻したと書く[4]。攻撃は tj-actions の bot のアカウントに結び付いたトークンからで、GitHub もどう漏れたか分からない、とも書く[4]
2025年3月16日保守者が、そのトークンは CI の秘密として置かれていて無効にしたこと、今後 tj-actions の組織では個人用のアクセストークンを使わないことを書く[4]
2025年3月18日CISA が告知を出し、CVE-2025-30066 を既知の悪用された脆弱性の一覧に載せる[7][1]。reviewdog の保守者が、Wiz Research の知らせで action-setup の件を知ったと公表する[6]
2025年3月19日CISA が、changed-files の侵害は reviewdog の侵害で可能になった恐れがあると告知に足す[7]。CVE-2025-30154 が公表される[5]
2025年3月23日reviewdog の保守者が、トークンを漏らした貢献者を特定したこと、そのトークンは別のリポジトリへの攻撃から漏れたこと、書き換えは v1 のタグだけだったことを書く[6]
2025年4月2日Unit 42 が、最初の漏れが SpotBugs の CI だったとする調べを足す[8]
最初の漏れの経路は保守者の発表に無い

SpotBugs の CI からの漏れと、tj-actions の bot のトークンが reviewdog を通じて漏れたという道筋は、セキュリティの会社の Unit 42 の調べによる[8]。tj-actions の保守者は、トークンがどう漏れたかを GitHub も特定できないと書いた[4]。reviewdog の保守者は、トークンが別のリポジトリへの攻撃から漏れたことまでを書き、その調べとして Unit 42 と Wiz を挙げた[6]。Unit 42 は、この連鎖の狙いの1つが Coinbase のリポジトリだったとし、Coinbase は損害は無かったと答えたと書く[8]。

通った道筋

reviewdog の v1 のタグが付け替えられ、それを中で呼ぶ tj-actions の部品が tj-actions の CI で動き、bot のトークンが漏れ、changed-files のタグが付け替えられ、利用者の CI の秘密がログに出る reviewdog/action-setup v1 を悪いコミットへ v1 で tj-actions の別の部品 中で action-setup を呼ぶ tj-actions の CI bot のトークンが漏れる changed-files 全部のタグを 0e58ed8 へ タグで呼ぶ利用者の全部 利用者の CI の実行機。2万3千を超えるリポジトリが使っていた ジョブの秘密 クラウドの鍵 トークン、秘密鍵 悪いコード メモリから読み 2重の Base64 に ジョブのログ 公開のリポジトリ では誰でも読める
図2 タグの付け替えが連鎖して利用者の秘密に届くまで。上半分の部品どうしの関係は Unit 42 の調べによる
段公表されたこと
reviewdog の書き込み権限reviewdog の組織は、action の各リポジトリに貢献した人を自動で招き、書き込みの権限を持つ班に入れていた。班は当時118人だった。保守者は、この自動の招きか、既存の貢献者のアカウントの侵害で書き込みの権限を得たとみた[6]。後に、ある貢献者のトークンで、ふつうの git push でタグが上書きされていたと分かった。タグへの git push は、GitHub の公開の活動の記録に出ないとも書いた[6]
中で呼ぶ部品reviewdog の他のアクションは中で action-setup を v1 で呼んでいたので、利用者がそのアクションを何で固定していても影響を受けた[5]。Unit 42 は、tj-actions の CI が、action-setup を中で呼ぶ tj-actions の別の部品を使っていたとする[8]
CI のトークン付け替えは tj-actions の bot に結び付いた個人用のアクセストークンからで、そのトークンは CI の秘密として置かれていた[4]
利用者の CI悪いコードは、実行機の作業のプロセスのメモリから秘密を読み、Base64 にしてログへ出した[2]。CISA は、ログの秘密は2重の Base64 で隠れている場合があると注意した[7]
  • ログへ出したのは秘密そのものではなく、符号にした値である。符号にした値は元の秘密の文字列と一致しないので、ログの伏せ字を当てにできない。CISA が2重の Base64 に注意を促したのはこのためである[7]。
  • 外へ送るのでなくログへ出したため、外への通信を絞っても漏れは止まらない。ただし、悪いコードは公開の gist から処理を取ってきており、その外への通信で見つかった[2]。

タグと、ハッシュでの固定

次は架空の組織 example-org の部品を使う、架空の CI の定義である。

悪い例。タグで呼び、広い権限で動かす

permissions: write-all
jobs:
  lint:
    runs-on: ubuntu-latest
    steps:
      - uses: example-org/changed-files@v45   # タグは付け替えられる
      - run: ./deploy.sh
        env:
          CLOUD_KEY: ${{ secrets.CLOUD_KEY }}

直した例。コミットのハッシュで固定し、権限と秘密を絞る

permissions:
  contents: read
jobs:
  lint:
    runs-on: ubuntu-latest
    steps:
      - uses: example-org/changed-files@4f2a9c1e7b3d5a6c8e0f1a2b3c4d5e6f7a8b9c0d  # v46.0.1
  deploy:
    needs: lint
    environment: production   # 承認の要る環境にだけ本番の秘密を置く
    runs-on: ubuntu-latest
    steps:
      - run: ./deploy.sh
        env:
          CLOUD_KEY: ${{ secrets.CLOUD_KEY }}
  • GitHub の文書は、外部のアクションをコミットのハッシュで固定するよう求め、作者を信頼していても、リポジトリに入られればタグは動かされたり消されたりすると書く。ハッシュを選ぶときは、fork ではなくそのアクションのリポジトリのものかを確かめるよう求める[9]。
  • 部品を使うジョブと、本番の秘密を使うジョブを分ける。この件の悪いコードはジョブのプロセスのメモリを読んだ[2]。同じジョブに秘密が無ければ、読む物が無い。
利用者がハッシュで固定した部品でも、その部品が中で別の部品をタグで呼んでいれば、付け替えが届く 利用者の CI 自分の定義 ハッシュ 部品 A 中身は固定される v1 部品 B 付け替えられる 自分の定義を全部ハッシュにしても、部品 A の中の呼び方は自分では決められない reviewdog の勧告は、影響を受けたアクションを、版や固定の仕方にかかわらず、と書いた 固定の確かめは、推移的な依存まで見て初めて足りる
図3 ハッシュでの固定が届かない所

2026年11月時点の GitHub の文書は、推移的な依存まで含めて全部のアクションのコミットを記録し、実行時に出どころのリポジトリを確かめるロックファイルを、最も強い守りとし、ハッシュでの固定に代わるものとしている[9]。GitHub は2025年8月15日から、ハッシュで固定していないアクションを使うワークフローを失敗させる決まりを、企業、組織、リポジトリの単位で掛けられるようにした[10]。

どこで止められたか

保守の権限、CI のトークン、利用者の固定、ジョブの秘密、ログと通信の5つの段に、それぞれ止める所を置く 保守の権限 CI のトークン 利用者の固定 ジョブの秘密 ログと通信 1 書き込みを自動で配らず、タグを守る 付け替えには保護と署名を要する 2 長く使える個人のトークンを置かない 短い寿命の、絞った権限のトークンに 3 推移的な依存まで中身で固定する 利用者の側の根本の段 4 部品を動かすジョブに秘密を渡さない 入られても読む物が無い 5 外への通信を見て、漏れたら全部回す この件で見つけたのはここ
図4 段ごとの止める所
止める所この件で限り
書き込みを自動で配らず、タグを守るreviewdog は貢献者を自動で招いて書き込みを渡していた。事故の後に自動の招きを止め、ほとんどの貢献者から書き込みを外した[6]。tj-actions は、検証されていないコミットを指すタグを押せない保護と、署名つきのコミットの必須を入れた[4]保守者の正規のトークンが盗まれれば、保護の範囲の中で付け替えられる
長く使える個人のトークンを置かない付け替えたのは CI の秘密に置いた bot のトークンだった[4]。SpotBugs から reviewdog までも、個人のトークンを渡り歩いた[8]CI が書き込む以上、何らかの資格情報は置く。寿命と範囲を絞るほど、盗まれた後に使える時間と場所が減る
推移的な依存まで中身で固定するchanged-files をハッシュで呼んでいれば、付け替えは届かない[9]中で別の部品をタグで呼ぶ部品からは入られる[5]。固定した版に最初から悪いコードがあれば防げない
部品を動かすジョブに秘密を渡さない悪いコードは実行機のプロセスのメモリから秘密を読んだ[2]同じジョブの中では、どの手順からもメモリは読める。ジョブを分けるしかない
外への通信を見て、漏れたら全部回す外の gist への想定外の通信が見つかるきっかけだった[2]。CISA は、期間中に動いた CI の秘密を、漏れたものとして全部すぐに回すよう求めた[7]漏れはログへの出力で、通信を止めても漏れは止まらない。見つけた時には読まれている前提で回す

守り方

根本の直し方
  • 外部のアクションは全部、コミットのハッシュで固定する。使える所では推移的な依存まで記録するロックファイルを使い、使えない所ではハッシュの固定を組織の決まりで強制する[9][10]。固定のハッシュは、fork でなく本来のリポジトリのものかを確かめる[9]。
  • 自分の作る部品が中で使う部品も、ハッシュで固定する。reviewdog は事故の後に、全部のリポジトリで中のアクションをハッシュで指定し直した[6]。
  • CI に長く使える個人用のアクセストークンを置かない。GITHUB_TOKEN は要る権限だけを付け、要るジョブにだけ広げる[9]。tj-actions は今後、組織で個人用のトークンを使わないとした[4]。
  • 外の部品を動かすジョブと、本番の秘密を使うジョブを分け、本番の秘密は承認の要る環境に閉じる。
  • fork からの変更の依頼で、秘密を持って動く pull_request_target の契機は、要らなければ使わない。使うなら信頼しないコードを取り出して動かさない[9]。
緩和の策
  • CI の実行機から外への通信を、行き先の許可の一覧で絞り、想定外の通信を警告にする。この件はその通信で見つかった[2]。
  • 公開のリポジトリのログに秘密が出ていないか、期間中の実行を調べる。符号にした値は伏せられないので、ログの伏せ字を当てにしない[7]。
  • 漏れの疑いが出たら、期間中に動いた CI に渡していた秘密を全部回す。手順は 秘密の鍵の扱い の漏れたときの順にある。
  • 組織に書き込みの権限を持つ人と bot を棚卸しし、自動で渡す仕組みを止める。

型としての整理は A03 ソフトウェアサプライチェーンの不備 の型3、ビルドと配布の仕組みを乗っ取られる型に当たる。悪い版が範囲指定で届いた先例は event-stream 2018年、配布物にだけ仕込まれた先例は xz utils 2024年 にある。

AIが書く CI の定義

  • AIのコーディングの道具にワークフローを書かせると、アクションを版のタグで呼ぶ定義を出しがちである。差分で uses: の行を見て、ハッシュでなければ直させる。固定の考え方は AIが持ち込む部品の危険 の固定する節にある。
  • AIは、動かないと言われた CI を直すために permissions: write-all を足したり、個人用のトークンを秘密に置く案を出したりする。権限を広げる差分は、人がその権限が要る理由を確かめてから入れる。

参照

  1. CVE-2025-30066 NIST NVD
  2. tj-actions changed-files through 45.0.7 allows remote attackers to discover secrets by reading actions logs GHSA-mrrh-fwg8-r2c3 GitHub Advisory Database、2025年3月15日
  3. Multiple tags in this action are compromised tj-actions/changed-files の issue 2463、2025年3月14日
  4. Pretty sure this repo got hacked tj-actions/changed-files の issue 2464、保守者の2025年3月15日と16日の書き込み
  5. CVE-2025-30154 NIST NVD、2025年3月19日公表
  6. Security Advisory Supply Chain Attack on reviewdog GitHub Actions during a specific time period reviewdog の保守者、2025年3月18日、3月23日の追記
  7. Supply Chain Compromise of Third-Party tj-actions/changed-files CVE-2025-30066 and reviewdog/action-setup@v1 CVE-2025-30154 CISA、2025年3月18日、3月26日改訂
  8. GitHub Actions Supply Chain Attack: A Targeted Attack on Coinbase Expanded to the Widespread tj-actions/changed-files Incident Palo Alto Networks Unit 42、2025年3月20日、4月2日更新。調査会社の調べ
  9. Secure use reference GitHub Docs、2026年11月に参照
  10. GitHub Actions policy now supports blocking and SHA pinning actions GitHub Changelog、2025年8月15日