GitHub Actionsのワークフロー内でGitHub APIが403 Forbiddenになる場合は、まず対象APIに必要な権限を特定し、そのAPIを呼び出すジョブのpermissionsへ必要最小限の権限を追加します。Issueの作成やコメントならissues: write、プルリクエストの作成ならpull-requests: writeというように、操作対象ごとに権限を分けるのが基本です。
また、権限を与えただけではAPIコマンドが自動的にトークンを使用するとは限りません。GitHub CLIにはGH_TOKEN、curlにはAuthorizationヘッダーなどを通じて、組み込みのGITHUB_TOKENを渡します。
ただし、GITHUB_TOKENがアクセスできるのは、原則としてワークフローを実行しているリポジトリ内です。別リポジトリや組織全体のリソースを操作する場合は、GitHub Appのインストールアクセストークンや、対象を限定したPersonal Access Tokenを検討する必要があります。([GitHub Docs][1])
GitHub ActionsのAPI操作が403になる主な原因
GitHub ActionsでAPI操作が403になる原因は、単純な「トークンの設定漏れ」だけではありません。主に次の原因が考えられます。
| 原因 | 典型的な状況 | 主な対処 |
|---|---|---|
| APIに必要な権限がない | Issue作成時にissues: writeがない | 対象ジョブのpermissionsへ追加 |
| コマンドにトークンを渡していない | ghやcurlが意図した認証情報を使っていない | GH_TOKENやAuthorizationヘッダーを設定 |
| 対象が別リポジトリ | owner/repositoryが実行元リポジトリと異なる | GitHub Appまたは適切なPATを使用 |
| Forkからのプルリクエスト | 通常の実行では成功するが、外部PRで失敗する | 書き込み処理を信頼済みイベントへ分離 |
| Dependabotによる実行 | DependabotのPRだけ書き込みに失敗する | 読み取り用と書き込み用の処理を分離 |
| 組織・Enterpriseのポリシー | YAMLを直しても権限が付与されない | 管理者側のActions設定を確認 |
| APIレート制限 | レスポンスにrate limit関連のメッセージがある | 再試行間隔や呼び出し回数を見直す |
GITHUB_TOKENは各ジョブの開始時に生成されるGitHub Appのインストールアクセストークンです。トークンの存在と、そのトークンに与えられている権限は別の問題として切り分ける必要があります。([GitHub Docs][1])
どの権限が必要かはAPIエンドポイントごとに確認する
必要な権限を推測だけで決めると、権限不足が続いたり、反対に不要な書き込み権限を付けたりしやすくなります。
まず、実行している処理を次のように分解します。
- どのAPIエンドポイントを呼んでいるか
- 読み取り処理か、作成・更新・削除処理か
- Issue、プルリクエスト、リポジトリ内容、Checksなど、どのリソースを操作するか
- 実行元と操作対象が同じリポジトリか
GitHub REST APIの各エンドポイントには、GitHub AppやFine-grained PATで必要となるリポジトリ権限が掲載されています。GITHUB_TOKENのpermissionsを決める際にも、この権限名が判断材料になります。
代表的な対応関係は次のとおりです。
| 操作 | 主に必要となる権限 |
|---|---|
| リポジトリ内のファイルを読む | contents: read |
| ファイル更新、コミット、push | contents: write |
| Issueを読む | issues: read |
| Issueの作成、更新、コメント | issues: write |
| プルリクエストを読む | pull-requests: read |
| プルリクエストの作成、更新 | pull-requests: write |
| プルリクエストをマージする | contents: write |
| Check Runを作成・更新する | checks: write |
| コミットステータスを作成する | statuses: write |
| ワークフローの再実行やキャンセルなどを行う | actions: write |
| パッケージを読む | packages: read |
| パッケージを公開する | packages: write |
たとえば、Issue作成にはissues: write、プルリクエスト作成にはpull-requests: write、Check Run作成にはchecks: writeが必要です。一方、プルリクエストのマージではpull-requests: writeではなく、対象エンドポイントがcontents: writeを要求します。操作名だけで決めず、使用するエンドポイントの記載を確認することが重要です。([GitHub Docs][2])
Issueとプルリクエストのコメントはエンドポイントに注意する
プルリクエストへのコメントには、会話欄へ投稿する通常のコメントと、コード差分に付けるレビューコメントがあります。
通常の会話コメントはIssues APIで扱われることがあり、エンドポイントによってissues: writeまたはpull-requests: writeのいずれかを使用できます。一方、レビューコメントはPull Requests系のAPIとして扱われます。
「プルリクエストを操作しているから必ずpull-requests: write」と判断せず、実際のAPIパスを確認してください。([GitHub Docs][3])
レスポンスヘッダーから必要な権限を確認できる
REST APIでResource not accessible by integrationが返される場合は、トークンの権限不足が疑われます。
レスポンスに含まれるX-Accepted-GitHub-Permissionsヘッダーから、そのエンドポイントが受け付ける権限の組み合わせを確認できる場合があります。たとえば、次のような値です。
X-Accepted-GitHub-Permissions: issues=write
複数の権限が必要な場合や、複数の許可パターンがある場合もあります。REST APIを直接呼んでいるなら、エラー本文だけでなくレスポンスヘッダーも確認すると、闇雲に権限を追加せずに済みます。([GitHub Docs][4])
権限はAPIを呼び出すジョブのpermissionsへ設定する
permissionsはワークフロー全体にも設定できますが、API操作を行うジョブだけに書き込み権限を与える方が安全です。
Issueを作成するジョブは、次のように記述できます。
name: Create issue
on:
workflow_dispatch:
jobs:
create-issue:
runs-on: ubuntu-latest
permissions:
contents: read
issues: write
steps:
- name: Create an issue
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
GH_REPO: ${{ github.repository }}
run: |
gh issue create \
--repo "$GH_REPO" \
--title "定期確認が必要です" \
--body "GitHub Actionsから自動作成しました。"
この設定では、create-issueジョブだけにissues: writeを付与しています。GitHub CLIのghは、GH_TOKEN環境変数に設定されたトークンを認証に使用します。組み込みのGITHUB_TOKENはGitHub側で生成されるため、この用途だけならリポジトリシークレットへ手動登録する必要はありません。([GitHub Docs][5])
issues: writeのwriteには読み取り権限も含まれます。ただし、Issue以外のリポジトリ内容を読む処理がある場合は、別途contents: readなどを明示します。([GitHub Docs][6])
curlでREST APIを呼び出す場合の設定例
GitHub CLIを使わず、curlでREST APIを直接呼ぶ場合も考え方は同じです。
name: Call GitHub API
on:
workflow_dispatch:
jobs:
create-issue:
runs-on: ubuntu-latest
permissions:
issues: write
steps:
- name: Create an issue with REST API
env:
TOKEN: ${{ secrets.GITHUB_TOKEN }}
REPOSITORY: ${{ github.repository }}
run: |
curl --fail \
--request POST \
--url "https://api.github.com/repos/$REPOSITORY/issues" \
--header "Accept: application/vnd.github+json" \
--header "Authorization: Bearer $TOKEN" \
--data '{
"title": "API操作の確認",
"body": "GitHub Actionsから作成しました。"
}'
トークンはコマンドへ直接埋め込まず、環境変数を経由してAuthorizationヘッダーへ渡します。トークンの値をechoしたり、デバッグ用の出力へ含めたりしないでください。GitHubはシークレットの利用に環境変数などを使い、機密値をログへ出さない運用を推奨しています。([GitHub Docs][5])
permissionsで特に間違えやすいポイント
1つでも権限を書くと、未指定の権限はnoneになる
permissionsでいずれかの権限を指定すると、記載していない権限は原則としてnoneになります。
たとえば、次の設定ではissues: write以外が無効になるため、同じジョブ内でリポジトリをチェックアウトする処理などが失敗する可能性があります。
permissions:
issues: write
Issue操作に加えてリポジトリ内容も読むなら、必要な読み取り権限を分けて記載します。
permissions:
contents: read
issues: write
「書き込み権限を追加したら、それまで動いていた読み取り処理が失敗した」という場合は、未指定権限がnoneになっていないか確認してください。([GitHub Docs][6])
ワークフロー全体よりジョブ単位を優先する
次のようにワークフロー直下へ設定すると、すべてのジョブへ同じ権限が適用されます。
permissions:
contents: read
issues: write
jobs:
test:
runs-on: ubuntu-latest
steps:
- run: npm test
create-issue:
runs-on: ubuntu-latest
steps:
- run: gh issue create ...
この例では、API操作を行わないtestジョブにもIssueの書き込み権限が与えられます。
書き込み権限は、必要なジョブの中へ移します。
permissions:
contents: read
jobs:
test:
runs-on: ubuntu-latest
steps:
- run: npm test
create-issue:
runs-on: ubuntu-latest
permissions:
contents: read
issues: write
steps:
- name: Create issue
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: gh issue create ...
GitHub Actionsでは、ワークフロー単位とジョブ単位の両方にpermissionsを設定できます。API更新を行うジョブだけを切り離すと、意図しない処理から書き込み権限を使われる範囲を狭められます。([GitHub Docs][5])
write-allで解決しない
次の設定を追加すれば、多くの権限不足は一時的に解消する可能性があります。
permissions: write-all
しかし、すべての利用可能な権限へ広く書き込み権限を与えるため、恒久的な解決策には適しません。
write-allで成功した場合は、「権限が原因だった」と分かるだけです。その後、対象エンドポイントに必要な権限へ絞り込んでください。最終的には、次のような設定へ戻すのが基本です。
permissions:
contents: read
issues: write
GitHubもGITHUB_TOKENには必要最小限のアクセスだけを与えることを推奨しています。([GitHub Docs][5])
tokenを明示していなくてもActionから参照されることがある
一部のActionは、ワークフロー内でtoken:を明示的に渡していなくても、github.tokenコンテキストからGITHUB_TOKENへアクセスできます。
そのため、「このActionにはトークンを渡していないから安全」とは限りません。信頼できるActionを使用するとともに、そのジョブのpermissionsを必要最小限にしてください。([GitHub Docs][5])
権限を追加しても403が直らないときの確認手順
エラー本文とレスポンスヘッダーを確認する
最初に、403のレスポンス本文を確認します。
Resource not accessible by integrationなら権限不足や対象範囲の不一致が有力です。一方、rate limitに関するメッセージなら、permissionsを増やしても解決しません。
REST APIでは、レート制限に達した場合も403または429が返ることがあります。x-ratelimit-remainingが0か、retry-afterヘッダーが付いていないかを確認してください。([GitHub Docs][4])
操作対象が実行元リポジトリと同じか確認する
GITHUB_TOKENは、ワークフローが保存されているリポジトリを対象とするトークンです。
たとえば、organization/appで動くワークフローから、次の別リポジトリを操作しようとすると、権限を追加してもアクセスできないことがあります。
organization/infrastructure
この場合は、次のいずれかを検討します。
- 対象リポジトリへインストールしたGitHub Appのアクセストークン
- 対象リポジトリと必要な権限だけを許可したFine-grained PAT
- 組織の運用ルールに基づいて管理された専用トークン
別のトークンを使用する場合は、リポジトリや組織のシークレットへ保存し、GH_TOKENなどを通じてコマンドへ渡します。
env:
GH_TOKEN: ${{ secrets.CROSS_REPO_TOKEN }}
複数リポジトリを継続的に操作する自動化では、対象リポジトリと権限をインストール単位で管理できるGitHub Appが選択肢になります。([GitHub Docs][1])
Forkからのプルリクエストか確認する
Forkされたリポジトリからのpull_requestイベントでは、通常、GITHUB_TOKENの書き込み権限が読み取りへ変更されます。YAMLへissues: writeやcontents: writeを書くだけでは、期待どおりの書き込み権限にならないことがあります。
外部から変更可能なコードへ書き込みトークンを渡すのは危険です。次のように処理を分ける方が安全です。
- プルリクエスト上ではテストや解析だけを実行する
- Issue作成、ラベル付与、リリースなどは信頼済みブランチ側のジョブで行う
- 書き込み処理は条件式で実行イベントやブランチを限定する
Fork向けの書き込みトークン設定を安易に有効化するのではなく、ワークフロー自体を読み取り処理と書き込み処理へ分離してください。([GitHub Docs][6])
Dependabotが実行したワークフローか確認する
Dependabotのプルリクエストで実行されるワークフローは、Forkからのプルリクエストと同様に扱われ、GITHUB_TOKENは読み取り専用になります。また、通常のActionsシークレットも利用できません。
通常のプルリクエストでは成功し、Dependabotによる更新だけ失敗する場合は、権限名ではなく実行元の制約を疑ってください。([GitHub Docs][6])
組織やEnterpriseの設定を確認する
GITHUB_TOKENの初期権限は、Enterprise、Organization、リポジトリの設定によって制限されます。組織側で制限された既定値が設定されている場合、リポジトリ側だけではより緩い設定へ変更できないことがあります。
管理下のリポジトリでは、次を確認します。
- OrganizationまたはリポジトリのActions設定
- Workflow permissionsの既定値
- Enterprise側で上書きされているポリシー
- GitHub Actionsによるプルリクエストの作成・承認が許可されているか
特に、pull-requests: writeを付けてもプルリクエスト作成や承認が失敗する場合は、Actionsによるプルリクエスト操作を許可する設定も確認してください。([GitHub Docs][7])
安全に403を解消するための判断基準
GitHub ActionsのAPI操作で403が発生したら、次の順序で対応すると権限を広げすぎずに解決できます。
- 実際に呼んでいるAPIエンドポイントを確認する
- エンドポイントが要求する権限名と
read・writeを特定する - APIを呼ぶジョブの
permissionsへ必要な権限だけを追加する GH_TOKENやAuthorizationヘッダーでトークンをコマンドへ渡す- 別リポジトリ、Fork、Dependabot、組織ポリシー、レート制限を確認する
Issue作成なら、まず次の形から始めます。
permissions:
contents: read
issues: write
それでも失敗する場合は、write-allへ広げるのではなく、エラー本文、X-Accepted-GitHub-Permissions、操作対象リポジトリ、ワークフローの実行イベントを順番に確認してください。
最も安全な構成は、テストやビルドを行う読み取り専用ジョブと、Issue作成やリリースを行う書き込みジョブを分離し、書き込みジョブだけに必要な権限を与える構成です。
[1]: https://docs.github.com/en/actions/concepts/security/github_token “GITHUB_TOKEN – GitHub Docs”
[2]: https://docs.github.com/en/rest/issues/issues “REST API endpoints for issues – GitHub Docs”
[3]: https://docs.github.com/en/rest/issues/comments “REST API endpoints for issue comments – GitHub Docs”
[4]: https://docs.github.com/en/rest/using-the-rest-api/troubleshooting-the-rest-api “Troubleshooting the REST API – GitHub Docs”
[5]: https://docs.github.com/en/actions/tutorials/authenticate-with-github_token “Use GITHUB_TOKEN for authentication in workflows – GitHub Docs”
[6]: https://docs.github.com/en/actions/writing-workflows/workflow-syntax-for-github-actions “Workflow syntax for GitHub Actions – GitHub Docs”
[7]: https://docs.github.com/en/organizations/managing-organization-settings/disabling-or-limiting-github-actions-for-your-organization “Disabling or limiting GitHub Actions for your organization – GitHub Docs”

コメント