GitHub Actions 실행 보호가 GA가 됐다: pull_request_target을 막기 전에 볼 세 경계
GitHub가 2026년 9월 17일 Actions의 workflow execution protections를 정식 출시했습니다. 이름만 보면 워크플로 안의 if: 조건을 하나 더 두는 기능처럼 보이지만, 적용 지점이 다릅니다. 워크플로가 runner에 올라가기 전에 누가(actor) 어떤 이벤트(event)로 실행을 시작할 수 있는지 정책으로 거릅니다.
이번 정식 버전에는 특정 workflow 파일만 겨냥하는 조건, 정책 평가 결과를 모아 보는 Insights, enterprise·organization·repository 수준의 REST API가 추가됐습니다. 공개 저장소의 pull_request_target을 기본 차단하는 규칙도 함께 예고됐습니다. 이 기본 규칙은 처음에는 evaluate mode로 동작하고, 영향을 받는 저장소에는 2026년 11월 2일부터 강제 적용될 예정입니다.
actor와 event를 모두 통과해야 실행된다
실행 보호는 허용 목록 방식입니다. actor 규칙은 실행을 요청한 주체를, event 규칙은 push, workflow_dispatch, pull_request_target 같은 시작 이벤트를 봅니다. GitHub는 두 조건을 모두 평가한 뒤 run을 만들지 결정합니다. 허용되지 않은 실행은 runner 안에서 실패하는 것이 아니라 정책 오류와 함께 실행 단계에서 막힙니다.
정책은 enterprise, organization, repository에 각각 둘 수 있습니다. 상위 정책을 하위 관리자가 느슨하게 덮는 구조로 이해하면 위험합니다. 공식 문서는 여러 범위의 보호가 계층적으로 함께 적용된다고 설명합니다. enterprise에서 양보할 수 없는 넓은 제한을 두고, organization과 repository에서 더 좁은 제한을 추가하는 편이 맞습니다.
그림 1. 실행 보호는 actor와 event를 runner 앞에서 평가하고, enterprise·organization·repository 및 workflow path 범위로 계층화한다. 출처: github.blog/changelog/2026-09-17-workflow-execution-protections-in-github-actions-generally-available.
정식 버전의 중요한 변화는 workflow path 조건입니다. 한 저장소 안에서도 배포 워크플로와 일반 CI를 다르게 다룰 수 있습니다. 예를 들어 .github/workflows/deploy.yml은 지정된 팀만 실행하게 하고, 읽기 전용 검사 워크플로는 더 넓게 열 수 있습니다. 저장소 전체를 한 규칙으로 묶어 예외를 남발하는 것보다 권한이 필요한 workflow만 좁게 지정하는 방식이 낫습니다.
actor 규칙도 단순한 사용자 이름 목록은 아닙니다. 공식 문서는 개별 사용자, repository role, GitHub App, Copilot, Dependabot을 구분할 수 있다고 설명합니다. 기본적으로 repository에 write access가 있는 사용자는 workflow를 시작할 수 있지만, 정책을 쓰면 코드를 기여할 수 있는 사람과 민감한 CI를 실행할 수 있는 사람을 분리할 수 있습니다. 자동화 계정은 사람 팀에 섞지 말고 전용 주체로 관리해야 차단 원인과 예외 범위를 추적하기 쉽습니다.
이 정책은 YAML 내부 조건과도 역할이 다릅니다. if:는 이미 시작된 workflow나 job 안에서 분기를 결정하지만, execution protection은 실행 자체가 허용되는지 앞단에서 판단합니다. 따라서 공격자가 workflow 파일을 잘못 구성하거나 넓은 event를 넣어도 중앙 정책이 마지막 바깥 경계로 남을 수 있습니다. 반대로 정책이 허용했다고 workflow 내부 명령이 안전해지는 것은 아닙니다. 두 층을 같은 통제로 보지 않아야 합니다.
pull_request_target은 왜 별도 경계가 필요한가
pull_request_target은 외부 fork에서 온 PR에도 base repository의 컨텍스트로 동작합니다. 그래서 PR에 라벨을 붙이거나 댓글을 다는 작업처럼 base 쪽 권한이 필요한 자동화에 쓸 수 있습니다. 반대로 PR head의 신뢰하지 않은 코드를 checkout해 빌드하거나 실행하면, 쓰기 권한이나 secret을 가진 runner에서 공격자 코드가 움직일 수 있습니다. workflow_run으로 단계를 나눌 때도 앞 workflow가 만든 artifact는 신뢰하지 않은 데이터로 취급해야 하며, 권한 있는 단계에서 그대로 실행해서는 안 됩니다.
GitHub의 이벤트 문서는 이 trigger로 PR 코드를 빌드하거나 실행하지 말라고 명시합니다. Security Lab도 pull_request_target과 신뢰하지 않은 PR checkout을 함께 쓰는 패턴을 이른바 "pwn request"의 핵심으로 짚습니다. 악성 변경은 테스트 파일뿐 아니라 package.json의 install script나 빌드 스크립트에도 숨을 수 있습니다. checkout 뒤 npm install만 실행해도 이미 늦을 수 있습니다.
그림 2. `pull_request_target`에서 PR head code를 실행하지 말고, 신뢰하지 않은 코드 검사와 base 권한 작업을 분리해야 한다. 출처: docs.github.com/actions/reference/security/securely-using-pull_request_target.
여기서 새 기본 정책의 의미가 생깁니다. 공개 저장소에 적용 가능한 event policy가 없다면 GitHub가 pull_request_target을 막는 기본 규칙을 추가합니다. private·internal 저장소에는 이 기본값이 적용되지 않습니다. 필요한 workflow가 있다면 event를 저장소 전체에 다시 여는 대신 새 workflow-file targeting으로 정확한 파일만 allowlist할 수 있습니다.
기본 차단을 해제해야 한다면 "현재 문제 없이 돌아간다"는 이유만으로는 부족합니다. 해당 workflow가 base branch의 안전한 코드만 실행하는지, PR head의 파일·artifact를 읽는지, token permission이 무엇인지 확인해야 합니다. GitHub는 pull_request_target의 기본 cache 접근을 읽기 전용으로 제한하지만, workflow가 write-capable cache-mode를 명시하면 cache poisoning 위험이 다시 생긴다고 경고합니다. 허용 예외에는 담당 팀과 만료 시점, 재검토 조건을 같이 남기는 편이 좋습니다.
evaluate mode는 로그 수집 단계이지 면책 장치가 아니다
처음부터 enforce로 바꾸면 라벨러나 triage automation이 갑자기 멈출 수 있습니다. evaluate mode는 실제 run을 차단하지 않은 채 어떤 실행이 정책에 걸릴지 보여 줍니다. GitHub는 Insights에서 enterprise, organization, repository의 평가 결과를 확인하고, 강제 적용 전후에 규칙의 영향을 조정할 수 있다고 설명합니다.
다만 evaluate 결과가 없다고 안전하다고 단정할 수는 없습니다. 관찰 기간에 해당 event가 발생하지 않았을 수도 있고, 오래된 workflow가 드물게만 실행될 수도 있습니다. 최소 한 번의 정상 경로와 거부돼야 할 경로를 의도적으로 만들어 결과를 확인해야 합니다. 정책은 workflow 내부의 권한 설정, action SHA 고정, secret 범위, untrusted input 검토를 대신하지도 않습니다.
전환할 때 확인할 순서
운영 전환은 다음 순서가 현실적입니다.
- 기본 분기의
.github/workflows/*.yml에서pull_request_target사용 위치를 찾습니다. - 각 workflow가 PR head를 checkout하거나 PR이 바꿀 수 있는 script·config·artifact를 실행하는지 확인합니다.
- base 권한이 필요한 라벨·댓글 작업과 untrusted code의 빌드·테스트를 분리합니다.
- 실행 보호를 evaluate mode로 만들고 정상 run과 차단 후보를 Insights에서 대조합니다.
- 필요한 예외는 repository 전체가 아니라 workflow path 단위로 좁힙니다.
- 정책을 REST API로 관리한다면 생성·조회·수정·삭제 권한과 변경 기록을 별도로 감시합니다.
- enforce 뒤에는 차단 건수뿐 아니라 필요한 자동화가 빠진 비율도 확인합니다.
정책 API는 organization과 repository에서 list, create, get, update, delete 작업을 제공합니다. 대규모 조직은 이 API로 정책을 코드처럼 배포할 수 있지만, API 호출 자체가 새 관리 권한이 됩니다. 누가 정책을 느슨하게 바꿀 수 있는지와 변경을 어떤 감사 로그로 잡을지까지 설계해야 합니다.
오늘 다른 글과 겹치지 않는 지점
오늘 아침 글은 Python 3.14 free-threaded 빌드의 GIL·C 확장·공유 상태 경계를 다뤘습니다. 오후 글은 작은 데이터베이스 에이전트의 protocol·timeout·memory 실패를 분해했습니다. 이번 글은 CI/CD가 시작되기 전 actor·event·workflow path를 정책으로 제한하는 공급망 보안 경계가 주제입니다.
결론
이 기능은 YAML 한 파일을 고치는 도구가 아닙니다. 실행 주체와 이벤트를 runner 앞에서 걸러 내고, 위험이 큰 workflow만 파일 단위로 좁혀 운영하는 정책층입니다. pull_request_target을 계속 써야 한다면 전체 허용보다 정확한 workflow allowlist와 코드 실행 분리가 먼저입니다.
11월 2일 기본 규칙 적용을 기다렸다가 실패한 자동화를 고치는 방식은 피하는 편이 낫습니다. 지금 evaluate mode에서 실제 영향을 모으고, PR head code와 base 권한이 만나는 경로를 먼저 끊어야 합니다.
참고 자료
- GitHub Changelog: Workflow execution protections generally available
- GitHub Docs: About Actions policies
- GitHub Docs: Control workflow execution
- GitHub REST API: Actions policies
- GitHub Docs: Events that trigger workflows - pull_request_target
- GitHub Docs: Securely using pull_request_target
- GitHub Security Lab: Preventing pwn requests


댓글
댓글 쓰기