GitHub Actions 의심 워크플로 승인 보류 설정과 안전한 검토 절차
GitHub Actions 의심 워크플로 승인 보류라는 한 표현 아래에는 서로 다른 세 통제가 섞여 있다. GitHub가 잠재적으로 악성인 실행을 자동으로 붙잡는 기능, 외부 기여자의 포크 실행을 관리자가 허용하는 정책, 보호된 배포 환경 앞에서 검토자를 기다리는 기능이다. 기다리는 대상과 가능한 버튼이 다르므로, 먼저 어느 통제인지 가르지 않으면 존재하지 않는 거절 버튼을 찾거나 높은 권한의 실행을 포크 승인 설정만으로 막았다고 오해할 수 있다.
아래 내용은 2026년 8월 16일 GitHub 공식 자료와 Linux·Bash 5.2.21·awk에서 수행한 두 YAML 표본의 정적 비교를 바탕으로 한다. 로그인된 GitHub 화면, 실제 악성 판정, 취소·승인·비밀정보 접근은 재현하지 않았다. 이 글을 읽으면 관리자가 자기 화면의 통제를 구분하고, 하나의 풀 리퀘스트를 따라 실행 허용 여부와 처리 완료 상태를 판단할 수 있다.
20초 핵심 요약
- 무엇: 자동 의심 실행 보류, 포크 PR 실행 승인, 환경 배포 승인을 먼저 구분한다.
- 왜: 잘못 승인하면 높은 권한의 자동 토큰으로 외부 코드가 실행되거나 비밀값과 실행 머신까지 영향을 받을 수 있다.
- 어떻게: 한 PR의 전체 변경, 실행 계기, 토큰 권한, 비밀정보 접근, 외부 액션과 러너를 연결해 본 뒤 처리 결과를 확인한다.
같은 승인 화면처럼 보여도 세 통제의 결과는 다르다
풀 리퀘스트(PR, 다른 사람이 제안한 코드 변경 묶음)와 Actions 화면에서 보이는 맥락을 아래 표의 왼쪽부터 읽는다. 워크플로(workflow)는 코드 검사·빌드·배포 같은 자동 작업의 순서이며, 이 표에서는 무엇이 기다리고 누가 어떤 처리를 할 수 있는지 구분하는 기준이다.
| 통제 | 무엇이 기다리는가 | 시작 또는 설정 조건 | 공식 문서에 나온 처리 | 처리 뒤 확인할 것 |
|---|---|---|---|---|
| 자동 의심 실행 보류 | GitHub가 잠재적으로 악성이라고 식별한 공개 github.com 저장소의 특정 실행 | 별도 설정 없이 자동 적용 | 쓰기 권한 협업자가 인증된 웹 세션에서 검토·승인 | 승인했다면 실행 상태와 로그 |
| 포크 PR 실행 승인 | 정책 대상 외부 기여자가 만든 공개 포크 PR 실행 | Settings → Actions → General의 외부 기여자 승인 정책 |
Files changed 검토 후 Approve workflows to run |
작업별 대기·실행·완료 또는 실패 상태 |
| 환경 배포 승인 | 보호 환경을 참조하는 작업 | 환경의 required reviewer 설정 | Review deployments에서 Approve and deploy 또는 Reject |
승인 뒤 배포 상태, 거절 뒤 워크플로 실패 |
포크(fork)는 다른 계정에 복사한 저장소이며, 외부 기여자가 원본 저장소에 변경을 제안할 때 쓰인다. 2026년 7월 28일 발표된 자동 보호는 공개 github.com 저장소의 특정 실행에 별도 설정 없이 적용되고, 쓰기 권한이 있는 협업자가 인증된 웹 세션에서 승인해야 계속된다. 공지 시점에는 GitHub Enterprise Server에 적용되지 않았다. 다만 보류는 안전 검토 시간을 주는 상태이지 악성 확정이 아니며, 보류되지 않은 실행도 안전하다고 증명된 것은 아니다.
포크 정책은 Settings → Actions → General → Approval for running fork pull request workflows from contributors에서 정한다. 기본값은 첫 기여자 승인을 요구하며, 가입·병합 이력에 따른 첫 기여자부터 모든 외부 기여자까지 대상을 고를 수 있다. 현재 값을 먼저 기록해 두면 승인 부담과 대기 시간이 예상보다 커졌을 때 되돌릴 수 있다. 모든 외부 기여자를 대상으로 삼는 선택은 우회 여지를 줄이는 운영 후보지만 GitHub의 모든 저장소에 대한 보편 권고는 아니다.
환경(environment)은 배포 대상과 그곳에서 쓸 비밀값을 묶어 보호하는 단위다. required reviewer는 지정된 검토자가 허용할 때까지 그 환경을 참조한 작업과 환경 비밀값 접근을 기다리게 한다. 세 통제 가운데 공식 Reject가 확인되는 것은 이 배포 검토 흐름이며, 기능 가용성은 요금제와 저장소 공개 범위에 따라 달라질 수 있다.
설명용 PR 하나에서 승인 판단이 바뀌는 지점을 따라간다
이제 외부 기여자가 보낸 하나의 가상 PR을 따른다. 실제 계정에서 관측한 사건이 아니라 공식 문서의 위험 조건을 한 흐름으로 묶은 설명용 사례다. 처음 보이는 Awaiting approval은 아직 사람이 허용하지 않아 실행이 기다리는 상태다. 반대로 상태가 이미 in progress라면 진행 중인 실행이므로 Actions 실행 상세에서 취소한 뒤 멈췄는지 확인한다. 실제 버튼 위치와 강제 취소 조건은 로그인 화면에서 검증하지 않았다.
대기 중이라면 승인하지 않은 채 Files changed를 연다. GitHub의 포크 승인 절차도 전체 변경, 특히 .github/workflows/ 아래 파일을 먼저 검토한 뒤 Approve workflows to run을 선택하도록 안내한다. YAML은 자동화 설정을 적는 문법이며, 이 디렉터리에서는 언제 어느 머신에서 무슨 명령을 실행할지 정한다.
on: 트리거(trigger, 워크플로를 시작시키는 사건)runs-on: runner(러너, 명령을 실제로 실행하는 머신)uses: 제3자 액션 또는 다른 워크플로 호출run: 셸에서 직접 실행할 명령permissions: 자동 생성 토큰에 허용할 GitHub 작업secrets,environment: 비밀값과 보호된 배포 대상 사용 여부
이 PR의 YAML에 run: npm test가 있다면 YAML만 읽고 끝내지 않는다. package script, Makefile, 셸 스크립트, 설치 hook, dependency(의존성, 실행에 함께 쓰는 외부 코드)와 lockfile까지 실제 호출 경로를 따라간다. 다운로드 직후 실행, 알아보기 어렵게 감춘 명령, 외부 서버 전송, 예상 밖의 artifact(실행 산출물)나 cache(다시 쓰려고 보관한 데이터) 경로가 보이면 목적이 확인될 때까지 기다림을 유지한다.
수정 요청 뒤 새 커밋이 올라오면 사용자가 보는 결과는 새 diff다. 위험 코드가 다른 호출 파일로 옮겨졌을 수 있으므로 이전에 지적한 줄만 확인하지 않고 전체 변경과 호출 관계를 다시 읽는다. 포크 승인 대기는 30일이 지나면 자동 삭제되므로 항목이 사라졌다면 승인 실패로 단정하지 말고 새 커밋이나 이벤트가 만든 실행을 다시 검토한다.
실행 계기와 토큰 권한이 만나면 결론이 달라진다
GITHUB_TOKEN은 각 작업이 GitHub API를 호출할 때 쓰는 자동 생성 자격 증명이다. API(Application Programming Interface)는 워크플로가 저장소 읽기나 댓글 작성 같은 기능을 정해진 요청으로 이용하는 접점이다. 액션은 토큰을 직접 전달받지 않아도 github.token으로 접근할 수 있으므로 permissions에는 작업에 필요한 최소 권한만 둬야 한다.
일반 공개 포크의 pull_request에서는 GITHUB_TOKEN이 읽기 전용이며 다른 저장소 비밀값은 러너로 전달되지 않는다. 하지만 실행 코드는 러너 데이터, 산출물, 캐시와 자동 토큰을 다룰 수 있으므로 이 조건 하나로 승인을 결정할 수는 없다.
pull_request_target은 원본 저장소의 신뢰된 기본 브랜치 문맥에서 PR 관련 작업을 시작하는 트리거다. 라벨이나 댓글 처리에 쓸 수 있지만 원본 저장소의 토큰과 조직·저장소 비밀값에 접근할 수 있는 높은 신뢰 문맥이다. 여기서 외부 기여자가 바꾼 PR head나 merge commit을 체크아웃해 빌드·테스트하면 그 코드도 높은 권한으로 실행될 수 있다.
공식 문서의 경고를 실제 검토 순서로 구별할 수 있는지 확인하려고 두 YAML 표본을 정적으로 비교했다. 위험 표본은 pull_request_target, contents: write, PR head 체크아웃, 실행 단계가 함께 있었고, 대조 표본은 pull_request, 읽기 권한, 전체 SHA로 고정한 액션을 사용했다.
event=1 write=1 untrusted_checkout=1 execute=1
risky_exit=0
event=0 write=0 untrusted_checkout=0 execute=1
safer_exit=1
대조 표본의 종료값 1은 명령 오류가 아니라 정의한 복합 위험 조건이 성립하지 않았다는 뜻이다. 이 검사는 YAML 별칭, 재사용 워크플로, 간접 스크립트와 동적 표현을 해석하지 못하는 단순 텍스트 비교이며 GitHub의 실제 보류 판정이나 안전성을 증명하지 않는다. 관측이 공식 문서의 고위험 조합을 예상대로 갈랐다는 범위에서만 쓸 수 있다.
포크 승인 설정은 pull_request_target을 멈추지 않는다. 따라서 사례의 네 신호 가운데 하나라도 보이면 호출 파일과 권한을 더 확인하고, 높은 권한 문맥에서 신뢰하지 않은 코드를 실행하는 조합이면 승인보다 워크플로 수정이 먼저다. 필요하지 않다면 pull_request를 사용하고, 꼭 필요하다면 PR 코드를 데이터로만 검사하며 실행하지 않는다.
비밀정보와 실행 머신까지 이어서 영향 범위를 좁힌다
secret(비밀값)은 토큰이나 암호처럼 공개하면 안 되는 값을 GitHub에 암호화해 두었다가 워크플로에 전달하는 기능이다. 일반 공개 포크의 pull_request에 다른 저장소 비밀값이 전달되지 않아도 로그 가림 처리를 보안 장벽으로 믿어서는 안 된다. 실행 코드는 값을 나누어 출력하거나 외부로 전송할 수 있기 때문이다.
environment가 보이면 어느 배포 대상을 가리키는지, required reviewer가 켜졌는지, 실행을 시작한 사람이 자기 배포를 승인하지 못하도록 설정했는지 확인한다. 환경 승인이 끝난 뒤에야 환경 비밀값에 접근할 수 있지만 이 기능이 러너 자체를 격리하는 것은 아니다.
제3자 액션의 uses는 전체 길이 commit SHA로 고정됐는지 본다. SHA는 커밋 내용을 고유하게 가리키는 긴 식별자이며, 이 흐름에서는 tag가 나중에 다른 코드로 옮겨 가는 공급망 위험을 줄이기 위해 필요하다. 그 SHA가 원래 액션 저장소의 것인지, 액션 소스가 비밀값을 예상하지 않은 곳으로 보내지 않는지도 확인한다.
runs-on은 마지막 영향 범위를 보여준다. GitHub-hosted runner는 일회성 격리 가상 머신이지만 self-hosted runner는 운영자가 관리하는 머신이라 신뢰하지 않은 코드가 내부망에 접근하거나 흔적을 남길 수 있다. GitHub는 공개 저장소에서 self-hosted runner 사용을 거의 피하라고 경고한다. 외부 PR 코드를 이 머신에서 실행한다면 환경 승인 유무와 별개로 설계를 다시 검토한다.
아래 그림은 앞의 판별표에서 고른 통제를 왼쪽 출발점으로 삼아 읽는다. 가운데에서 변경 파일, 높은 권한 트리거, 비밀값과 실행 머신을 확인한 뒤 오른쪽의 세 처리 결과로 이동한다.

