GitHub ActionsでSecretsが使えない原因|pull_request時の安全な設計
GitHub Actionsのpull_requestでSecretsが空になる原因を切り分け、フォークPRを秘密なしで検品し、信頼後の工程と安全に分ける方法を解説します。
GitHub Actionsでsecrets.API_TOKENを設定したのに、pull request(PR=コードの取り込み依頼)では空になることがあります。最初に疑うべきなのは、登録ミスよりイベントの実行元です。フォークから届くPRでは、信頼されていないコードへ秘密を渡さないため、GitHubが意図的にActions Secretsを渡しません。
安全な解決策は制限を外すことではありません。「秘密なしのPR検品」と「信頼後の秘密あり工程」を分けることです。この2段構えにすると、外部の人が送ってきたコードにも鍵を触らせず、それでいてPRの中身はきちんと検査できます。
本記事は公式資料を確認して書いた下書きです。GitHubのActionや安全機構は変わりやすいので、コード例のバージョンや権限は、反映する直前に公式ページで確認してください。
最初に30秒で原因を切り分ける
Secretを作り直す前に、失敗したrun(実行結果)を上から見ます。順番はこうです。
- 失敗したrunの
event(きっかけ)がpushかpull_requestか actor(実行した人)が普通の人か Dependabot か- そのPRが同じリポジトリのブランチか、外部フォークか
- jobが対象の Environment(環境)を参照しているか
- Secretが空かどうか(値そのものは出さない)
大事なのは、値を画面に出さないことです。イベント名とフォークかどうかだけを表示すれば、安全に切り分けられます。
- name: 実行元を確認
run: |
echo "event=${{ github.event_name }}"
echo "actor=${{ github.actor }}"
echo "is_fork=${{ github.event.pull_request.head.repo.fork }}"
event=pull_request かつ is_fork=true なら、Actions Secretsが渡らないのは保護動作です。Secretを作り直しても解決しません。未設定のSecretを参照すると式は空文字列を返すので、「空だった」という事実だけでは登録ミスと安全制限を区別できません。だからイベントとフォーク判定を先に見ます。
Secretsが空になる原因を3群に分ける
原因は大きく3つのグループに分かれます。ごちゃ混ぜにすると直し方を間違えます。
| 原因グループ | 具体例 | 直す方向 |
|---|---|---|
| ① 登録ミス・名前違い | Secretを作っていない、綴りが違う | 名前を確認して登録し直す |
| ② スコープ不一致 | Repository・Organization・Environmentのどこに置いたかがjobと合わない | jobが対象Environmentを参照しているか確認する |
| ③ 実行元による安全制限 | 外部フォークPR、Dependabotが開始した実行 | Secretを渡さず、工程を分ける設計にする |
①と②は自分の設定で直せます。③は「制限を外す」問題ではなく「設計を変える」問題です。Secret名は参照時に大文字・小文字を区別しませんが、置き場所(スコープ)は区別します。ここを混同すると、名前は合っているのに空、という状態になります。
環境変数そのものの読み込みで詰まっている場合は、環境変数を読み込めないときの確認順も先に確認してください。
フォークPRにSecretsを渡さない理由
フォークPRでは、PRを送った人が package.json の scripts やテストコードまで書き換えられます。もしそこにデプロイ用の鍵(credential)を渡すと、npm test の名を借りて鍵を盗み出せてしまいます。これを防ぐため、GitHubはフォークPRの実行に GITHUB_TOKEN 以外のSecretsを渡しません。しかも、その GITHUB_TOKEN も読み取り専用になります。
Dependabot(依存関係を自動更新してくれる仕組み)が開始したワークフローも、同じようにActions Secretsが使えず、GITHUB_TOKEN は既定で読み取り専用です。公開リポジトリでは、最初のコントリビューターのPRは、書き込み権限を持つ人が承認するまで実行が待つ場合もあります。
つまり、npm test は「PR作成者が用意したコードの実行」です。そこへ本物の鍵を渡してはいけません。これは不便ではなく、境界(信頼の線引き)です。
pull_requestとpull_request_targetを比較する
イベントごとに「動かすコードは誰のものか」「Secretsは来るか」が違います。設計判断のために早見表にします。実際の権限はOrganizationの設定や permissions の指定でも変わるので、対象リポジトリでも確認してください。
| 実行条件 | 動かすコード | 通常のActions Secrets | GITHUB_TOKEN | 向いた用途 |
|---|---|---|---|---|
同一リポジトリの pull_request | PR側 | 条件を満たせば利用可 | permissionsで最小に | 静的解析・テスト・ビルド |
外部フォークの pull_request | PR側 | 原則利用不可 | 読み取り専用 | 秘密なしのPR検品 |
| Dependabotの実行 | 更新差分 | 利用不可 | 読み取り専用 | 依存更新の検品 |
pull_request_target | base側 | base側へ到達し得る | base側の権限 | ラベル付け等(PRコードは実行しない) |
main への push | 信頼済み | 条件を満たせば利用可 | permissionsで最小に | 統合テスト・デプロイ |
とくに大事なのは、「runの承認」と「Secretsの提供」は別だということです。メンテナーがフォークPRの実行を承認しても、それだけで通常のActions Secretsが渡るわけではありません。
pull_request_targetを迂回に使わない
pull_request_target はbaseリポジトリの既定ブランチを基準に動きます。ラベル付けやコメントのような、PRのメタデータ(付随情報)を扱う自動化には使えます。しかし、公開フォークからの実行でもbase側の GITHUB_TOKEN に読み書き権限が与えられるため、通常の pull_request より強い信頼領域です。
いちばん危険なのは、pull_request_target でPR側のcommitをcheckout(取り出し)して、その後 npm ci やテスト、ビルドを実行する形です。公式のActionである actions/checkout は、この危険をふまえ、v7では pull_request_target や workflow_run からフォークPRのコードを既定ではcheckoutしません。保護を外す allow-unsafe-pr-checkout の既定値も false です。「Secretsを使いたいからイベント名だけ変える」は、安全な解決ではありません。
SecretsなしでPRを検品する
PR段階では、外部サービスへ接続しなくてもできることを実行します。静的解析、単体テスト、ビルドです。外部APIはモック(=テスト用の代用品)へ差し替えます。
name: Pull request checks
on: [pull_request]
permissions:
contents: read
jobs:
check:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
- uses: actions/setup-node@v7
with:
node-version: 24
- run: npm ci
- run: npm test
env:
API_MODE: mock
- run: npm run build
permissions: contents: read を明示して、GITHUB_TOKEN を必要最小限にします。公式の actions/setup-node も contents: read を推奨し、強い権限や機密情報に触れるワークフローでは不要なnpmの自動キャッシュを無効にするよう案内しています。
ここで npm ci を使うのは理由があります。npm-ci はCI向けのクリーンインストールで、lockfile(依存の固定表)が必須です。package.json と食い違えばlockfileを更新せずエラーになり、既存の node_modules は開始前に消してから入れ直します。つまり、依存関係を毎回同じ状態で再現できます。
そのうえで、テストは順序や回数が変わっても同じ結果になる「決定的」な形が望ましいです。Node.js公式のモック解説は、適切なsetupとmockがそれを可能にし、外部システムもmockの候補になると説明しています。だから本物の鍵がなくても、PRでは意味のある検品ができます。ただし npm ci を使うこと自体が未信頼コードを安全にするわけではありません。scriptsやテストはPR側で書き換えられるので、鍵を渡さない前提は変えません。
記事サイト全体のCIはGitHub Actionsで記事サイトを自動検品する方法を、変更範囲の確認はAI生成コードの検品チェックリストも参考にしてください。
値を漏らさず安全に診断する
存在確認のためにSecret自体を echo してはいけません。GitHubは登録済みSecretsをログで伏せ字にしますが、変形した値まで必ず隠せるとは限りません。だから値ではなく、空かどうかだけを調べます。
env:
DEPLOY_TOKEN: ${{ secrets.DEPLOY_TOKEN }}
steps:
- name: Secretの有無だけ確認
run: |
if [ -z "$DEPLOY_TOKEN" ]; then
echo "::error::DEPLOY_TOKEN is unavailable"
exit 1
fi
echo "DEPLOY_TOKEN is available"
Secretsは if: 条件へ直接書けないので、環境変数へ渡してから判定します。
もう1つ注意があります。PRのタイトル、本文、ブランチ名などは、外部の人が自由に書ける値です。${{ github.event.pull_request.title }} のような値を run: へ直接埋め込むと、その文字列がコマンドとして実行される危険(スクリプト注入)があります。専用Actionへ入力として渡すか、いったん環境変数を介して、コードとして解釈させない設計にします。
信頼後に実行する工程を分ける
デプロイやクラウド上の統合テストなど、Secretが要る工程はPR検品から切り離します。PRでは秘密なしのチェックを必須にし、レビューとマージ後、保護された main への push で秘密ありジョブを動かします。
on:
push:
branches: [main]
permissions:
contents: read
jobs:
deploy:
runs-on: ubuntu-latest
environment: production
steps:
- uses: actions/checkout@v7
- run: ./scripts/deploy.sh
env:
DEPLOY_TOKEN: ${{ secrets.DEPLOY_TOKEN }}
こうすると、フォークPRは秘密なしで検品され、レビューを通って main へ入った版だけがデプロイ資格を持ちます。
長期の鍵を保存したくない場合は、OIDC(=実行時にその場で短期の身分証を受け取る仕組み)も検討できます。ただし「認証も権限設定も不要」という意味ではありません。GitHub側では id-token: write はOIDCトークンの取得を許すだけで、それだけでクラウド資源への書き込み権限が付くわけではありません。クラウド側でも条件を絞る必要があります。たとえばAWSのOIDC用ロール作成の案内は、sub 条件でGitHubのorganization・repository・branchを限定するよう勧めています。絞らないと、自分の管理外のActionsからロールを引き受けられてしまうからです。
よくある質問
同じワークフローなのに、pushでは成功してフォークPRでは失敗するのはなぜですか?
実行するコードの信頼度が違うためです。外部フォークのPRでは、変更されたコードから鍵を読み取られないよう、通常のActions Secretsが渡されず、GITHUB_TOKEN も読み取り専用になります。Secretの再登録より先に、イベント名と github.event.pull_request.head.repo.fork を確認します。
フォークPRのワークフローを承認すればSecretsも使えますか?
いいえ。runの承認は実行を許可する操作で、通常のActions SecretsをフォークPRへ渡す操作ではありません。Secretsが要る処理は、レビューとマージ後の push など、信頼済みのイベントへ分けます。なおPrivateリポジトリの管理者設定やGitHub Enterprise Serverでは条件が異なる場合があります。
pull_request_targetへ変えればSecretsの問題を解決できますか?
イベント名を変えるだけでは安全な解決になりません。pull_request_target はbase側の強い文脈で動くため、PR側のコードをcheckoutして実行すると、base側の権限やSecretsが危険にさらされます。ラベル付けやコメントなど、PRコードを実行しない用途に限定します。
外部APIを使うテストはフォークPRでどう実行しますか?
PRではモックやローカルのテスト用サービスを使い、秘密なしで再現できる範囲を検品します。本物の外部APIを使う統合テストは、レビューとマージ後の信頼済みcommitで実行します。
GITHUB_TOKENと自分で登録したSecretは同じものですか?
異なります。GITHUB_TOKEN は各jobの開始時にGitHubが自動発行する、そのリポジトリ向けの一時的なtokenです。フォークPRでは読み取り専用になります。自分で登録したAPIキーなどのActions Secretsは、別の提供条件で管理されます。
まとめ
- Secretが空でも、まず登録ミスを疑う前に、失敗したrunの
event・actor・フォーク判定・空かどうかを、値を出さずに確認する。 - フォークPRやDependabotの実行では鍵を渡さない前提で、
permissions: contents: readの秘密なし検品(静的解析・単体テスト・ビルド、外部APIはモック)を用意する。 - 本物の鍵が要るデプロイや統合テストは、レビューとマージ後の
mainへのpushへ分離し、可能ならOIDCでクラウド側の信頼条件も絞る(Actionのバージョンや設定は反映直前に公式で確認する)。
公式一次情報
確認日: 2026-07-26
- Events that trigger workflows - GitHub Docs: フォークPRではGITHUB_TOKEN以外のSecretsが渡らず読み取り専用になること、Dependabotやpull_request_targetの扱いを説明。
- Using secrets in GitHub Actions - GitHub Docs: 未設定Secretは空文字列を返すこと、if条件へ直接書けず環境変数を介して判定することを案内。
- Secure use reference - GitHub Docs: 変形したSecretは自動伏せ字が保証されないこと、GITHUB_TOKENを最小権限にすべきことを説明。
- Script injections - GitHub Docs: PRのタイトルや本文などをrunへ直接展開するとコマンド注入につながり得る具体例を提示。
- OpenID Connect reference - GitHub Docs: id-token: writeはOIDCトークン取得の許可であり書き込み権限そのものではないこと、sub claimで信頼条件を絞れることを説明。
- actions/checkout - GitHub: v7の使用例と、pull_request_target等でフォークPRコードを既定でcheckoutしない安全既定を掲載。
- actions/setup-node - GitHub: v7・Node.js・npm ciの基本例と、contents: read推奨および不要なキャッシュ無効化の案内を掲載。
- npm-ci | npm Docs: npm ciがlockfile必須のクリーンインストールで、不一致時はエラーになり既存node_modulesを削除することを説明。
- Mocking in tests | Node.js Learn: テストは決定的であるべきで、外部システムをmockに置き換えられることを説明。
- Create a role for OpenID Connect federation - AWS IAM: GitHub OIDC用IAMロールのsub条件をorganization・repository・branchで絞るよう推奨。
関連記事
続き(結論と実データ)はnoteに置いています
ここでは手順のところまで書きました。実際に出た数字、うまくいかなかった条件、そのまま使える設定ファイルは、 note の記事にまとめてあります。