GitHub ActionsのAPIが403になる原因とGITHUB_TOKEN権限の設定方法

GitHub Actionsのワークフロー内でGitHub APIが403 Forbiddenになる場合は、まず対象APIに必要な権限を特定し、そのAPIを呼び出すジョブのpermissionsへ必要最小限の権限を追加します。Issueの作成やコメントならissues: write、プルリクエストの作成ならpull-requests: writeというように、操作対象ごとに権限を分けるのが基本です。

また、権限を与えただけではAPIコマンドが自動的にトークンを使用するとは限りません。GitHub CLIにはGH_TOKENcurlには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へ追加
コマンドにトークンを渡していないghcurlが意図した認証情報を使っていないGH_TOKENAuthorizationヘッダーを設定
対象が別リポジトリowner/repositoryが実行元リポジトリと異なるGitHub Appまたは適切なPATを使用
Forkからのプルリクエスト通常の実行では成功するが、外部PRで失敗する書き込み処理を信頼済みイベントへ分離
Dependabotによる実行DependabotのPRだけ書き込みに失敗する読み取り用と書き込み用の処理を分離
組織・EnterpriseのポリシーYAMLを直しても権限が付与されない管理者側のActions設定を確認
APIレート制限レスポンスにrate limit関連のメッセージがある再試行間隔や呼び出し回数を見直す

GITHUB_TOKENは各ジョブの開始時に生成されるGitHub Appのインストールアクセストークンです。トークンの存在と、そのトークンに与えられている権限は別の問題として切り分ける必要があります。([GitHub Docs][1])

どの権限が必要かはAPIエンドポイントごとに確認する

必要な権限を推測だけで決めると、権限不足が続いたり、反対に不要な書き込み権限を付けたりしやすくなります。

まず、実行している処理を次のように分解します。

  1. どのAPIエンドポイントを呼んでいるか
  2. 読み取り処理か、作成・更新・削除処理か
  3. Issue、プルリクエスト、リポジトリ内容、Checksなど、どのリソースを操作するか
  4. 実行元と操作対象が同じリポジトリか

GitHub REST APIの各エンドポイントには、GitHub AppやFine-grained PATで必要となるリポジトリ権限が掲載されています。GITHUB_TOKENpermissionsを決める際にも、この権限名が判断材料になります。

代表的な対応関係は次のとおりです。

操作主に必要となる権限
リポジトリ内のファイルを読むcontents: read
ファイル更新、コミット、pushcontents: 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: writewriteには読み取り権限も含まれます。ただし、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-remaining0か、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: writecontents: 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が発生したら、次の順序で対応すると権限を広げすぎずに解決できます。

  1. 実際に呼んでいるAPIエンドポイントを確認する
  2. エンドポイントが要求する権限名とreadwriteを特定する
  3. APIを呼ぶジョブのpermissionsへ必要な権限だけを追加する
  4. GH_TOKENAuthorizationヘッダーでトークンをコマンドへ渡す
  5. 別リポジトリ、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”

この記事を書いた人

実務の現場で詰まりがちなポイントを地図にするITブログ「IT trip」を運営。Windows/Office(Teams・Excel)からSQL、サーバ運用、ガジェットまで、再現性のある手順と“なぜそうなるか”を丁寧に解説します。読んだらすぐ試せること、そして迷った人の次の一歩が見えることを大切にしています。

コメント

コメントする

目次