그림의 결론은 승인 버튼이 검토의 시작이 아니라 검토 결과라는 점이다. 포크 실행과 자동 의심 보류에는 공통 Reject 버튼을 가정하지 않고, 환경 배포에만 공식 문서에서 확인된 Reject 분기를 둔다.
승인·보류 유지·환경 거절은 서로 다른 완료 상태로 끝난다
| 선택 | 사용자가 하는 행동 | 기다리거나 보게 되는 결과 | 실패·복구 | 완료 확인 |
|---|---|---|---|---|
| 승인 | Awaiting approval에서 Approve workflows to run 선택 |
작업이 queued, in progress를 거쳐 실행됨 |
예상 밖 작업이나 노출 정황이 있으면 실행을 멈추고 영향 조사 | 예상한 작업만 completed에 도달했는지 또는 실패 로그가 설명 가능한지 확인 |
| 승인 보류 유지 | 승인 버튼을 누르지 않고 위험 파일·권한·트리거의 제거나 분리를 요청 | 실행은 기다리는 상태로 남거나 30일 뒤 자동 삭제됨 | 수정 커밋이 오면 전체 diff와 호출 파일을 처음부터 재검토 | 위험 조합이 제거된 새 변경 묶음에 대해 새 판단을 마침 |
| 환경 배포 거절 | Review deployments에서 환경을 고르고 Reject 선택 |
워크플로가 실패함 | 수정된 새 실행이 올라오면 환경과 변경을 다시 검토 | 거절한 실행의 실패 상태를 확인하고 새 실행을 별도로 판단 |
승인 후 비밀값 노출 정황을 발견했다면 로그 삭제로 복구를 끝내지 않는다. 노출 가능성이 있는 자격 증명을 폐기·회전하고 토큰 권한, 외부 통신과 러너 영향을 조사해야 한다. 그 뒤 권한을 줄인 새 실행이 예상한 경로만 사용하는지 확인한다.
이 절차의 적용 대상은 공개 저장소에서 외부 PR과 자동 배포를 함께 운영하는 관리자다. 내부 기여자만 있는 저장소라도 pull_request_target, 넓은 토큰 권한, self-hosted runner가 만난다면 같은 판단이 필요하다. 최종 선택은 버튼 이름이 아니라 완료 상태로 구분한다. 승인은 실행 결과까지, 보류 유지는 위험 변경을 제거한 새 diff의 재검토까지, 환경 거절은 해당 워크플로의 실패 확인까지 이어져야 끝난다.
참고 링크
- GitHub Changelog: 잠재적으로 악성인 워크플로 승인 보류
- GitHub Docs: 포크 워크플로 실행 승인
- GitHub Docs: 저장소의 Actions 설정 관리
- GitHub Docs: pull_request_target 안전하게 사용하기
- GitHub Docs: GitHub Actions 보안 사용 지침
- GitHub Docs: GITHUB_TOKEN 인증
- GitHub Docs: GitHub Actions 비밀값 사용
- GitHub Docs: 배포와 환경
- GitHub Docs: 배포 검토
- GitHub Docs: 손상된 러너
- GitHub Docs: 워크플로 실행 관리