Permissions и GITHUB_TOKEN
Что такое автоматически выдаваемый `GITHUB_TOKEN`, где задаются `permissions`, как работают права на уровне workflow/job и почему минимальные permissions должны быть нормой.
Permissions и GITHUB_TOKEN
GITHUB_TOKEN — это автоматически выдаваемый токен, с которым workflow может обращаться к GitHub API, читать репозиторий, писать checks/statuses, создавать issues, публиковать packages, делать deployment-записи и выполнять другие действия от имени GitHub Actions. Его не нужно создавать вручную в настройках secrets: GitHub сам создаёт уникальный token перед началом каждого job и делает его доступным как ${{ secrets.GITHUB_TOKEN }} и через github.token context.
Важная деталь: token привязан к репозиторию, где запущен workflow, и живёт недолго. Для обычной автоматизации это плюс: не надо хранить long-lived personal access token ради каждого CI. Но «автоматический» не значит «безопасный по умолчанию для любой задачи». Если job запускает сторонний action, shell-скрипт или код из PR, этот код может получить доступ к тем правам, которые выдали GITHUB_TOKEN.
flowchart TD
A[Enterprise / organization / repository default] --> B[Top-level workflow permissions]
B --> C[Job-level permissions]
C --> D{Fork PR?}
D -- pull_request from fork, write tokens disabled --> E[Write scopes downgraded to read]
D -- normal run or approved privileged context --> F[Effective GITHUB_TOKEN for this job]
E --> F
F --> G[Steps, run scripts, actions, GitHub API calls]Где задаются permissions
Права задаются ключом permissions. Его можно поставить на верхнем уровне workflow — тогда настройка применяется ко всем jobs — или внутри конкретного job. В Workflow: файл, запуск, jobs и steps это тот же YAML-слой, где живут on, jobs, runs-on и steps; в Contexts и expressions токен появлялся как часть secrets и github contexts.
Минимальный read-only CI обычно выглядит так:
name: CI
on:
pull_request:
push:
branches: [main]
permissions:
contents: read
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: npm ci
- run: npm testЗдесь workflow может читать содержимое репозитория, что обычно нужно actions/checkout, но не может писать issues, packages, deployments или pull request comments. Это хорошая базовая позиция: CI проверяет код, а не получает лишние write-права.
Если отдельный job должен писать комментарий в PR или обновлять issue, права лучше расширять только для него:
name: PR feedback
on:
pull_request:
types: [opened, synchronize, reopened]
permissions:
contents: read
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: npm test
comment:
needs: test
runs-on: ubuntu-latest
permissions:
contents: read
pull-requests: write
steps:
- name: Comment on PR
run: gh pr comment "$PR_URL" --body "Tests finished."
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
PR_URL: ${{ github.event.pull_request.html_url }}Обратите внимание на подводный камень: если вы перечислили хотя бы один permission, все неперечисленные permission становятся none. Поэтому job-level блок permissions: { pull-requests: write } не наследует автоматически contents: read из верхнего уровня как «добавку» в бытовом смысле; для этого job лучше явно написать оба нужных scope.
# Недостаточно для job, которому нужны оба scope:
permissions:
pull-requests: write
# Явно перечисляйте полный набор для этого job:
permissions:
contents: read
pull-requests: writeУровни доступа и полезные scopes
Для большинства permissions доступны read, write и none; write включает read. Есть исключения: например, id-token бывает write или none, потому что он не «читает репозиторий», а разрешает job запросить OIDC token. Именно id-token: write понадобится в Cloud deploy через OIDC и OIDC и секреты облаков, но сам по себе он ещё не даёт доступ в AWS, Azure или GCP: cloud provider должен доверять claims из GitHub.
Частые permissions в реальных workflow:
contents: read— checkout и чтение кода.contents: write— push commit/tag, обновление файлов через API, релизные коммиты.issues: write— создание или обновление issues.pull-requests: write— комментарии, labels и изменения PR через API.checks: writeилиstatuses: write— публикация checks/statuses, если action делает это напрямую.packages: write— публикация пакетов и container images, см. Packages и container images.deployments: write— создание deployment records, близко к Deployment workflows.security-events: write— загрузка security scan results, например SARIF.
Синтаксис read-all и write-all существует, но в production workflow write-all почти всегда слишком широкий. permissions: {} отключает все permissions для GITHUB_TOKEN; это уместно для job, который только считает что-то локально и не должен обращаться к GitHub API.
jobs:
lint-generated-docs:
runs-on: ubuntu-latest
permissions: {}
steps:
- run: echo "No repository token is available to this job"Как GitHub вычисляет итоговые права
Итоговый набор прав не берётся только из YAML. GitHub сначала смотрит default setting на уровне enterprise, organization или repository. Затем применяет permissions на уровне workflow. Потом — jobs.<job_id>.permissions для конкретного job. После этого для PR из fork GitHub обычно понижает write-права до read-only, если администратор явно не включил отправку write tokens в workflows из pull requests.
Отдельно стоит помнить про pull_request_target: при этом событии workflow выполняется в контексте base repository и может получить read/write repository permissions даже для PR из public fork. Это не «удобная версия pull_request», а privileged trigger. Если такой workflow делает checkout кода из untrusted PR и затем запускает скрипты, вы можете отдать write-token чужому коду. Полная модель угроз относится к pull_request_target и untrusted PR и Secure use и threat model.
Почему GITHUB_TOKEN не всегда запускает новые workflows
Есть ещё одно поведение, которое часто удивляет в release automation. Если workflow с помощью GITHUB_TOKEN делает push, создаёт tag или меняет репозиторий, большинство событий от этих действий не создают новый workflow run. Это защита от бесконечной рекурсии: workflow пушит commit, push запускает workflow, тот снова пушит commit, и так далее.
Исключения есть: workflow_dispatch и repository_dispatch могут запускать workflows, а PR, созданный или обновлённый через GITHUB_TOKEN, может поставить pull_request runs в состояние, где нужен approval от пользователя с write access. Если вам осознанно нужен каскад workflow-to-workflow, обычно используют GitHub App installation token или PAT с узкими правами, но это уже отдельное security-решение, а не способ «просто заставить push сработать».
Практическая норма
Хороший workflow проектируется от действия к permission, а не наоборот. Сначала спросите: job читает код, пишет комментарий, публикует package, создаёт deployment, запрашивает OIDC token? Потом выдайте ровно эти scopes. Для сторонних actions из Marketplace actions и pinning это особенно важно: action может получить github.token даже если вы явно не передали ${{ secrets.GITHUB_TOKEN }} в with или env.
Нормальный baseline для репозитория: default GITHUB_TOKEN read-only, верхний уровень workflow contents: read, расширение прав только в jobs, которым оно реально нужно. Это не бюрократия, а простое ограничение blast radius: если test job, dependency install или сторонний action скомпрометирован, ему нечем писать в репозиторий.
See also
- Workflow: файл, запуск, jobs и steps — где
permissionsнаходится в структуре workflow. - Events и filters — почему
pull_request,pull_request_targetи fork PR дают разный security-контекст. - Jobs, dependencies и conditions — как разделять read-only jobs и privileged jobs через
needs. - Contexts и expressions — где видны
secrets.GITHUB_TOKENиgithub.token. - Variables, env и secrets — чем token отличается от обычных
env,varsи repository secrets. - Marketplace actions и pinning — почему сторонним actions нельзя давать лишние permissions.
- OIDC и секреты облаков — когда вместо cloud secrets нужен
id-token: write. - Secure use и threat model — общая модель угроз для workflows, secrets и untrusted input.
