言語を切り替える
テーマを切り替える

GitHub Actions Secrets 管理:漏洩リスクから OIDC のキーレスデプロイまで

2025 年 3 月のある週末、GitHub のセキュリティチームは 23,000 を超えるリポジトリのオーナーに緊急のメールを送りました。

あなたの secrets は、すでに漏洩しているかもしれません——という内容です。

原因となったのは tj-actions/changed-files という action でした。広く使われていたこのツールが攻撃者に乗っ取られ、workflow 内のすべての環境変数と secrets をひそかに盗み出していたのです。「自分のプロジェクトもこの action を使っているけど、被害に遭うのだろうか」と不安になった人も多いでしょう。

この事件は、多くの人が見落としていた問題を表舞台に押し上げました。GitHub Actions の secrets は、いったいどう管理すべきなのか。

この記事では 3 つのテーマを扱います。secrets の 3 層アーキテクチャの選び方、8 つのセキュリティ鉄則の守り方、そして OIDC によって静的な認証情報の漏洩という悪夢から完全に抜け出す方法です。

一、GitHub Actions Secrets の 3 層アーキテクチャ

GitHub は secrets を保存する場所として、Repository、Environment、Organization の 3 つの層を用意しています。どれを選ぶか。答えは「あなたのシナリオ次第」です。

Repository Secrets:個人プロジェクトの第一候補

これは最もシンプルな層です。secrets はリポジトリ単位で保存され、すべての workflow からアクセスできます。個人プロジェクト、単一リポジトリ、複数環境へのデプロイが不要——こうしたケースなら、これだけで十分です。

唯一の欠点は、staging と production で同じ名前の secret を区別できないこと。たとえば DATABASE_URL があり、staging と production で値が異なる場合はどうするか。そこで必要になるのが Environment Secrets です。

Environment Secrets:複数環境デプロイの必須機能

Environment secrets は環境ごとに分離でき、承認フローにも対応します。GitHub のリポジトリ設定で stagingproduction という 2 つの環境を作り、それぞれ別の secrets を設定できます。

重要な特性が 1 つあります。その環境を参照する job だけが、対応する secrets にアクセスできることです。これによってセキュリティの境界がより明確になります。

jobs:
  deploy-staging:
    runs-on: ubuntu-latest
    environment: staging  # staging 環境を参照
    steps:
      - run: echo "Deploying to staging..."
      - env:
          API_KEY: ${{ secrets.API_KEY }}  # staging 環境の secret

  deploy-production:
    runs-on: ubuntu-latest
    environment: production  # production 環境を参照(承認を設定可能)
    steps:
      - run: echo "Deploying to production..."
      - env:
          API_KEY: ${{ secrets.API_KEY }}  # production 環境の secret

上の設定では、deploy-stagingstaging 環境の secrets にしかアクセスできず、deploy-productionproduction 環境の secrets にしかアクセスできません。両者は互いに干渉しません。

さらに Environment は「保護ルール」にも対応します。たとえば production 環境には、手動の承認を経なければ実行できないように設定できます。チームで協働するときに特に役立ちます。

Organization Secrets:チーム共有を一元管理

チームに数十のリポジトリがあり、どのリポジトリにも同じ AWS_ACCESS_KEY を設定しなければならない——コピー&ペーストを数十回、更新のたびにまた数十回。考えただけで頭が痛くなります。

Organization secrets は、まさにこの問題を解決するためのものです。組織レベルで一度設定すれば、すべてのリポジトリで使えます。どのリポジトリがアクセスできるかも制御できます。すべてのリポジトリにするか、指定したリポジトリのリストに限定するかです。

# リポジトリの workflow では、repository secrets とまったく同じように使える
steps:
  - env:
      AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
      AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}

どう選ぶか

シンプルにまとめると、次のとおりです。

シナリオおすすめの層
個人プロジェクト、単一環境Repository Secrets
複数環境デプロイ(staging/production)Environment Secrets
チームの複数リポジトリ、認証情報の共有Organization Secrets

