- uses: actions/checkout@v4 # 태그 — 옮길 수 있다
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # SHA — 불변
공식 문서가 못 박는다 — 전체 길이 커밋 SHA 로 고정하는 것이 액션을 불변 릴리스로 쓰는 유일한 방법이다.
태그가 왜 약속이 아닌가
Git 태그는 특정 커밋을 가리키는 이름표일 뿐이고 언제든 다른 커밋으로 옮길 수 있다. v4 는 관례상 v4 계열의 최신을 가리키도록 관리자가 계속 옮긴다 — 즉 옮기는 것이 정상 동작이다.
그래서 액션 저장소가 침해되면 공격자는 v4 를 자기 커밋으로 옮기기만 하면 된다. 그 순간 그 액션을 쓰는 모든 저장소가 다음 실행부터 공격자 코드를 돌린다. 우리 쪽은 아무것도 바꾸지 않았는데 말이다.
이것이 공급망 공격이 무서운 이유다 — 한 곳을 뚫으면 그것을 신뢰하는 전부에 닿는다. 실제로 널리 쓰이던 액션이 침해돼 시크릿이 대량 유출된 사건이 있었다.
그럼 업데이트는 어떻게 하나
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
SHA 뒤에 버전을 주석으로 남긴다. Dependabot 이 이 주석을 읽어 새 버전의 SHA 로 갱신하는 PR 을 만들어 준다. 고정이 곧 방치가 아니게 되고, 갱신이 리뷰를 거치는 변경으로 바뀐다.
사고는 이렇게 번진다
① 인기 액션의 관리자 계정·토큰이 침해된다
② 공격자가 악성 커밋을 밀고 v3 태그를 그리로 옮긴다
③ 그 액션을 쓰는 저장소들이 다음 실행부터 그 코드를 돌린다
④ 워크플로 메모리에서 시크릿을 긁어 로그에 흘린다
⑤ 로그가 공개된 저장소에서는 누구나 읽을 수 있다
우리 저장소는 아무것도 바뀌지 않았는데 시크릿이 나간다. 코드 리뷰로도, 우리 쪽 테스트로도 걸리지 않는다. 신뢰의 방향이 바깥을 향하고 있기 때문이다.
SHA 로 고정돼 있었다면 ②가 우리에게 닿지 않는다. 태그가 어디를 가리키든 우리는 그 커밋만 실행한다.
조직 차원에서 조이기
- 허용 액션 목록 — 검증된 게시자와 지정한 액션만 쓰게 한다
- 재사용 워크플로 — 표준 파이프라인을 한곳에서 관리하면 고정과 권한 설정을 한 번에 강제할 수 있다. 저장소마다 확인할 필요가 없어진다
- 로그 감사 — 시크릿을 쓴 뒤의 로그를 주기적으로 본다
고정의 한계도 안다
SHA 고정은 그 액션이 다시 실행하는 것까지 막아 주지 않는다. 액션 내부가 설치 시점에 패키지를 받아 온다면 그 경로는 여전히 열려 있다. 그래서 중요한 파이프라인에서는 의존성 자체도 잠금 파일과 무결성 해시로 고정한다.
어디까지 고정하나
- 서드파티 액션 — 반드시 고정한다
actions/공식 액션 — 조직 정책에 따르되, 엄격한 곳은 함께 고정한다- 컨테이너 이미지 — 같은 논리가 그대로 적용된다.
latest대신 다이제스트로 고정한다
조직 설정에서 허용 액션 목록을 제한하면 한 겹 더 좁힐 수 있다.
면접 함정
- ❌ "버전 태그를 쓰면 고정된 것" → 태그는 옮겨진다. SHA 만 불변이다.
- ❌ "짧은 SHA 로도 충분" → 전체 길이를 쓰라는 것이 공식 권장이다.
- ❌ "고정하면 보안 패치를 못 받는다" → Dependabot 이 갱신 PR 을 만든다. 받되 검토하고 받는 것이다.