- Published on
GitHub Actions 403 권한 오류 - GITHUB_TOKEN 설정법
- Authors
- Name
- 스타차일드
- https://x.com/ETFBITX
GitHub Actions로 CI/CD를 구성하다 보면 어느 순간부터 403이 튀어나옵니다. 로그에는 보통 403: Resource not accessible by integration, permission denied, write access not granted 같은 메시지가 섞여 나오고, 문제는 토큰이 “없는 것”이 아니라 “권한이 부족한 상태로 발급된 것”인 경우가 많습니다.
특히 GitHub는 보안 강화를 위해 워크플로우에서 기본 GITHUB_TOKEN 권한을 점점 더 보수적으로 운영해 왔고, 조직/리포지토리 설정에 따라 기본값이 read로 제한되는 환경도 흔합니다. 이 글에서는 GITHUB_TOKEN이 무엇인지부터, 403이 나는 대표 시나리오, 그리고 가장 안전하고 재현 가능한 해결책(permissions 명시)을 중심으로 정리합니다.
403이 의미하는 것: 인증은 됐는데 권한이 없다
GITHUB_TOKEN은 GitHub Actions 런타임이 자동으로 주입하는 단기 토큰입니다. 즉, 워크플로우가 실행되는 동안만 유효하며, 기본적으로 “해당 리포지토리의 특정 작업”을 하기 위한 권한을 가집니다.
문제는 이 권한이 다음 요인에 의해 부족해질 수 있다는 점입니다.
- 워크플로우 상단에
permissions를 명시하지 않아 기본값이 제한됨 - 리포지토리 설정에서 워크플로우 권한이
Read repository contents permission으로 제한됨 - PR 이벤트(
pull_request)에서 포크 저장소가 소스인 경우, 토큰 권한이 더 제한됨 - GitHub Packages, Releases, Environments, Issues/PR 코멘트 등은 별도 스코프가 필요함
- 다른 리포지토리로
git push하려는 경우(크로스 레포) 기본 토큰으로는 불가하거나 제한됨
가장 흔한 에러 메시지 패턴
로그에서 아래 문구를 보면 거의 확실히 권한 문제입니다.
403: Resource not accessible by integrationremote: Permission to OWNER/REPO.git denied to github-actions[bot]Write access to repository not grantedHttpError: Resource not accessible by integrationThe requested URL returned error: 403
이때 “토큰을 다시 생성”하기보다, 먼저 워크플로우의 permissions와 리포지토리 설정을 확인해야 합니다.
1) 워크플로우에 permissions를 명시하라 (가장 확실한 해결)
GitHub Actions는 최소 권한 원칙을 권장합니다. 즉, 필요한 권한만 명시적으로 열어야 합니다.
예를 들어 릴리즈 생성, 태그 푸시, PR 코멘트 작성 등은 contents: write 또는 pull-requests: write 등이 필요합니다.
예시: 태그/커밋 푸시가 필요한 경우
아래는 버전 파일 업데이트 후 푸시하는 전형적인 상황입니다. 이때 contents: write가 없으면 403이 납니다.
name: bump-and-push
on:
workflow_dispatch:
permissions:
contents: write
jobs:
bump:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
persist-credentials: true
- name: Bump version
run: |
echo "1.2.3" > VERSION
git config user.name "github-actions[bot]"
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
git add VERSION
git commit -m "chore: bump version"
- name: Push
run: git push
핵심은 다음 두 가지입니다.
permissions에서contents: write를 명시actions/checkout에서persist-credentials: true로 토큰 기반 인증을 유지
예시: GitHub Release 생성
Release 생성은 보통 contents: write가 필요합니다.
name: release
on:
push:
tags:
- "v*"
permissions:
contents: write
jobs:
create-release:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Create GitHub Release
uses: softprops/action-gh-release@v2
with:
generate_release_notes: true
2) 리포지토리 설정에서 워크플로우 권한이 막혀 있지 않은지 확인
코드를 아무리 고쳐도, 리포지토리 설정에서 기본 권한이 강제로 제한되어 있으면 같은 문제가 반복됩니다.
확인 경로(일반적으로)는 다음과 같습니다.
- Repository
Settings Actions메뉴GeneralWorkflow permissions
여기서 다음 옵션이 관건입니다.
Read and write permissionsAllow GitHub Actions to create and approve pull requests(PR 자동 승인/생성 시)
조직 정책(Organization policy)으로 더 상위에서 잠겨 있을 수도 있으니, 조직 레벨 설정도 함께 확인해야 합니다.
3) PR from fork에서 403이 나는 구조적 이유
pull_request 이벤트로 외부 포크 PR을 빌드하면, 보안상 GITHUB_TOKEN의 쓰기 권한이 제거되거나 민감한 시크릿이 주입되지 않습니다. 이때는 다음이 전형적인 실패 패턴입니다.
- PR 빌드 중 린트/테스트는 되는데, 코멘트 작성/라벨링/아티팩트 업로드 일부가 403
- PR에서 자동으로
git push하려다 403
대응 전략
- 쓰기 작업은
pull_request가 아니라push또는workflow_dispatch에서만 수행 - 꼭 PR 컨텍스트에서 쓰기가 필요하다면
pull_request_target을 고려하되, 체크아웃 대상과 실행 스크립트를 매우 보수적으로 설계(보안 이슈 주의)
예를 들어 코멘트만 달고 싶다면, pull_request_target에서 “신뢰 가능한 코드”만 실행하고, PR의 코드는 빌드하지 않는 방식이 안전합니다.
4) 체크아웃/푸시가 꼬일 때: persist-credentials와 fetch-depth
권한을 열었는데도 푸시가 실패한다면, 인증 정보가 체크아웃 단계에서 제거되었거나, 태그/히스토리가 부족해 작업이 실패한 경우도 있습니다.
체크 포인트
persist-credentials: false로 되어 있지 않은지 확인- 태그가 필요하면
fetch-depth: 0설정
- uses: actions/checkout@v4
with:
fetch-depth: 0
persist-credentials: true
태그 기반 릴리즈, 변경 로그 생성, 이전 커밋 비교 등은 얕은 클론에서 자주 깨집니다.
5) 크로스 레포 푸시/배포는 GITHUB_TOKEN만으로 안 될 수 있다
GITHUB_TOKEN은 기본적으로 “워크플로우가 실행된 그 리포지토리” 중심으로 권한이 부여됩니다. 다음 같은 경우는 별도 인증 수단이 필요할 수 있습니다.
- 다른 리포지토리에
git push - 조직 내 별도 리포지토리의 Releases/Packages 업로드
- 서드파티 시스템(AWS/GCP 등) 배포
대안 1: Fine-grained PAT 사용
가장 단순하지만 관리 비용이 있습니다. PAT는 만료/회수/권한 범위 관리가 중요합니다.
- name: Push to another repo
env:
PAT: ${{ secrets.DEPLOY_PAT }}
run: |
git remote set-url origin https://x-access-token:${PAT}@github.com/OWNER/OTHER_REPO.git
git push origin HEAD:main
대안 2: Deploy Key 사용
특정 리포지토리에만 제한된 SSH 키를 부여할 수 있어 범위가 명확합니다.
대안 3: OIDC로 클라우드 권한 위임
클라우드 배포에서 403이 난다면 토큰 문제가 아니라 클라우드 IAM/신뢰 정책 문제일 수 있습니다. 특히 AWS 배포에서 OIDC 설정이 잘못되면 403이 빈번합니다. 이 케이스는 아래 글이 직접적인 연장선입니다.
6) 최소 권한 예시 템플릿 모음
아래는 자주 쓰는 권한 조합입니다. 워크플로우 최상단에 두는 것을 권장합니다.
단순 CI (읽기만)
permissions:
contents: read
PR 코멘트/라벨링
permissions:
contents: read
pull-requests: write
issues: write
패키지 퍼블리시(GitHub Packages)
permissions:
contents: read
packages: write
Pages 배포
permissions:
contents: read
pages: write
id-token: write
id-token: write는 OIDC 기반 인증(예: Pages, 클라우드 연동)에서 필요할 수 있습니다.
7) 빠른 진단 체크리스트
403이 나면 아래 순서대로 보면 대부분 10분 안에 원인이 좁혀집니다.
- 에러가 난 API/동작이 무엇인지 확인(푸시, 릴리즈, 패키지, PR 코멘트 등)
- 워크플로우에
permissions가 있는지 확인(없으면 추가) - 리포지토리
Workflow permissions가 읽기 전용으로 잠겨 있는지 확인 - 이벤트가
pull_request이고 포크 PR인지 확인(구조적으로 write 불가 가능) - 크로스 레포/외부 시스템 접근인지 확인(
GITHUB_TOKEN대신 PAT/OIDC 필요) actions/checkout@v4설정(persist-credentials,fetch-depth) 확인
마무리: 403은 “토큰 문제”가 아니라 “권한 설계 문제”인 경우가 많다
GitHub Actions의 GITHUB_TOKEN은 편리하지만, 보안 정책과 이벤트 컨텍스트에 따라 권한이 달라집니다. 따라서 403을 근본적으로 없애려면 다음 원칙이 가장 효과적입니다.
- 워크플로우에 필요한 권한을
permissions로 명시 - 리포지토리/조직의 Actions 권한 정책을 함께 점검
- 포크 PR에서는 쓰기 작업을 분리하거나 안전한 이벤트로 재설계
- 크로스 레포/클라우드 배포는 PAT 또는 OIDC 같은 적절한 인증 수단을 선택
같은 403이라도 “GitHub API 권한 부족”과 “클라우드 IAM 403”은 해결 방향이 완전히 다를 수 있으니, 배포 계열이라면 위의 OIDC 글도 함께 참고하는 것이 좋습니다.