본문으로 건너뛰기
개발 10분 읽기

GitHub Actions 보안 강화: 워크플로 권한부터 줄여라

CI 워크플로에서 토큰 권한, 외부 액션 고정, 시크릿 노출을 줄이는 기본 hardening 절차를 정리했다. GitHub Actions·CI Security·Supply Chain 관점에서 기본 개념부터 구현 순서, 검증 방법, 문제가 생겼을 때 되돌리는 절차까지 설명한다.

박지민
에디터
2026년 6월 26일
GitHub Actions 보안 강화: 워크플로 권한부터 줄여라

핵심 요약

GitHub Actions는 워크플로 파일에 적힌 코드가 저장소 토큰, 시크릿, 릴리스 권한을 사용할 수 있는 실행 환경이다. 기본 토큰 권한을 비우고 작업별로 열며, 외부 액션을 전체 커밋 SHA로 고정하고, 장기 클라우드 키 대신 OIDC를 사용해야 공급망 피해 범위를 줄일 수 있다.

CI 워크플로는 테스트 자동화 도구이면서 동시에 배포 권한을 가진 프로그램이다. 의존성 설치 스크립트, 외부 액션, PR 제목, 브랜치 이름, 빌드 출력처럼 평범해 보이는 입력이 셸 명령이나 스크립트로 들어가면 저장소 토큰과 시크릿이 노출될 수 있다. 보안 강화의 첫 단계는 복잡한 탐지 제품이 아니라 워크플로가 가진 권한을 정확히 줄이는 것이다.

GitHub의 기능과 기본값은 조직 정책, 저장소 공개 여부, 엔터프라이즈 설정에 따라 달라질 수 있다. 아래 예시는 시작점이며, 적용 전 현재 GitHub 공식 문서와 조직 설정을 확인해야 한다.

1. GITHUB_TOKEN 기본 권한부터 닫는다

워크플로 최상단에서 기본 권한을 비우거나 읽기 전용으로 제한하고, 필요한 작업에만 명시적으로 권한을 연다.

name: ci

on:
  pull_request:
  push:
    branches: [main]

permissions: {}

jobs:
  test:
    permissions:
      contents: read
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@<FULL_COMMIT_SHA>
      - run: npm ci
      - run: npm test

write-all이나 저장소 전체 쓰기 권한을 편의상 주지 않는다. 테스트 작업이 이슈 작성, 패키지 게시, 릴리스 수정, 배포 권한을 가질 이유가 있는지 각각 확인한다. 권한이 필요한 경우에도 워크플로 전체가 아니라 해당 작업에만 부여한다.

주요 권한은 목적을 문서화한다.

권한필요한 상황 예시주의점
contents: read코드 체크아웃대부분의 테스트 기본값
contents: write태그·릴리스·커밋 작성보호 환경과 별도 배포 작업 권장
packages: write패키지·이미지 게시대상 저장소와 태그 규칙 제한
id-token: writeOIDC 토큰 요청다른 쓰기 권한을 뜻하지 않음
pull-requests: writePR 댓글·라벨신뢰하지 않는 입력과 결합 주의
attestations: write빌드 증명 생성게시 작업에만 제한

2. 외부 액션은 전체 커밋 SHA로 고정한다

uses: owner/action@v4처럼 태그만 참조하면 태그가 가리키는 코드가 바뀔 가능성이 있다. 제3자 액션은 검토한 전체 길이 커밋 SHA에 고정하고, 업데이트는 의존성 봇 또는 승인된 변경 절차로 수행한다.

- name: Checkout source
  uses: actions/checkout@<40_CHARACTER_COMMIT_SHA>

SHA 고정만으로 액션이 안전해지는 것은 아니다. 다음도 확인한다.

  • 공식 소유자와 저장소가 맞는가
  • 유지보수와 보안 공지가 지속되는가
  • 액션이 어떤 입력, 파일, 네트워크, 토큰에 접근하는가
  • 빌드된 JavaScript와 소스가 일치하는지 검증 가능한가
  • 같은 기능을 짧은 셸 명령이나 내부 액션으로 대체할 수 있는가
  • 새 SHA의 변경 내용을 사람이 리뷰했는가

저장소·조직 정책으로 허용된 액션과 재사용 워크플로만 실행하게 제한할 수 있다면 적용한다. 단, 내부 액션도 자동으로 신뢰하지 말고 소유자와 변경 보호를 둔다.

3. 장기 클라우드 키를 OIDC로 바꾼다

클라우드 접근 키를 저장소 시크릿에 넣으면 노출 시 폐기할 때까지 사용할 수 있다. OIDC를 사용하면 워크플로가 짧은 수명 토큰을 요청하고 클라우드가 저장소·브랜치·환경 조건을 검증하게 할 수 있다.

permissions:
  contents: read
  id-token: write

jobs:
  deploy:
    environment: production
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@<FULL_COMMIT_SHA>
      - name: Configure cloud credentials
        uses: cloud-provider/official-oidc-action@<FULL_COMMIT_SHA>
        with:
          role: <MINIMUM_DEPLOY_ROLE>
      - run: ./deploy.sh

