GitHub

GitHub Actionsのpermissions|GITHUB_TOKENの既定権限と最小権限の設計

GitHub Actionsのpermissions|GITHUB_TOKENの既定権限と最小権限の設計

GitHub Actionsのpermissionsは、ジョブに配られるGITHUB_TOKENがリポジトリのどこまで触れるかを、ワークフローのYAML側から絞り込むためのキーです。書かなければ組織やリポジトリの設定に従った既定値がそのまま渡り、書けばその1か所で境界が決まります。ワークフローの本数が増えるほど、この1行の有無が事故の広がり方を左右しました。

ワークフローの基本構造やイベントの選び方には立ち入りません。全体像はGitHub Actionsとは?できることとCI/CD自動化の解説、起動条件の設計はGitHub Actionsのトリガー(on)の設計を参照してください。本記事はGITHUB_TOKENへ与える権限の決め方だけを扱います。仕様と数値は2026年8月時点の公式ドキュメントで実測しました。

まとめ:permissionsで最初に決める三つの分岐

先に結論から書きます。第一の分岐は、そのリポジトリの既定権限がどちらになっているかです。2023年2月2日以降に作られたenterpriseとorganizationは読み取り専用が既定ですが、それ以前から動いている組織は読み書きのまま残っています。ここを確かめずに個別のワークフローだけ絞っても、書き忘れた1本が広い権限で走り続けました。

第二の分岐は、permissionsをワークフロー直下に置くか、ジョブ直下に置くかという配置です。どちらに書いても、明示しなかったスコープはすべてnoneへ落ちます。この挙動を知らないまま1スコープだけ足すと、それまで動いていた別の処理が権限不足で止まります。逆に言えば、この仕様は最小権限を作るうえで使いやすい性質でもありました。

第三の分岐は、そもそもGITHUB_TOKENで足りるのかという点です。別のリポジトリへ触る、組織を横断する、あるいは自分が起こしたpushで次のワークフローを回したい。この三つはいずれもGITHUB_TOKENの設計上の範囲外で、細粒度の個人アクセストークンかGitHub Appのインストールトークンへ差し替える判断になります。

GITHUB_TOKENの正体|ジョブごとに払い出される一時トークンの寿命と範囲

GITHUB_TOKENは、ジョブが始まるたびにGitHubが自動で作るGitHub Appのインストールアクセストークンです。人のアカウントにも組織のシークレットにも紐づいておらず、権限の適用範囲はそのワークフローを含むリポジトリ1つに限定されています。secrets.GITHUB_TOKENとgithub.tokenはどちらも同じ値です。

トークンの有効期限は最大6時間|ジョブ終了と同時に無効化される

トークンはジョブが終わった時点で無効化されます。GitHubホステッドランナーではジョブの実行時間そのものが最長6時間なので、トークンの寿命も最長で6時間です。セルフホストランナーではジョブが最長5日まで走りますが、その場合でもトークンの更新は24時間までに制限されています。長時間ジョブでAPIを叩き続けるなら、この境界を先に確かめてください。

ログ上のトークン値は伏せ字へ置換されますが、Base64へ包めばその保護は効きません。actions/checkoutとは|v7の既定変更と主要入力で扱っているpersist-credentialsは既定がtrueで、取得後もジョブの中に資格情報が残る設計です。取得しかしないジョブなら、ここをfalseにするだけで持ち出せる面が1つ減ります。

既定の権限が読み取り専用かどうかを組織とリポジトリの設定で確かめる

GitHubは2023年2月2日の変更で、新しく作られるenterpriseとorganizationの既定を「Read repository contents and packages permissions」へ切り替えました。既存のenterprise・organization・リポジトリは対象外だったため、その前から運用されている組織は今も読み書きが既定のまま動いている可能性があります。

