모든 GitHub Actions 작업은 토큰을 받습니다. GitHub는 각 작업이 시작될 때 워크플로가 있는 리포지토리로 범위가 제한된 새 GITHUB_TOKEN을 생성하며, 작업의 모든 단계가 이 토큰을 사용할 수 있습니다. 토큰으로 무엇을 할 수 있는지는 많은 팀이 한 번도 들여다보지 않은 설정에 달려 있으며, 오래된 리포지토리에서는 그 답이 여전히 "거의 모든 곳에 쓰기"입니다.

이는 작업 안의 무언가가 악성으로 바뀔 때 중요해집니다. 2025년 8월 Nx "s1ngularity" 침해 사고에서 리포지토리의 토큰은 여전히 읽기 및 쓰기 권한을 갖고 있었습니다. pull_request_target 워크플로의 인젝션이 이 토큰으로 게시 워크플로를 트리거했고, 그 결과 악성 Nx 버전을 게시하는 데 사용된 npm 토큰이 유출되었습니다. 읽기 전용 토큰이었다면 이 단계를 막을 수 있었습니다.

이 글에서는 토큰 권한이 어떻게 설정되는지, 일반적인 작업마다 무엇이 필요한지, 워크플로에 필요한 최소 권한을 어떻게 찾는지 다룹니다.

GITHUB_TOKEN이란

GITHUB_TOKEN은 GitHub Actions 앱의 설치 액세스 토큰입니다. 놓치기 쉬운 몇 가지 특성이 있습니다.

  • 사용 여부와 관계없이 존재합니다. 워크플로는 이를 secrets.GITHUB_TOKEN으로 참조하지만, 어떤 액션이든 전달받지 않아도 github.token 컨텍스트로 읽을 수 있습니다.
  • 기본적으로 디스크에 기록됩니다. actions/checkout은 이후 git 명령을 위해 토큰을 저장합니다. 이를 막으려면 persist-credentials: false를 설정하세요. v6 이전에는 .git/config에 저장되었고, v6에서는 $RUNNER_TEMP 아래의 별도 파일로 옮겨졌습니다. 2024년 Unit 42의 ArtiPACKED 연구는 .git/config가 포함된 아티팩트를 통해 토큰이 유출되는 사례를 발견했습니다.
  • 작업과 함께 만료됩니다. GitHub 호스팅 러너에서는 작업 시간 제한인 최대 6시간입니다. 셀프 호스팅 러너에서는 최대 24시간까지 갱신될 수 있습니다.

토큰은 항상 존재하므로, 제어할 수 있는 것은 토큰이 할 수 있는 범위뿐입니다.

기본값: 허용 또는 제한

각 엔터프라이즈, 조직, 리포지토리에는 토큰의 기본값이 있습니다.

  • 허용(permissive): 모든 권한에 대해 읽기 및 쓰기.
  • 제한(restricted): contents와 packages에 대해서만 읽기 전용, 그 외에는 없음.

2023년 2월 2일부터 새 엔터프라이즈, 조직, 리포지토리의 기본값은 제한입니다. 이 변경은 기존 대상에는 적용되지 않았으므로, 그 이전에 만들어진 리포지토리나 조직은 누군가 바꾸지 않았다면 허용 기본값을 유지합니다. 상위 수준에서 제한을 선택하면 하위 수준에서는 허용을 선택할 수 없습니다.

Settings → Actions → General → Workflow permissions에서 조직 수준부터 확인하세요. 같은 섹션에 Allow GitHub Actions to create and approve pull requests도 있으며, 워크플로에 필요하지 않다면 꺼 두어야 합니다.

permissions 키

워크플로의 permissions 키는 기본값에서 토큰 권한을 조정합니다. 두 수준에서 작동합니다.

permissions:
  contents: read

jobs:
  release:
    permissions:
      contents: write
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@<full-commit-sha>

규칙은 다음과 같습니다.

  • 작업 수준이 워크플로 수준보다 우선합니다. 워크플로 기본값을 읽기 전용으로 설정하고, 필요한 작업에서만 권한을 높이세요.
  • 나열하지 않은 권한은 모두 none이 됩니다. 권한을 하나라도 설정하면 나머지 권한은 모두 제거됩니다. permissions: {}는 모든 권한을 제거합니다.
  • 값은 read, write, none입니다. write는 read를 포함합니다. read-all과 write-all은 모든 권한을 한 번에 설정합니다. write-all은 피하세요.

2026년 9월 기준으로 사용할 수 있는 권한은 다음과 같습니다.

권한제어 대상
actions워크플로 실행, 아티팩트, 캐시
artifact-metadata아티팩트 메타데이터 레코드(2026년에 이 API에 대해 contents를 대체)
attestations아티팩트 증명
checks체크 실행과 체크 스위트
code-quality커버리지 보고서 같은 코드 품질 결과
contents리포지토리 콘텐츠, 커밋, 브랜치, 태그, 릴리스
deployments배포
discussions토론
id-tokenOIDC 토큰 요청(write 또는 none만 가능)
issues이슈와 댓글
packages컨테이너 레지스트리를 포함한 GitHub Packages
pagesGitHub Pages 빌드
pull-requests풀 리퀘스트, 레이블, 리뷰
security-events코드 스캔 알림과 SARIF 업로드
statuses커밋 상태
vulnerability-alertsDependabot 알림(read 또는 none만 가능)