私自身の失敗から言うと、多くのプロジェクトは最初 Repository Secrets で始め、複数環境が必要になってから Environment Secrets へ移行します。移行コストは高くありませんが、それでも早めの計画をおすすめします。

二、Secrets セキュリティのベストプラクティス — 8 つの鉄則

ここまでは secrets の保存方法を説明しました。ここからは使い方の話です。実践のなかで見つけてきた 8 つの鉄則をまとめました。どれも教訓に裏打ちされています。

1. 命名は規則どおりに

すべて大文字 + アンダースコア区切り、たとえば AWS_ACCESS_KEY_ID です。小文字やキャメルケースは使わないこと。理由はシンプル。これが secret だと一目でわかり、ふつうの変数と混同しないからです。

2. JSON を 1 つの secret にまとめない

これは典型的な落とし穴です。設定ファイル全体を 1 つの secret に押し込む人がいます。

{"api_key": "xxx", "db_url": "yyy", "token": "zzz"}

そして workflow 内で fromJson を使って解析する。問題は、この secret がひとたび漏洩すると、すべての機密情報がいっしょに漏れることです。正しいやり方は、値ごとに独立した secret として保存することです。

3. 明示的に渡す、インラインにしない

# 悪い例 ❌
- run: my-cli --token ${{ secrets.MY_TOKEN }}

# 正しいやり方 ✅
- env:
    MY_TOKEN: ${{ secrets.MY_TOKEN }}
  run: my-cli --token $MY_TOKEN

なぜか。GitGuardian の調査によると、コマンドライン引数は同じマシン上の別プロセスから ps x -w で見えてしまいます。環境変数のほうがはるかに安全です。

4. 定期的にローテーションする

30〜90 日に 1 回は交換しましょう。ローテーションが面倒なのはわかります——でも、漏洩後の後始末に比べれば、この手間は何でもありません。Blacksmith チームは、クラウドサービス(AWS/GCP)を使うなら OIDC と組み合わせてこのステップを完全にスキップできると勧めています。

5. Actions を SHA にピン留めする

サプライチェーン攻撃に対する第一の防衛線です。

# 悪い例 ❌
- uses: tj-actions/changed-files@v45

# 正しいやり方 ✅
- uses: tj-actions/changed-files@b827595e0a7e97537d7c7a2f458b5a8e6d5c8e39

バージョン番号のタグではなく commit SHA を使います。タグは攻撃者に書き換えられますが、SHA は不変です。

6. GITHUB_TOKEN には最小権限だけを

GitHub は各 workflow に GITHUB_TOKEN を自動で提供します。デフォルトの権限は高すぎます——コードを書き込めるし、issue も変更できます。workflow またはリポジトリ設定で read-only に変更することをおすすめします。

permissions:
  contents: read

7. ログで secrets が正しくマスクされているか確認する

GitHub はログ内の ${{ secrets.XXX }} を自動的に *** に置き換えます。ただし、次のように書くと注意が必要です。

- run: echo "Token is $MY_TOKEN"

ログに本物の token の値が出てしまいます。workflow をテストして、意図しない露出がないか必ず確認しましょう。

8. 派生した機密値も登録する

workflow が 1 つの secret から新しい機密値を生成した場合(たとえば API key で JWT を生成するなど)、その新しい値も secret として登録してください。メモリ内で受け渡すだけにしないことです。


この 8 つの鉄則は机上の空論ではありません。tj-actions 事件のあと、StepSecurity チームが数千の公開リポジトリを監査したところ、これらのルールに違反するリポジトリがかなりの割合を占めていました。直すのは難しくありませんが、1 項目ずつ点検するには少し時間がかかります。

三、OIDC — キーレス時代のクラウドデプロイ認証

前の 2 つの鉄則で「secrets を定期的にローテーションする」ことに触れました。正直に言うと、ローテーションは本当に面倒です——毎回、手動で AWS コンソールを変更し、GitHub secrets を更新し、チームメンバーに知らせる必要があります。

OIDC(OpenID Connect)は 1 つの抜け道を用意してくれます。そもそも secrets を保存しない、という考え方です。