確認する場所は、リポジトリなら Settings の Actions の General にある Workflow permissions、組織なら組織側の同じ画面です。組織で選んだ値がリポジトリの上限になり、enterpriseの配下に新設されたorganizationは親の設定を引き継ぎます。受託案件では、まずこの画面で現状を控えるところから始めています。

permissionsキーの書き方|スコープ一覧と指定漏れがnoneへ落ちる仕様

permissionsはマップで書き、キーにスコープ名、値にread・write・noneのいずれかを取ります。writeはreadを含むため、書き込みが要るスコープに読み取りを併記する必要はありません。

permissionsで指定できるスコープ一覧と読み書きの三つの値

2026年8月時点で指定できるスコープは16種です。actions、artifact-metadata、attestations、checks、code-quality、contents、deployments、discussions、id-token、issues、packages、pages、pull-requests、security-events、statuses、vulnerability-alertsが並びます。数年前の記事を写した設定ではartifact-metadataとcode-qualityが抜け落ちます。

値には例外が二つあります。id-tokenはwriteかnoneしか取らず、OpenID Connectで外部クラウドへ接続するときにwriteが要ります。vulnerability-alertsは逆にreadかnoneだけで、書き込みは指定できません。

明示したスコープ以外はすべてnoneへ落ちるという挙動と注意点

公式ドキュメントは「If you specify the access for any of these permissions, all of those that are not specified are set to none.」と書いています。つまりpermissionsを1行でも書いた瞬間、書かなかったスコープは既定値ではなくnoneになります。

permissions:
  contents: read
  pull-requests: write

この例ではcontentsとpull-requests以外の14スコープがnoneです。PRへコメントを書くためにpull-requests: writeだけ足したところ、同じジョブのチェック更新が止まる相談を受けました。原因はたいていchecksやstatusesの書き漏れで、足りない分を明示すれば戻ります。

read-all・write-all・空マップという三つの短縮記法

スコープを1つずつ書かずに済ませる記法も三つ用意されています。permissions: read-allは全スコープに読み取りを、permissions: write-allは全スコープに書き込みを与える書き方です。permissions: {}は全スコープを無効にする書き方で、外部の情報を取ってくるだけのジョブや、シークレット経由で別サービスだけを叩くジョブに向いています。

jobs:
  lint:
    runs-on: ubuntu-latest
    permissions: {}
    steps:
      - uses: actions/checkout@v5
        with:
          persist-credentials: false

ただしpermissions: {}を置くと、プライベートリポジトリでは取得の工程そのものが失敗します。自分のコードを取ってくるにはcontents: readが要るため、空マップではなくcontentsだけを許す形が最小になります。

ワークフロー全体とジョブ単位で書いたときの効き方と上書き順序

トップレベルに書いたpermissionsはそのワークフローの全ジョブに適用され、ジョブ直下に書いた値がそれを上書きします。ジョブごとに必要な権限が違う構成では、トップレベルでcontents: readまで落とし、書き込みが要るジョブだけ個別に足す形が扱いやすくなりました。

permissions:
  contents: read

jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v5

  release:
    needs: test
    runs-on: ubuntu-latest
    permissions:
      contents: write
    steps:
      - run: gh release create "v1.0.0"
        env:
          GH_TOKEN: ${{ github.token }}

ここで注意したいのは、ジョブ直下に書いた側でも「書かなかったスコープはnone」の規則がそのまま働く点です。上のreleaseジョブはトップレベルのcontents: readを引き継いでいるのではなく、contents: writeだけを持ち他はnoneという状態になります。呼び出し先の再利用ワークフローへどう伝わるかは、Reusable Workflowsとは?workflow_callでの作成と呼び出しの入出力設計と併せて確かめてください。

用途別に必要なスコープを引き当てる手順と段階的な絞り込みの進め方

スコープ名とAPIの対応は覚えるものではなく、引くものです。実務で頻出する組み合わせを先に手元へ置いておくと、絞り込みの往復が減ります。

実務でよくあるジョブと必要になるスコープの対応を一覧表で示す