클라우드 신뢰 정책을 조직 전체나 모든 브랜치에 열지 않는다. 운영 역할은 특정 저장소, 보호된 브랜치 또는 태그, GitHub Environment에 묶는다. 토큰 주체 클레임과 대상자 조건을 공식 문서에 맞게 검증한다. CI/CD 시크릿 관리의 워크로드 신원 원칙과 같은 맥락이다.

4. PR 이벤트의 신뢰 경계를 구분한다

외부 기여자의 PR은 공격자가 제어하는 코드와 메타데이터를 포함할 수 있다. 테스트에는 시크릿과 쓰기 토큰을 전달하지 않는다.

pull_request

일반적으로 PR의 변경 코드를 테스트하는 데 사용한다. 포크에서 온 PR에는 제한된 권한이 적용되도록 조직 설정을 확인하고, 별도 시크릿을 요구하는 테스트를 분리한다.

pull_request_target

대상 브랜치 컨텍스트에서 실행되므로 댓글·라벨 자동화 같은 작업에 쓸 수 있지만, 공격자가 변경한 코드를 체크아웃해 실행하면 높은 권한과 결합될 수 있다. 이 이벤트에서는 PR 헤드 코드를 실행하지 않는 것을 기본으로 한다. 꼭 필요하다면 권한 없는 별도 워크플로에서 빌드한 검증 결과만 받아 처리한다.

워크플로 연계 이벤트

한 워크플로의 산출물을 권한이 더 큰 후속 워크플로가 사용한다면 아티팩트 변조와 실행 주체를 검증한다. 성공 상태만 믿지 말고 커밋 SHA, 저장소, 브랜치, 워크플로 ID를 확인한다.

5. 표현식과 셸 명령 주입을 막는다

PR 제목, 이슈 본문, 브랜치 이름, 커밋 메시지 같은 컨텍스트 값을 셸 스크립트에 직접 삽입하지 않는다.

위험한 예:

- run: echo "${{ github.event.pull_request.title }}" | ./check-title.sh

더 안전한 패턴은 값을 환경 변수로 전달하고 스크립트에서 인자로 다루는 것이다.

- name: Check pull request title
  env:
    PR_TITLE: ${{ github.event.pull_request.title }}
  run: ./check-title.sh "$PR_TITLE"

환경 변수도 완전한 신뢰 경계는 아니다. 스크립트에서 따옴표, 옵션 종료, 허용 문자, 길이 제한을 적용하고 eval이나 문자열로 조립한 명령 실행을 피한다. JSON·YAML·셸 각 컨텍스트의 이스케이프 규칙을 구분한다.

6. 시크릿 노출 경로를 줄인다

GitHub 시크릿 마스킹은 보조 통제다. 변형되거나 분할된 값, 일부 문자열, 파생 토큰은 로그에 남을 수 있다.

  • 전체 환경 변수 출력과 set -x를 금지한다.
  • HTTP 클라이언트의 상세 디버그 로그를 운영 배포에서 끈다.
  • 시크릿을 명령행 인자로 전달하지 않는다. 프로세스 목록에 보일 수 있다.
  • 파일로 써야 한다면 권한을 제한하고 작업 종료 시 삭제한다.
  • 아티팩트 업로드 전에 파일 목록과 민감 패턴을 검사한다.
  • PR 워크플로에서 운영 환경 시크릿을 참조하지 않는다.
  • 재사용 워크플로에 전달하는 시크릿을 명시하고 상속을 최소화한다.
  • 노출 의심 시 로그 삭제보다 자격 증명 폐기를 먼저 한다.

시크릿 회전과 폐기 런북은 시크릿 회전 자동화에 연결한다.

7. GitHub Environment로 운영 배포를 분리한다

운영 배포 작업에 Environment를 사용하고 필요에 따라 승인자, 브랜치 제한, 환경 시크릿, 배포 기록을 적용한다. 승인자가 워크플로 실행자가 만든 변경을 독립적으로 검토할 수 있게 한다.

승인 화면에는 최소한 다음 정보가 보여야 한다.

  • 배포할 커밋과 이전 커밋 비교
  • 빌드·테스트·보안 검사 결과
  • 생성된 아티팩트 식별자와 해시
  • 대상 환경과 변경 범위
  • 데이터베이스 변경과 롤백·전진 복구 계획
  • 요청자와 승인자

긴급 배포 예외도 같은 감사 로그와 만료 조건을 가져야 한다. 승인 절차가 불편하다는 이유로 운영 시크릿을 일반 저장소 변수로 옮기지 않는다.

8. 자체 호스팅 러너를 별도 신뢰 경계로 본다