작업에 필요한 권한이 없으면 API 호출은 403 Resource not accessible by integration으로 실패합니다. 이 오류는 write-all로 바꾸라는 신호가 아니라, 해당 단계에 필요한 권한 하나를 추가하라는 신호입니다.

일반적인 작업에 필요한 권한

작업권한
코드 체크아웃contents: read
릴리스 생성 또는 태그 푸시contents: write
이슈 또는 풀 리퀘스트에 댓글 작성issues: write(풀 리퀘스트 댓글은 이슈 API를 거침)
풀 리퀘스트에 레이블 또는 리뷰 추가pull-requests: write
OIDC로 클라우드 공급업체에 인증id-token: write
아티팩트 증명 생성id-token: write, attestations: write, contents: read, 컨테이너 이미지의 경우 packages: write 추가
GitHub Container Registry에 이미지 푸시packages: write
코드 스캔에 SARIF 업로드security-events: write
워크플로에서 GitHub Pages 배포pages: write, id-token: write

대부분의 테스트 및 빌드 작업에는 contents: read 외에 아무것도 필요하지 않습니다.

포크, pull_request_target, Dependabot

  • 포크에서 온 풀 리퀘스트는 관리자가 "Send write tokens to workflows from pull requests"를 켜지 않는 한, 워크플로가 무엇을 요청하든 pull_request 이벤트에서 읽기 전용 토큰을 받습니다. 이 설정은 꺼 두세요.
  • **pull_request_target**은 공개 포크에서 온 풀 리퀘스트에도 읽기 및 쓰기 토큰을 받습니다. 그래서 가장 잘 알려진 토큰 탈취 사고 다수의 배경이 되었습니다. Nx 침해, 2024년 12월 Ultralytics 침해, 2026년 3월 Trivy 액션 침해는 모두 pull_request_target 워크플로에서 시작되었습니다. GitHub는 이를 좁혀 가고 있습니다. 2026년 7월부터 actions/checkout은 기본적으로 이러한 워크플로에서 포크 코드의 체크아웃을 거부하며, 2026년 11월 2일부터 자체 정책이 없는 공개 리포지토리에서는 pull_request_target이 기본적으로 비활성화됩니다.
  • Dependabot 풀 리퀘스트는 포크처럼 취급되어, 읽기 전용 토큰을 받고 Actions 시크릿에 접근할 수 없습니다. 다른 업데이트 도구를 검토 중이라면 Dependabot 대안을 참조하세요.

최소 권한을 찾는 방법

모든 액션의 소스를 읽어 가며 알아낼 필요는 없습니다.

  • GitHub Security Lab의 actions-permissions는 워크플로가 토큰을 어떻게 사용하는지 모니터링하고 최소 권한을 권장합니다. 현재 공개 베타입니다.
  • OpenSSF Scorecard의 Token-Permissions 검사는 최상위 수준에 읽기 전용 권한을 설정하지 않은 워크플로를 표시합니다. 이 검사는 고위험으로 평가됩니다.
  • 워크플로용 정적 분석기인 zizmor는 과도하거나 기본값인 권한, 자격 증명을 유지하는 체크아웃을 표시합니다.
  • StepSecurity의 Secure-Repo와 Harden-Runner는 액션 지식 베이스와 작업이 수행하는 API 호출을 바탕으로 작업 수준 권한을 권장합니다.

실용적인 도입 방법은 다음과 같습니다. 조직 기본값을 제한으로 설정하고, 모든 워크플로 최상단에 permissions: contents: read를 추가한 뒤, 워크플로를 실행하세요. Resource not accessible by integration으로 실패하는 워크플로가 어떤 작업에 어떤 추가 권한이 필요한지 정확히 알려 줍니다.

CRACI의 역할

최소 권한은 탈취된 토큰이 할 수 있는 일을 제한합니다. 하지만 탈취 자체를 막지는 못하며, npm 토큰이나 클라우드 자격 증명 같은 작업의 다른 시크릿도 똑같이 노출되어 있습니다.

CRACI는 CRACI 러너에서 GitHub Actions 작업을 실행합니다. runs-on을 craci로 바꾸면 됩니다. GitHub가 여전히 토큰을 발급하므로 GITHUB_TOKEN, permissions 키, 리포지토리 설정은 이전과 똑같이 작동합니다. CRACI가 더하는 것은 러너에 있습니다. 작업이 접근할 수 있는 호스트를 결정하는 이그레스 정책은 작업 시작 전에 검증되고 실패 시 차단하며, 작업이 가져온 모든 패키지의 기록이 네트워크 연결 추적과 함께 남습니다. 작업 안의 악성 코드가 토큰을 손에 넣더라도, 정책이 토큰을 보낼 수 있는 곳을 제한하고 기록이 무슨 일이 있었는지 보여 줍니다.

작업의 나머지 시크릿에 대해서는 GitHub Actions 시크릿: 유출 경로와 보호 방법을 참조하세요. 데모를 예약하고 여러분의 빌드 하나의 기록을 확인해 보세요.