ジョブの目的 必要なスコープ
ビルドとテストだけ contents: read
タグ付与とリリース作成 contents: write
PRへコメントやラベル pull-requests: write
Issueの自動起票 issues: write
ghcr.ioへイメージ公開 packages: write
コード検査結果の送信 security-events: write
OIDCで外部クラウド接続 id-token: write
GitHub Pagesへ反映 pages と id-token に write
成果物の来歴を署名 attestations: write
他のワークフロー再実行 actions: write
デプロイ状態の更新 deployments: write

OpenID Connectで外部クラウドへ繋ぐ場合、id-token: writeは署名済みトークンを要求する権限であってクラウド側の権限ではありません。どのロールを引き受けられるかは接続先の信頼ポリシーで決まり、そこには環境名も差し込めます。環境単位で権限差を作る設計はGitHub ActionsのEnvironmentsと承認ゲートにまとめました。

既定をwrite-allにせず実測しながら絞り込む三段階の進め方

最初から正解のスコープ表を書き切ろうとすると手が止まります。三段階で寄せていく進め方が実際には速く終わりました。

第一段階は、トップレベルにcontents: readだけを置いて全ワークフローを一度流すことです。ここで落ちたジョブが、追加の権限を要求している箇所そのものになります。第二段階は、落ちたジョブのログからAPIの応答コードを拾い、対応するスコープをジョブ直下へ1つずつ足す作業です。403が返っていればスコープ不足、404が返っていれば対象が見えていないという切り分けになり、後者もほとんどは読み取り権限の欠落でした。

第三段階は、足した権限をジョブ単位へ降ろして、トップレベルを最小のまま固定することです。ここまで済んだら、YAMLの静的解析を挟んで崩れを防ぎます。actionlintとは?ワークフローを静的解析するLintツールのルールはスコープ名の綴りミスも拾うので、レビューの前段に置くと差し戻しが減りました。GitHub Actionsの定期実行(cron)の実務で触れている失敗通知の仕組みは、権限不足の検知にも使えます。

GITHUB_TOKENで足りない場面|PATとGitHub Appトークンの選び分け

スコープを足しても解決しない要求が二つあります。ここを取り違えると、いつまでもpermissionsを書き換え続けることになりました。

GITHUB_TOKENでのpushが次のワークフローを起動しない制約

公式ドキュメントは、リポジトリのGITHUB_TOKENを使ってpushしても、pushイベントで動くワークフローは新たに走らないと明記しています。無限ループを避けるための仕組みで、contents: writeを与えても挙動は変わりません。生成物をコミットして次のパイプラインへ渡す構成は、この時点で成立しなくなります。

例外もあります。workflow_dispatchとrepository_dispatchは常にワークフロー実行を作るため、後続を明示的に呼ぶ設計へ寄せれば回避できました。GitHub Actionsの手動実行(workflow_dispatch)とAPI起動がその入口です。もう一つ、GITHUB_TOKENが作成・更新したPRのpull_requestイベントは、opened・synchronize・reopenedに限って承認待ちで実行が作られます。

他のリポジトリや組織横断の操作でトークンを差し替える判断基準

二つ目は範囲の問題です。GITHUB_TOKENはワークフローを含むリポジトリに限定されたインストールトークンなので、隣のリポジトリのIssueを立てる、共通のマニフェストリポジトリを更新するといった操作には届きません。

細粒度PATとGitHub Appトークンの寿命と権限の違い

差し替え先の候補は二つです。細粒度の個人アクセストークンは発行が手軽な反面、発行した個人に紐づくため、担当者の異動や退職でパイプラインが止まります。有効期限と権限の粒度はGitHub PATとは?classic/fine-grainedの違いに整理しました。

もう一方のGitHub Appは、インストール先に紐づくため個人から独立します。ワークフローの中でインストールアクセストークンを都度発行する形にすれば、長期の秘密情報をリポジトリへ置かずに済みます。発行されたトークンは1時間で失効するため、漏れたときの影響も短く抑えられました。