OIDC はどう動くのか

従来の方式では、AWS で IAM ユーザーを作成し、access key を生成して、その key を GitHub secrets に保存します。workflow が実行されるたびに、この静的な key で AWS のリソースをリクエストします。

OIDC 方式では、GitHub が ID プロバイダーとして「この workflow は eastondev/my-repo リポジトリから来た」ことを AWS に証明します。AWS は検証したうえで、短期間の JWT トークン(有効期間は数分から数時間)を発行します。workflow はこの一時トークンでタスクを完了し、トークンは期限が切れると自動的に無効になります。

長期的な認証情報を一切保存する必要がなく、ローテーションも不要、漏洩リスクも存在しません。

AWS OIDC の設定例

手順は 2 つに分かれます。AWS 側で信頼関係を設定し、GitHub 側でトークンをリクエストします。

AWS 側(コンソール操作):

  1. IAM Identity Provider を作成し、URL を https://token.actions.githubusercontent.com に設定
  2. IAM Role を作成し、信頼ポリシーを自分のリポジトリに限定する
{
  "Version": "2012-10-17",
  "Statement": [{
    "Effect": "Allow",
    "Principal": {
      "Federated": "arn:aws:iam::123456789:oidc-provider/token.actions.githubusercontent.com"
    },
    "Action": "sts:AssumeRoleWithWebIdentity",
    "Condition": {
      "StringEquals": {
        "token.actions.githubusercontent.com:aud": "sts.amazonaws.com",
        "token.actions.githubusercontent.com:sub": "repo:eastondev/my-repo:ref:refs/heads/main"
      }
    }
  }]
}

この設定の意味は、eastondev/my-repo リポジトリの main ブランチだけがこの role を assume できる、ということです。

GitHub 側(workflow 設定):

jobs:
  deploy:
    runs-on: ubuntu-latest
    permissions:
      id-token: write  # 必須:OIDC token をリクエスト
      contents: read
    steps:
      - uses: aws-actions/configure-aws-credentials@v4
        with:
          role-to-assume: arn:aws:iam::123456789:role/GitHubActionsRole
          aws-region: us-east-1
      - run: aws s3 sync ./dist s3://my-bucket

ここには secrets.AWS_ACCESS_KEY が一切ないことに注目してください——role による認証を直接使っています。

GCP と Azure

3 大クラウドはいずれも OIDC に対応しており、設定もほぼ同じです。

クラウドGitHub Action公式ドキュメントのキーワード
AWSaws-actions/configure-aws-credentialsOIDC federation
GCPgoogle-github-actions/authWorkload Identity Federation
Azureazure/loginFederated Identity Credentials

OIDC の思わぬメリット

johal.in のテストによると、OIDC 認証の遅延は従来の secrets 方式に比べて約 87% 削減されました。理由はシンプルで、GitHub secrets API から認証情報を読み取る必要がなく、ローカルの JWT から直接一時トークンに引き換えるからです。

私個人の使用感としても、OIDC はクラウドデプロイの第一候補です。唯一のハードルは設定が少し複雑なこと——でも一度設定してしまえば、あとはまったく手がかかりません。

四、サプライチェーン攻撃対策 — tj-actions 事件の振り返り

冒頭で触れた tj-actions/changed-files 事件に戻りましょう。それはどう起きたのか。私たちは何を学べるのか。

事件の経緯

2025 年 3 月、攻撃者は tj-actions リポジトリの maintainer 権限を取得しました(具体的な経路はまだ調査中で、認証情報の漏洩かアカウント乗っ取りの可能性があります)。彼らは v45 バージョンのコードに悪意あるスクリプトを忍ばせました。このスクリプトは workflow の実行時にすべての環境変数と secrets をひそかに読み取り、攻撃者が管理するサーバーに送信していました。

Semgrep の分析によると、tj-actions/changed-files@v45 を使うすべての workflow が影響を受けました——GitHub secrets を使っていようと OIDC を使っていようと、この action が環境変数にアクセスできさえすれば盗まれてしまいます。