자체 호스팅 러너는 조직 네트워크와 캐시, 장기 디스크, 클라우드 인스턴스 역할에 접근할 수 있다. 신뢰하지 않는 PR을 영구 러너에서 실행하지 않는다.

  • 작업마다 새 인스턴스를 만드는 일회성 러너를 우선한다.
  • 저장소·환경·신뢰 수준별로 러너 그룹을 분리한다.
  • 러너 인스턴스 역할에 배포 권한을 상시 부여하지 않는다.
  • 아웃바운드 네트워크 목적지를 제한하고 DNS 로그를 남긴다.
  • 작업 공간, 도커 소켓, 캐시, 임시 파일을 초기화한다.
  • 러너 소프트웨어와 기본 이미지를 패치하고 무결성을 확인한다.
  • 운영 러너에는 대화형 로그인과 개인 도구 설치를 제한한다.

도커 소켓을 마운트한 러너는 호스트 권한으로 이어질 수 있다. 단순한 컨테이너 격리를 강한 보안 경계로 가정하지 않는다.

9. 아티팩트와 배포 대상을 묶는다

테스트한 코드와 실제 배포한 코드가 같아야 한다. 배포 단계에서 소스를 다시 빌드하기보다 검증된 아티팩트를 승격하고, 해시·출처·빌드 워크플로를 기록한다.

  • 아티팩트에 커밋 SHA와 빌드 실행 ID를 붙인다.
  • 업로드와 다운로드 작업의 권한을 최소화한다.
  • 아티팩트 보존 기간과 공개 범위를 제한한다.
  • 서명 또는 증명을 검증한 뒤 배포한다.
  • 재빌드가 필요하면 동일성 검증 방법을 둔다.
  • SLSA 프로비넌스로 빌드 산출물 신뢰를 증명하는 법의 출처 증명과 연계한다.

탐지 신호

신호확인할 내용
워크플로 권한 확대permissions, Environment, 조직 정책 변경자
미승인 액션 추가uses: 소유자와 SHA 고정 여부
OIDC 발급 이상예상 밖 저장소·브랜치·환경의 토큰 요청
시크릿 접근 증가새 작업·재사용 워크플로의 시크릿 참조
자체 러너 이상외부 통신, 장기 프로세스, 새 파일, 권한 상승
아티팩트 변경빌드 후 해시 불일치와 출처 없는 업로드
보호 규칙 우회관리자 우회, 긴급 승인, 직접 운영 배포
폐기된 키 사용숨은 워크플로 또는 침해자의 재사용 시도

저장소 감사 로그, Actions 실행 로그, 클라우드 감사 로그를 공통 실행 ID와 커밋 SHA로 연결한다. SIEM에는 토큰 원문이 아니라 저장소, 워크플로, 작업, 행위자, 러너, 대상 환경, 권한 집합을 보낸다.

사고 대응 순서

  1. 영향을 받은 워크플로와 커밋, 실행 ID를 고정한다.
  2. 실행을 중단하고 운영 Environment 배포를 일시 정지한다.
  3. 노출된 시크릿·PAT·클라우드 키를 폐기한다.
  4. GITHUB_TOKEN으로 수행된 변경과 릴리스·패키지를 조사한다.
  5. 외부 액션, 재사용 워크플로, 자체 러너의 변경을 확인한다.
  6. 의심 아티팩트와 이미지의 배포를 차단한다.
  7. 클라우드·레지스트리·저장소 감사 로그를 보존한다.
  8. 깨끗한 커밋과 러너 이미지에서 다시 빌드한다.
  9. 복구 후 권한 축소와 회귀 테스트를 추가한다.

액션 태그나 공급망 구성요소가 침해됐다고 의심되면 동일 액션을 사용하는 모든 저장소를 검색한다. 한 저장소의 워크플로 수정만으로 끝내지 않는다.

리뷰 체크리스트

  • 최상단 permissions가 비어 있거나 최소 읽기 권한이다.
  • 쓰기 권한은 필요한 작업에만 명시됐다.
  • 제3자 액션과 재사용 워크플로가 전체 커밋 SHA로 고정됐다.
  • 포크 PR은 시크릿과 쓰기 토큰 없이 실행된다.
  • pull_request_target에서 PR 코드를 체크아웃해 실행하지 않는다.
  • 신뢰하지 않는 컨텍스트를 셸 명령에 직접 삽입하지 않는다.
  • 운영 배포는 보호된 Environment와 승인 절차를 사용한다.
  • 장기 클라우드 키 대신 제한된 OIDC 신뢰를 사용한다.
  • 자체 호스팅 러너는 일회성·분리·최소 네트워크 원칙을 따른다.
  • 아티팩트의 커밋, 해시, 빌드 출처를 배포 전에 검증한다.
  • 시크릿 폐기와 공급망 사고 대응 런북이 시험됐다.

참고 기준

GitHub Actions 보안 강화는 액션을 몇 개 교체하는 작업이 아니다. 토큰 권한, 이벤트 신뢰 경계, 외부 코드, 러너, 시크릿, 아티팩트를 한 흐름으로 봐야 한다. 기본 권한을 닫고 필요한 작업만 열면 워크플로가 침해되더라도 저장소와 운영 환경으로 번지는 범위를 줄일 수 있다.

전체 댓글 0

댓글을 불러오는 중입니다...
새로고침

태그

GitHub Actions CI Security Supply Chain

공유하기

관련 기사