jobs:
  sync:
    runs-on: ubuntu-latest
    permissions: {}
    steps:
      - uses: actions/create-github-app-token@v3
        id: app-token
        with:
          client-id: ${{ vars.APP_CLIENT_ID }}
          private-key: ${{ secrets.APP_PRIVATE_KEY }}
          owner: ${{ github.repository_owner }}
      - run: gh issue create --repo "other-org/other-repo" --title "sync"
        env:
          GH_TOKEN: ${{ steps.app-token.outputs.token }}

このcreate-github-app-tokenは2026年3月のv3系でNode 24へ移り、5月のリリースでenterprise向けAppにも対応しました。入力はclient-idが第一級で、従来のapp-idは非推奨のフォールバックへ変わっています。なおvarsとsecretsの使い分けはGitHub Actionsの環境変数の使い分けで扱っています。

fork PRでの権限の縮退とサプライチェーン攻撃への備え方

公開リポジトリで外部PRを受けると、権限の話は一段複雑になります。誰が書いたか分からないコードが、こちらのランナーで走るためです。

fork由来のPRでは書き込み権限が読み取りへ落とされる仕組み

fork元のリポジトリから届いたPRでは、pull_request_target以外のPRイベントで、かつ Send write tokens to workflows from pull requests の設定が選ばれていない場合、書き込み権限はすべて読み取りへ落とされます。公式ドキュメントもfork向けには読み取りの増減しかできず書き込みは通常与えられないと書いており、YAML側でwriteを並べても通りません。

同じ扱いはDependabotが作るPRにも及びます。Dependabotのワークフロー実行はfork由来として動くため、読み取り専用のGITHUB_TOKENになります。依存更新を自動マージしたい要件は、この制約を前提にAppトークンなどへ差し替える設計が必要でした。

pull_request_targetを使うときに増える権限のリスク

pull_request_targetは、fork由来であっても読み書きの権限が付き、シークレットも参照できるイベントです。ワークフローの定義はPRの中身ではなくベースブランチ側が使われるため、一見すると安全に見えます。危ないのはその先で、PRのブランチを明示的に取得してビルドやテストを走らせた瞬間、外部が書いたコードが強い権限のトークンとシークレットを持つジョブの中で実行されます。

このイベントを使うなら、ラベル付けやコメントのようにPRの中身を実行しない処理へ限定するのが実務上の線引きです。どうしてもビルドが要るなら、取得と実行は権限のないジョブへ分け、結果の書き戻しだけをpull_request_target側で行う二段構えにします。イベントごとの性質は起動条件の記事で整理しました。

permissionsだけでは塞げない範囲とSHA固定・静的解析

permissionsが絞るのは、あくまでそのジョブへ配られるトークンの権限です。ジョブの中で動くサードパーティ製actionは、同じ環境変数とファイルシステムを共有します。つまり参照しているactionが差し替えられた場合、絞ったトークンの範囲内であれば自由に使われる余地が残ります。

ここを詰めるには、参照先の版を動かない値へ固定する対策を重ねます。SHA Pinningとは?コミットSHAで固定する方法による版の固定と、静的解析による書式の検査、そしてpersist-credentialsをfalseにする資格情報の非保持。この三つとpermissionsを合わせて、はじめて乗っ取られても持ち出せるものが少ない状態になりました。

受託開発の現場でpermissionsをどう決めるか|採用条件と見送る場面

ここからは判断の話です。すべてのリポジトリでpermissionsを1行ずつ書き切る必要はないと考えています。

permissionsを最小へ寄せる採用条件を三つの前提で示す

明示的に書き切るべきなのは、次の三つのどれかに当てはまる場合です。第一に、外部からのPRを受け付けるリポジトリであること。第二に、パッケージ公開やリリース作成など、外へ出ていく成果物をワークフローが自動で作っていること。第三に、ワークフローが5本を超えて、誰がどれを直したか追いにくくなっていること。