Unit42 Palo Alto のレポートは、23,000 を超えるリポジトリがこの action を使っていたと指摘しています。そのなかには有名プロジェクトの fork も含まれていました。

教訓

この事件は、いくつかの問題を浮き彫りにしました。

  1. Action のバージョンタグは信用できない——攻撃者はタグを悪意ある commit に向け直せる
  2. サードパーティの action はあなたの secrets にアクセスできる——ひとたび乗っ取られれば、すべての secrets が露出する
  3. 過去の実行ログに機密情報が漏れている可能性がある——いま修正しても、過去の実行記録には痕跡が残っているかもしれない

あなたのチェックリスト

過去に tj-actions/changed-files を使ったことがあるなら、1 項目ずつ点検することをおすすめします。

□ workflow ログを確認し、secrets が出力に漏れていないか確認する
□ 露出した可能性のあるすべての secrets(API keys、tokens など)をローテーションする
□ action をバージョン番号ではなく commit SHA にピン留めする
□ ほかのサードパーティ action の maintainer の出所を監査する
□ Dependabot や Renovate で action のバージョンチェックを自動化することを検討する

まだ乗っ取られていないプロジェクトにとって、SHA ピン留めが最も重要な対策です。SHA はバージョン番号より読みにくいものの、タグの改ざんを防げる唯一の方法です。

なお、GitHub は 2026 年のセキュリティロードマップで重要な方向性を示しています。コード貢献の権限と認証情報管理の権限を分離するというものです。将来的には、より細かいアクセス制御が導入され、サードパーティの action が全部ではなく必要な secrets だけにアクセスできるようになるかもしれません。これはよい知らせです——とはいえ、いまのところは自分で防衛線を守るしかありません。

結論

GitHub Actions Secrets 管理は、複雑な技術的問題ではなく、継続的に気を配るべきセキュリティの実践です。まとめましょう。

3 層アーキテクチャの選び方:個人プロジェクトは Repository Secrets、複数環境デプロイは Environment Secrets に切り替え、チーム共有は Organization Secrets を使う。

セキュリティ鉄則の守り方:SHA ピン留め、明示的な受け渡し、定期ローテーション——この 3 つが最も重要です。

クラウドデプロイの認証方法:OIDC が第一候補。secrets の保存も、ローテーションの手間もありません。

サプライチェーン攻撃の防ぎ方:サードパーティ action の secrets アクセスを制限し、maintainer の出所を監査する。

tj-actions 事件から教訓を得たあと、私自身がとっているやり方はこうです。すべての action を SHA にピン留めし、クラウドデプロイはすべて OIDC を使い、毎月 1 回 secrets の監査を行う。この仕組みの構築には 2 週間ほどかかりましたが、その後の維持コストはとても低いです。

まだこうした点検を始めていないなら、今日この記事のチェックリストを一通り走らせることをおすすめします。事後の後始末に比べれば、事前の予防のコストははるかに低いのです。

GitHub Actions OIDC キーレスデプロイの設定

AWS クラウドサービス向けに OIDC 認証を設定し、静的な認証情報を保存しない安全なデプロイを実現する

