GitHub Actions Lockfiles (actions.lock) GA: 가변 태그(@v4) 하이재킹을 막는 불변 SHA 잠금과 전이 종속성 검증
2026년 11월 정식 출시(GA)된 GitHub Actions 워크플로 종속성 락파일(actions.lock)을 해부한다. 가변 태그 변조(Retagging) 공격 원리, 복합 액션(Composite Action)의 전이 의존성 재귀 잠금, gh actions-lock --verify CI 검사, Dependabot 원자적 동기화, 6개 검증 시뮬레이터를 정리한다.
GitHub Actions 워크플로 종속성 락파일(actions.lock)의 불변 커밋 SHA 잠금과 CI/CD 무결성 보호 아키텍처를 나타낸 썸네일이다.
현대 소프트웨어 개발에서 CI/CD 파이프라인은 빌드·테스트·배포의 핵심 심장부다. 특히 GitHub Actions 생태계는 마켓플레이스에 공개된 수만 개의 오픈소스 제3자(Third-party) 액션을 단 한 줄의 uses: 구문으로 끌어다 쓸 수 있는 압도적인 생산성을 제공한다. 그러나 대부분의 프로젝트가 actions/checkout@v4나 docker/setup-buildx-action@v3처럼 가변 버전 태그(Mutable Version Tag)를 참조하고 있다는 사실은 심각한 소프트웨어 공급망 보안(Software Supply Chain Security) 구멍을 방치하는 것과 같다. Git의 태그는 커밋과 달리 언제든 git tag -f 명령으로 다른 커밋을 가리키도록 강제 이동(Retagging)할 수 있는 가변 참조자이기 때문이다. 결론부터 말하면, 2026년 11월 정식 출시(GA)된 GitHub Actions Lockfiles(.github/workflows/actions.lock)는 워크플로 YAML의 가독성 높은 버전 태그(@v4)를 그대로 유지하면서도 런타임에 불변 40자리 커밋 SHA를 강제 집행하고, 복합 액션(Composite Action)의 전이 의존성(Transitive Dependencies)까지 재귀적으로 고정하여 태그 하이재킹 공격을 원천 차단한다. GitHub Actions 공식 문서
만약 액션 메인테이너의 개인 액세스 토큰(PAT)이 탈취되거나 저장소 관리자 권한이 침해되면, 공격자는 기존에 널리 사용되던 @v4 태그를 악성 페이로드가 담긴 커밋으로 슬그머니 덮어쓸 수 있다. 이 경우 다운스트림 개발팀은 워크플로 코드를 단 한 글자도 바꾸지 않았음에도 불구하고, 다음 CI 빌드가 실행되는 순간 공격자의 악성 셸 스크립트가 실행되어 CI 러너의 GITHUB_TOKEN, AWS/GCP 배포 키, 내부 소스코드를 고스란히 탈취당하게 된다.
이 글은 가변 태그의 구조적 위험과 수동 Commit SHA 핀의 한계, GitHub Actions Lockfiles의 불변 해시 매핑 아키텍처, 복합 액션(Composite Action)의 전이 종속성 재귀 잠금 원리, gh actions-lock CLI와 CI 무결성 검증(--verify), Dependabot 원자적(Atomic) 동기화, 조직 거버넌스(Require Lockfile), 그리고 로컬 Python으로 직접 검증한 6개 시나리오와 실무 도입 체크리스트를 정리한다. 셀프 호스티드 러너(Self-hosted runner) 자체의 커널 레벨 microVM 격리나 OIDC 클라우드 임시 자격증명(AssumeRole) 교환 절차는 다루지 않는다.
1. 가변 태그의 위험성과 완전 SHA 수동 핀의 한계
지금까지 보안 표준(OpenSSF Scorecard 등)은 공급망 공격을 막기 위해 모든 액션을 40자리 커밋 SHA(예: actions/checkout@c85c95e3d7251135ab7dc9ce3241ca527f2e7083)로 직접 핀(Pin)할 것을 권장해 왔다. 하지만 이는 실무 현장에서 극심한 운영 마찰과 가독성 저하를 유발했으며, 결정적으로 '전이 의존성' 문제를 전혀 해결하지 못했다.
| 구분 항목 | 가변 태그(@v4) 직접 참조 | 수동 Commit SHA 핀 | GitHub Actions Lockfiles (actions.lock) |
| 가독성 및 관리 편의성 | 높음 (시맨틱 버전 직관적 확인) | 매우 낮음 (40자리 난수 해시 파편화) | 높음 (YAML은 @v4 유지, 락파일 분리) |
| 태그 변조(Retagging) 방어 | 불가능 (악성 커밋 즉시 풀 및 실행) | 가능 (단일 액션 커밋 고정) | 완벽 방어 (런타임 불변 SHA 강제 집행) |
| 전이 의존성(Transitive Deps) | 완전 무방비 노출 | 복합 액션 내부 전이 의존성 핀 불가 | 재귀적 트리 분석을 통한 전이 의존성 잠금 |
| 버전 업데이트 용이성 | 태그만 변경하면 자동 적용(보안 취약) | 워크플로 파일 전체 수동 커밋 필요 | Dependabot 단일 PR 원자적(Atomic) 동기화 |
| CI 무결성 검증 | 지원 불가 | 코드 리뷰 및 린터 정규식 의존 | gh actions-lock --verify로 CI 자동화 |
| 엔터프라이즈 거버넌스 | 조직 단위 강제 곤란 | 레포별 정적 분석 도구 도입 부담 | 'Require Lockfile' 정책으로 미잠금 워크플로 차단 |
가장 치명적인 맹점은 복합 액션(Composite Action)에 있다. 워크플로에서 직접 호출하는 메인 액션을 40자리 SHA로 핀해 두더라도, 그 액션이 내부 action.yml에서 또 다른 서드파티 액션을 가변 태그(예: @v2)로 호출하고 있다면 공급망 공격에 고스란히 노출된다. 개발자는 메인 액션의 내부 구현까지 일일이 열어보지 않는 한 전이 의존성의 오염 여부를 파악할 수 없다.
2. actions.lock 아키텍처와 전이 종속성 재귀 잠금
다음 다이어그램은 기존 가변 버전 태그를 사용했을 때 발생하는 업스트림 태그 변조 공격 및 전이 의존성 탈취 취약점(좌측)과, GitHub Actions Lockfiles가 불변 커밋 SHA를 강제하고 복합 액션 내부까지 재귀적으로 잠그는 방어 아키텍처(우측)를 비교한 도식이다.
기존 가변 버전 태그(@v4)의 공급망 변조 취약점(좌측)과 actions.lock 불변 SHA 강제 집행 및 전이 의존성 보호 아키텍처(우측)를 비교한 다이어그램이다.
1) YAML 가독성과 락파일 분리 메커니즘
Node.js 생태계의 package.json과 package-lock.json, Rust 생태계의 Cargo.toml과 Cargo.lock처럼, GitHub Actions도 선언부와 불변 잠금부를 분리했다.
- 워크플로 파일(
.github/workflows/*.yml): 개발자는 여전히uses: actions/checkout@v4와 같이 사람이 읽기 편한 시맨틱 버전 태그를 작성한다. 코드 리뷰 시 어떤 버전의 도구를 사용하는지 한눈에 파악할 수 있다.
- 락파일(
.github/workflows/actions.lock):gh actions-lock확장이 워크플로를 스캔하여@v4가 가리키는 정확한 40자리 Commit SHA(예:c85c95e3d7251135ab7dc9ce3241ca527f2e7083)와 내용 체크섬을 기록한다.
2) 복합 액션(Composite Action)의 전이 의존성 재귀 잠금
GitHub Actions Lockfiles의 핵심 혁신은 종속성 트리의 재귀적 해결(Recursive Resolution)이다.
- 메인 워크플로가 복합 액션
acme/deploy-pipeline@v1을 참조한다.
gh actions-lock은 해당 액션의 원격 리포지토리를 조회하고,action.yml내부에 정의된 중첩 액션들(예:aws-actions/configure-aws-credentials@v2)을 식별한다.
- 락파일에는 메인 액션뿐만 아니라 내부 전이 의존성까지
transitive via acme/deploy-pipeline메타데이터와 함께 불변 SHA로 고정된다.
- 따라서 업스트림 복합 액션이 참조하는 하위 모듈이 하이재킹되더라도, 락파일에 의해 안전한 커밋만 실행된다.
3) 런타임 강제 집행(Runtime Enforcement)
GitHub Actions 워크플로 러너는 잡(Job)을 시작할 때 작업 디렉토리에 actions.lock 파일이 존재하는지 검사한다. 락파일이 존재하고 온보딩된 워크플로라면, 워크플로 YAML에 @v4 태그가 적혀 있더라도 네트워크에서 태그를 다시 조회하지 않고 락파일에 명시된 커밋 SHA를 즉시 다운로드하여 실행한다. 업스트림에서 태그를 강제로 변경했더라도 러너는 이를 완전히 무시하므로 공급망 공격이 성립하지 않는다.
3. CI/CD 검증과 Dependabot 원자적 갱신 및 거버넌스 정책
1) gh actions-lock --verify 기반 CI 게이트키퍼
개발자가 워크플로 파일에 새로운 서드파티 액션을 추가해 두고 깜빡 잊고 락파일을 생성하지 않거나 갱신하지 않은 채 PR을 올리는 경우가 있다. 이를 방지하기 위해 gh actions-lock은 검증 모드를 지원한다.
- CI 파이프라인에서
gh actions-lock --verify를 실행하면 모든 워크플로의 선언과 락파일의 일치 여부를 대조한다.
- 락파일에 누락된 액션이 있거나, 워크플로의 태그와 락파일의 매핑이 불일치하면 즉시 0이 아닌 종료 코드(Non-zero Exit Code)를 반환하여 빌드를 중단시킨다.
- 이를 통해 신규 액션이 락파일 없이 프로덕션 워크플로로 머지되는 실수를 원천 차단한다.
2) Dependabot의 원자적(Atomic) 동시 업데이트
의존성 잠금을 도입했을 때 가장 우려되는 유지보수 부담은 버전 업그레이드다. GitHub Dependabot은 Actions Lockfiles를 완벽히 지원한다.
- 액션의 새 버전(예:
v4→v5)이 출시되면 Dependabot이 자동으로 업데이트 PR을 생성한다.
- 이때 Dependabot은 워크플로 YAML 파일의 태그를 변경함과 동시에
.github/workflows/actions.lock의 SHA 엔트리까지 단일 커밋에서 원자적으로(Atomically) 갱신한다.
- 개발팀은 두 파일이 함께 변경된 PR을 검토하고 테스트 통과를 확인한 뒤 머지하기만 하면 된다.
3) 조직 단위 'Require Lockfile' 거버넌스 정책
엔터프라이즈 환경에서는 개별 저장소의 자율성에만 의존할 수 없다. GitHub Enterprise 및 조직(Organization) 설정에서 "Require workflow lockfiles" 정책을 활성화하면, 락파일 없이 실행되는 모든 워크플로를 플랫폼 레벨에서 즉시 차단(Block)할 수 있다. 대규모 레포지토리의 경우 점진적 온보딩(Gradual Adoption) 옵션을 통해 특정 워크플로부터 순차적으로 적용해 나갈 수 있다.
4. 로컬 Python으로 구현·검증하는 Actions Lockfile 시뮬레이터
다음 코드는 외부 의존성 없이 표준 라이브러리만으로 가변 태그 변조 공격, actions.lock 불변 SHA 런타임 강제 집행, Composite Action의 전이 의존성 재귀 잠금, CI 검증(verify), Dependabot 원자적 갱신, 조직 거버넌스 정책을 시뮬레이션하고 검증하는 스크립트(actions_lockfile_security_sim.py)다.
직접 실행해 확인한 범위
로컬 환경에서 python actions_lockfile_security_sim.py를 실행해 다음 6개 검사가 모두 통과(6 checks passed)함을 확인했다.
- 기본 락파일 생성 및 가독성 분리 검증: 워크플로 선언부(
@v4)의 시맨틱 태그는 그대로 유지되면서, 락파일(actions.lock)에 불변 40자리 커밋 SHA(c85c95e3...)와 내용 체크섬 해시가 정상 추출·기록됨을 확인한다.
- 가변 태그 변조(Retagging) 공격 방어: 공격자가 업스트림 리포지토리의
@v4태그를 악성 커밋으로 강제 교체하더라도, 락파일이 존재하는 환경에서는 런타임이 이를 무시하고 원본 안전 커밋 SHA를 강제 실행(LOCKED_IMMUTABLE_SHA)하여 침해를 원천 차단한다.
- 복합 액션(Composite Action) 전이 종속성 재귀 잠금: 복합 액션 내부
action.yml에 숨겨진 하위 액션(aws-actions/configure-aws-credentials@v2)까지 종속성 트리를 재귀 추적하여actions.lock에 불변 SHA로 잠그는 동작을 검증한다.
- CI 검증 모드(
gh actions-lock --verify) 게이트키퍼: 워크플로 파일에 신규 액션이 선언되었으나 락파일에 엔트리가 누락되었거나 변조된 경우, CI 검사에서 즉시 불일치를 감지하고 오류(Missing lockfile entry)를 반환함을 확인한다.
- Dependabot 원자적(Atomic) 동시 업데이트 검증: 액션의 신규 메이저 버전(
v5) 배포 시 Dependabot이 워크플로 태그와 락파일의 커밋 SHA를 하나의 트랜잭션에서 동시에 동기화함을 검증한다.
- Require Lockfile 엔터프라이즈 거버넌스 정책: 조직 단위 보안 정책이 켜져 있을 때 락파일이 없는 워크플로의 실행을 플랫폼 레벨에서 즉시 거부(
PermissionError)하고, 락파일 온보딩 완료 시에만 안전하게 파이프라인이 가동됨을 확인한다.
5. Actions Lockfiles 실무 도입 체크리스트
- GitHub CLI 공식 확장 설치: 개발 머신 및 CI 러너에
gh actions-lock확장을 설치한다:
- 기존 워크플로 락파일 생성 및 온보딩: 저장소 루트에서 락파일 생성 명령을 실행하여 현재 사용 중인 모든 액션의 불변 커밋 SHA를
.github/workflows/actions.lock에 기록한다:
- CI 무결성 검증 워크플로(Verify Job) 등록: PR 검증 파이프라인에 락파일 동기화 여부를 검사하는 단계를 추가하여 락파일 누락 머지를 방지한다:
- Dependabot 설정 점검(
dependabot.yml):github-actions패키지 에코시스템이 활성화되어 있는지 확인한다. Dependabot은actions.lock을 자동으로 감지하여 버전 업데이트 시 락파일을 함께 갱신한다:
- 조직(Organization) 레벨 Require Lockfile 정책 검토: 팀 내 워크플로 온보딩이 완료된 후 GitHub 조직 설정(Organization Settings → Actions → General → Security)에서 "Require lockfile" 정책을 활성화하여 규정 준수를 자동화한다.
참고 자료
확인 기준일: November 6, 2026