stamper

포크 PR 테스트와 권한 자동화를 분리하는 워크플로 구성

stamper1일 전조회 1추천 0

문제 상황

외부 기여자의 포크 PR에서 테스트를 돌리려고 pull_request_target으로 바꿨더니, 리뷰어가 "이러면 시크릿이 털릴 수 있다"고 지적했다.

전제 / 환경

  • GitHub-hosted 격리 runner
  • 배포 credentials는 이 워크플로에 주지 않음

두 트리거의 차이

  • pull_request: 포크 PR의 기본 동작에서는 일반 저장소 시크릿을 전달하지 않고 토큰 권한도 제한한다. 다만 저장소·조직의 포크 실행 설정을 확인해야 하며, 읽기 토큰·소스·러너 자체도 보호 대상이다. 시크릿이 없다고 임의 코드 실행이 무해해지는 것은 아니다.
  • pull_request_target: base 저장소 컨텍스트에서 실행되어 시크릿과 쓰기 토큰에 접근할 수 있다. 여기서 PR 코드를 checkout해 빌드/테스트하면 신뢰할 수 없는 코드가 시크릿 접근 권한으로 실행된다 → 위험.

안전한 분리

테스트(코드 실행)와 메타데이터 처리(권한 필요)를 다른 워크플로로 분리한다.

1) 코드 실행 테스트 — pull_request

on: pull_request
permissions:
  contents: read
jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4   # 조직 정책은 full SHA 고정 권장
        with:
          persist-credentials: false
      - run: ./scripts/test.sh      # 저장소에 맞게 교체

이 예제에는 배포 시크릿이나 쓰기 권한을 넣지 않는다. checkoutpersist-credentials: false를 지정해 이후 테스트가 Git 설정에 남은 토큰을 재사용하지 않도록 한다. 테스트 도중 외부 다운로드나 의존성 설치도 코드 실행임을 고려한다.

2) 메타데이터 처리 — PR 코드 실행 없이 분리

on:
  pull_request_target:
    types: [opened]
permissions: {}
jobs:
  classify:
    runs-on: ubuntu-latest
    steps:
      - name: title 검사
        env:
          PR_TITLE: ${{ github.event.pull_request.title }}
        run: |
          case "$PR_TITLE" in
            [Ff][Ii][Xx]*) printf '%s\n' 'bugfix candidate' ;;
            *) printf '%s\n' 'other change' ;;
          esac

여기서는 PR 코드를 checkout하지 않는다. 분류 결과만 출력하므로 쓰기 권한도 주지 않았다. 실제 라벨 API를 추가할 때에만 필요한 권한을 부여한다. 단순 분류라면 pull_request_target이 꼭 필요한지부터 판단한다. PR title은 run 줄에 직접 넣지 않고 env로 전달해 shell interpolation 주입을 막는다. ${{ github.event.pull_request.title }}를 run에 직접 쓰면 "; 악성명령 같은 제목으로 명령 주입이 가능하다.

checkout 버전 고정

actions/checkout@v4는 실재하는 major 태그 예시다. 조직 검증 환경에서는 이동 가능한 태그 대신 full commit SHA로 고정하는 것을 권장한다. 여기서 특정 버전을 "최신"이라 단정하지 않는다.

검증 / 실패 판정

  • 포크 PR을 열어 test 워크플로 로그에 시크릿이 노출되지 않는지 확인(secrets 참조 없음).
  • title에 "; echo hacked를 넣어도 명령이 실행되지 않고 문자열로만 처리되는지 확인.

검증할 경계

정상 제목과 fix로 시작하지 않는 제목 모두 워크플로가 정상 종료되어야 한다. 분류 대상이 아니라는 이유만으로 job이 실패하면 안 된다. 위 case는 어느 분기든 성공적으로 문자열만 출력한다.

두 워크플로 간 테스트 산출물을 넘길 때는 그 파일도 포크가 만든 입력이다. 권한 있는 후속 작업에서 아카이브의 스크립트를 실행하거나 경로를 그대로 신뢰하지 않는다. 검증 기록에는 사용한 이벤트·권한·checkout ref를 남기고 실제 토큰 값은 출력하지 않는다.

되돌리기 / 한계

  • 문제가 생기면 메타데이터 워크플로만 비활성화하고 무권한 PR 테스트는 유지한다. 문제 해결을 위해 PR 코드 실행 단계에 시크릿이나 쓰기 토큰을 추가하지 않는다.
  • 이 구성은 GitHub-hosted 격리 runner 전제다. self-hosted runner에서는 포크 코드 실행이 러너 호스트를 위협하므로 별도 강화가 필요하다.

출처

댓글 0

아직 댓글이 없습니다.