この三つのいずれかを満たすなら、トップレベルにcontents: readを置き、書き込みが要るジョブだけ個別に足す形へ寄せてください。引き渡し後に第三者の目で確かめたい場合は、脆弱性診断・セキュリティ診断のように、権限設計とワークフロー定義まで含めて範囲に入れる形が実務的でした。

write-allのまま運用してよい場面と、そのときの代替策

見送ってよい場面もはっきりしています。単一のプライベートリポジトリで、外部からのPRを受けず、ワークフローがビルドとテストだけで、しかも組織の既定がすでに読み取り専用になっている。この条件が揃っているなら、ワークフローごとにpermissionsを書く効果はほとんどありません。組織設定を読み取り専用にする一手で足ります。

問題は、既定が読み書きのまま残っている古い組織で、なおかつ個別のYAMLも書かれていないケースです。ここではワークフローを1本ずつ直すより、組織設定を読み取り専用へ切り替えて壊れたものだけ足し直すほうが早く終わりました。CI/CD全体をどこまで自動へ寄せるかという前提はCI/CDとは?導入すべき企業の判断基準で整理しています。

よくある質問

permissionsを書いていないワークフローはどの権限で動きますか?

組織またはリポジトリの Workflow permissions で選ばれている既定値がそのまま渡ります。2023年2月2日以降に新設されたenterpriseとorganizationは読み取り専用が既定ですが、それ以前から存在する組織は変更の対象外だったため読み書きのまま残っていることがあります。

空マップのpermissionsを書くと取得の工程も失敗しますか?

プライベートリポジトリでは失敗します。自分のリポジトリを取ってくるにはcontents: readが要るためです。外部のリソースしか触らないジョブでなければ、空マップではなくcontents: readだけを許す形が現実的な最小になります。

fork元からのPRでシークレットは使えますか?

使えません。pull_request_target以外のPRイベントでは、fork由来の実行にシークレットは渡らず、書き込み権限も読み取りへ落とされます。外部PRのCIは、シークレットなしで完結するビルドとテストに範囲を限定して組んでください。

組織の既定を読み取り専用へ変えると既存のワークフローは壊れますか?

書き込みを使っていたワークフローは止まります。ただし止まり方は403などの明確な失敗なので、切り替えてから落ちたものへpermissionsを足す進め方が結果的に短く済みました。事前に洗い出すなら、リリース作成・パッケージ公開・PR操作を含むものを先に確認してください。

GITHUB_TOKENと細粒度PATはどちらを選ぶべきですか?

同一リポジトリで完結する処理ならGITHUB_TOKENが第一候補です。別リポジトリへ触る、組織を横断する、pushで後続のワークフローを起こしたい、のいずれかに当たるときだけ差し替えます。差し替え先は個人に紐づかないGitHub Appのトークンを優先しました。

関連記事

お気に入りに入れた記事の一覧

この記事は以下の記事からリンクされています

ほか 11 件の記事からもリンクされています。

資料請求

今日のトレンド記事 直近 24 時間で、いつもより多く読まれている記事

  1. 2026.10.09 テックブログ IDCFクラウド(IDCフロンティア)不正アクセス・ランサムウェア:影響先・復旧・データは戻るか
  2. 2026.10.08 テックブログ 大阪公立大学のランサムウェア被害と仮想化基盤の停止|全授業休講に至った経緯とバックアップを守る設定
  3. 2024.11.08 テックブログ OpenAPI GeneratorでJavaコードを自動生成する方法|CLI導入からSpring・ライブラリ選択まで
  4. 2026.10.09 テックブログ ニッスイのサイバー攻撃で日水物流の入出荷停止|委託先クラウド障害に荷主が備える手順
  5. 2026.10.09 テックブログ 京王電鉄のランサムウェア被害とグループ共通基盤:決済・ポイント・予約が止まった範囲と遮断の初動

RELATED POSTS 関連記事

目次