⏱️ 目安時間: 30 分

  1. 1

    ステップ1: IAM Identity Provider を作成する

    AWS IAM コンソールで ID プロバイダーを作成します:

    • Provider URL: https://token.actions.githubusercontent.com
    • Audience: sts.amazonaws.com
    • 生成された Provider ARN を記録する
  2. 2

    ステップ2: IAM Role を作成し信頼ポリシーを設定する

    IAM Role を作成し、信頼ポリシーを自分の GitHub リポジトリに限定します:

    ```json
    {
    "Version": "2012-10-17",
    "Statement": [{
    "Effect": "Allow",
    "Principal": {
    "Federated": "arn:aws:iam::ACCOUNT_ID:oidc-provider/token.actions.githubusercontent.com"
    },
    "Action": "sts:AssumeRoleWithWebIdentity",
    "Condition": {
    "StringEquals": {
    "token.actions.githubusercontent.com:aud": "sts.amazonaws.com",
    "token.actions.githubusercontent.com:sub": "repo:OWNER/REPO:ref:refs/heads/main"
    }
    }
    }]
    }
    ```

    • ACCOUNT_ID を自分の AWS アカウント ID に置き換える
    • OWNER/REPO を自分の GitHub リポジトリに置き換える
    • :ref:refs/heads/main を削除すればすべてのブランチを許可できる
  3. 3

    ステップ3: Workflow で OIDC を使うよう設定する

    GitHub Actions workflow で OIDC token をリクエストします:

    ```yaml
    jobs:
    deploy:
    runs-on: ubuntu-latest
    permissions:
    id-token: write # 必須:OIDC token をリクエスト
    contents: read
    steps:
    - uses: aws-actions/configure-aws-credentials@v4
    with:
    role-to-assume: arn:aws:iam::ACCOUNT_ID:role/GitHubActionsRole
    aws-region: us-east-1
    - run: aws s3 sync ./dist s3://my-bucket
    ```

    • id-token: write は必須の権限宣言
    • secrets.AWS_ACCESS_KEY_ID の設定は不要
  4. 4

    ステップ4: テストと検証

    workflow を実行して OIDC 設定を検証します:

    • workflow ログを確認し、認証が成功したことを確認する
    • role-to-assume が正しいか検証する
    • 静的な認証情報なしで AWS リソースにアクセスできることを確認する
    • 対象の操作(S3 同期、ECR プッシュなど)をテストする

FAQ

Repository Secrets と Environment Secrets の違いは?
Repository Secrets はリポジトリ単位で保存され、すべての workflow からアクセスできます。Environment Secrets は環境ごとに分離され、その環境を参照する job だけが対応する secrets にアクセスでき、承認フローにも対応します。複数環境デプロイのときは Environment Secrets がおすすめです。
workflow で secrets を安全に使うには?
3 つの原則を守りましょう:

• 明示的に渡す:env フィールドで注入し、${{ secrets.XXX }} をインラインで使わない
• 最小権限:必要な step にだけ渡し、GITHUB_TOKEN を使うときは permissions: contents: read を設定する
• 定期ローテーション:30〜90 日に 1 回の交換がおすすめ。クラウドデプロイは OIDC でローテーションを完全にスキップできる
OIDC とは何か?なぜ従来の secrets より安全なのか?
OIDC(OpenID Connect)は、GitHub が ID プロバイダーとしてクラウドに workflow の身元を証明し、クラウドが短期間の JWT トークンを発行する仕組みです。メリットは、静的な認証情報を保存しない、ローテーション不要、漏洩リスクがないこと。認証遅延も従来の secrets 方式より約 87% 削減されます。
サプライチェーン攻撃(tj-actions 事件など)を防ぐには?
中心となる対策はこちらです:

• Actions を commit SHA にピン留めし、バージョン番号タグを使わない
• secrets を信頼できる actions にだけ渡すよう制限する
• サードパーティ action の maintainer の出所を監査する
• Dependabot や Renovate を定期実行してバージョン更新を確認する
secrets はログに漏れることがある?
GitHub はログ内の ${{ secrets.XXX }} を自動的に *** に置き換えます。ただし環境変数を直接使う(echo ${MY_TOKEN} など)と、ログに本物の値が表示されます。workflow のログをテストして、意図しない露出がないか確認することをおすすめします。
Organization Secrets はどんなシナリオに向いている?
チームの複数リポジトリで認証情報を共有するシナリオに向いています。組織レベルで一度設定すれば、すべてのリポジトリで使えます。アクセス範囲も制御でき、すべてのリポジトリにするか、指定したリポジトリのリストに限定できます。たとえば複数プロジェクトで AWS_ACCESS_KEY を共有するとき、Organization Secrets を使えば各リポジトリで重複設定せずに済みます。

5分で読めます · 公開日: 2026年4月18日 · 更新日: 2026年6月8日

関連記事

コメント

GitHubアカウントでログインしてコメントできます