Huny's Dev Blog
huny.dev

GitHub Actions 워크플로 로그 시크릿 스캐닝 해부: 런타임 마스킹(::add-mask::)과 사후 스캔의 5가지 사각지대 점검하기

11월 9일 공개된 GitHub Actions 워크플로 로그 시크릿 스캐닝(퍼블릭 프리뷰)의 동작 원리와 5가지 프리뷰 제약 사항을 정리한다. 런타임 마스킹(::add-mask::)과의 차이, 커스텀 패턴·무알림 사각지대 및 안전한 동적 토큰 처리 패턴을 살펴본다.

Hun Jang
Hun Jang Nov 11, 2026

1단계 런타임 문자열 마스킹(::add-mask::)을 빠져나간 동적 토큰을 2단계 실행 종료 후 워크플로 로그 스캐너가 공인 공급자 패턴으로 탐지해 영속적인 경고로 기록하는 흐름을 나타낸 개념 일러스트다. 실제 콘솔 화면이 아니다.

2026년 11월 9일 GitHub가 기존 소스 코드, 이슈, 위키, PR에 더해 GitHub Actions 워크플로 실행 로그(Workflow Logs)에 노출된 시크릿까지 자동으로 탐지하는 기능을 퍼블릭 프리뷰(Public Preview)로 공개했다. 워크플로 실행(Run)이 완료되면 GitHub가 로그에서 주요 공급자 패턴(Provider patterns)을 스캔하고 유효성 검사(Validity checks)까지 수행해 저장소의 시크릿 스캐닝 경고 목록에 등록하며, 이후 워크플로 로그를 삭제하더라도 경고는 사라지지 않고 유지된다. GitHub Changelog 공식 발표, GitHub Enterprise Cloud 보안 정책 문서

결론부터 말하면 이 기능은 런타임에 동적 생성·변형되어 기본 마스킹(***)을 빠져나간 공인 공급자 토큰을 사후에 잡아내는 안전망일 뿐, 모든 유출을 찾아주거나 실시간 알림을 보내주지는 않는다. 프리뷰 단계에서는 ① 일반 패턴(Generic), 사내 커스텀 정규식(Custom patterns), AI 기반 시크릿 탐지가 로그 스캔에서 제외되고, ② 기능 활성화 이전에 끝난 과거 로그는 소급 스캔(Backfill)되지 않으며, ③ 경고가 생성되어도 이메일·웹훅 등 별도 알림(Notification)이 발송되지 않으므로 저장소 경고 목록을 직접 조회해야 한다. 따라서 워크플로에서 동적 토큰을 발급받을 때는 반드시 출력·전달 전에 ::add-mask::를 먼저 선언하는 선제 방어와 사후 로그 스캔을 병행해야 한다.

이 글은 GitHub Changelog 발표문과 GitHub Enterprise 보안 정책 가이드, CI 로그 마스킹 기술 분석을 교차 검증해 정리한 운영 가이드다. 실제 GitHub Enterprise Cloud 조직에 유출 토큰을 주입하는 실험은 수행하지 않았으며, 로컬 Python 검증 코드는 출력 시점 마스킹(Print-time redaction)과 실행 종료 후 스캔(Post-run scanning)의 탐지 경계를 시뮬레이션한다.

1. 왜 기본 시크릿 마스킹(***)이 있는데도 워크플로 로그에서 키가 유출되는가

GitHub Actions 러너는 저장소·조직·환경 시크릿(secrets.*)이나 ::add-mask:: 워크플로 명령으로 등록된 문자열을 콘솔에 출력하기 전에 ***로 치환한다. 그럼에도 실무에서 워크플로 로그 유출 사고가 반복되는 이유는 런타임 마스킹이 ‘등록된 정확한 문자열 일치(Exact-string match)’에만 의존하기 때문이다.

구분 항목
1단계: 러너 출력 시점 마스킹 (Print-time Redaction)
2단계: 워크플로 완료 후 로그 시크릿 스캐닝 (Post-run Log Scanning)
동작 시점
워크플로 스텝 실행 중 표준 출력/오류 스트림이 기록되는 즉시 동작
워크플로 실행(Run)이 완전히 종료된 직후 로그 전체를 대상으로 스캔 수행
탐지·치환 기준
secrets.* 컨텍스트 또는 ::add-mask::로 사전 등록된 문자열과 정확히 일치하는 값만 ***로 치환
마스킹되지 않고 로그 텍스트에 그대로 남은 공인 공급자 패턴(Provider patterns)을 정규식·구조로 탐지하고 유효성 검사(Validity check) 수행
놓치는 대표적 사각지대
OIDC·리프레시 토큰으로 런타임에 새로 발급받은 클라우드/API 토큰, Base64·URL 디코딩이나 슬라이싱으로 변형된 값, ::add-mask:: 호출 전에 set -x나 디버그 플래그가 먼저 출력한 값
일반(Generic) 비밀번호, 사내 커스텀 패턴(Custom patterns), AI 탐지 시크릿, 기능 활성화 이전의 과거 로그 (단, 이미 ***로 마스킹된 값은 스캔 대상에서 제외됨)
노출 범위 및 로그 삭제 후 동작
마스킹에 성공하면 로그 열람 권한자도 원본 값을 볼 수 없음
일단 로그에 찍히면 로그 열람 권한자(보통 시크릿 관리자보다 훨씬 넓은 범위)에게 노출됨. 나중에 워크플로 로그를 삭제해도 생성된 경고는 삭제되지 않고 유지됨

특히 “로그를 지웠으니 해결됐다”고 착각하는 실수를 막기 위해, GitHub는 관리자가 노출된 워크플로 실행 로그를 삭제하더라도 시크릿 스캐닝 경고와 원본 로그 위치 링크 기록을 닫지 않고 유지한다. 로그 삭제는 키 폐기(Revocation)나 교체(Rotation)를 대체하지 못하기 때문이다.

2. 퍼블릭 프리뷰 활성화 조건과 반드시 알아야 할 5가지 제약 사항

이번 기능은 저장소 YAML 파일을 수정할 필요 없이 엔터프라이즈 정책에서 켤 수 있지만, 라이선스 요건과 프리뷰 기간의 다섯 가지 제약을 정확히 숙지해야 한다.

점검 항목
지원 범위 및 활성화 방법
프리뷰 기간의 제약 사항 및 주의점
1. 지원 플랜 및 라이선스
GitHub Enterprise Cloud(GHE.com 데이터 레지던시 포함)에서 GitHub Secret Protection 또는 GitHub Advanced Security(GHAS) 라이선스를 보유한 엔터프라이즈
기본값은 비활성화(Disabled) 상태이며, 개별 저장소에 시크릿 스캐닝이 켜져 있어야만 해당 저장소의 워크플로 로그가 스캔됨
2. 활성화 경로
Enterprise Settings → Policies → Advanced Security → Secret scanning for Actions workflow logs 드롭다운에서 Enabled 선택
조직이나 개별 저장소 단위가 아니라 엔터프라이즈 소유자(Enterprise Owner)가 정책으로 일괄 활성화함
3. 과거 로그 소급(Backfill) 여부
정책을 Enabled로 켠 이후에 완료되는 신규 워크플로 실행만 스캔함
활성화 이전에 이미 완료된 과거 워크플로 로그는 자동으로 다시 스캔하지 않음
4. 탐지 패턴 범위
GitHub 토큰, AWS 키 등 파트너·공급자 고정 패턴(Provider patterns) 탐지 및 활성 여부(Validity check) 확인
Generic 패턴, 조직/엔터프라이즈 Custom 패턴, AI 탐지 시크릿, 이미 Actions가 마스킹한 값은 탐지하지 않음
5. 경고 알림(Notification) 동작
저장소의 Security → Secret scanning alerts 목록에 코드 유출 경고와 함께 표시됨
프리뷰 기간에는 워크플로 로그 유출 경고가 이메일·웹훅 등 자동 알림을 발생시키지 않음. 보안팀이 경고 목록이나 REST API를 주기적으로 직접 확인해야 함

3. 동적 토큰 유출을 원천 차단하는 워크플로 작성 패턴: “마스킹 먼저, 내보내기는 나중에”

사후 스캐너에 경고가 뜨지 않게 하려면 런타임에 발급받는 단기 토큰(예: 내부 Vault 토큰, OAuth 교환 토큰, 사내 커스텀 키)을 어떠한 로그나 환경 변수 파일에 쓰기 전에 ::add-mask::로 먼저 등록해야 한다.

아래는 실무에서 가장 흔히 발생하는 유출 패턴과 이를 막는 안전한 워크플로 작성 패턴이다.

또한 이번 프리뷰 기간에는 워크플로 로그 경고가 별도 알림을 보내지 않으므로, GitHub CLI(gh api)로 저장소 또는 엔터프라이즈의 미해결 시크릿 스캐닝 경고를 주기적으로 조회하는 스크립트를 두는 편이 좋다.

4. 로컬 Python으로 검증하는 런타임 마스킹 vs 사후 로그 스캔 경계

출력 시점의 ::add-mask:: 호출 순서와 사후 로그 스캐너의 패턴 범위(공급자 패턴만 탐지하고 사내 커스텀 토큰·과거 로그는 제외)가 어떻게 맞물리는지 로컬 Python 시뮬레이터로 검증했다.

직접 실행해 확인한 범위

로컬 환경에서 네 가지 대표 시나리오를 실행해 경계를 확인했다.

  1. ::add-mask::보다 출력이 먼저 발생한 공인 토큰(ghp_...): 1단계 마스킹을 통과해 첫 줄 로그에 그대로 노출되지만, 실행 종료 후 2단계 스캐너가 github_pat 경고를 생성한다(단, notification_sent: False).
  1. 출력 전에 ::add-mask::를 먼저 선언한 경우: 로그에 echo 'Token: ***'로 기록되며, 이미 마스킹된 값이므로 2단계 사후 스캐너에서도 불필요한 경고가 발생하지 않는다(alerts: []).
  1. 사내 커스텀 형식 토큰(INTERNAL_TOKEN_...) 노출: 마스킹 없이 출력하더라도 워크플로 로그 스캐너는 커스텀·일반 패턴을 지원하지 않으므로 경고가 0건이다. 즉 사내 자체 토큰은 반드시 ::add-mask::로 선제 차단해야 한다.
  1. 정책 활성화 이전의 과거 실행 로그(is_historical_run=True): 소급 스캔이 지원되지 않으므로 경고가 생성되지 않는다.

5. 실무 보안 점검 체크리스트

  • 엔터프라이즈 정책 수동 활성화: 기본값이 비활성화이므로 Enterprise Settings → Policies → Advanced Security에서 Secret scanning for Actions workflow logs를 Enabled로 전환한다.
  • 프리뷰 기간 경고 목록 정기 폴링: 워크플로 로그에서 탐지된 시크릿은 자동 알림이 오지 않으므로 보안 대시보드나 REST API로 저장소 경고 목록을 직접 점검한다.
  • 커스텀·내부 토큰에 대한 ::add-mask:: 강제 및 OIDC 전환: 사내 자체 토큰이나 일반 비밀번호는 로그 스캐너가 잡아내지 못하므로 printenv나 무분별한 set -x를 제거하고, 장기 클라우드 키 대신 GitHub OIDC(aws-actions/configure-aws-credentials 등 — 내부적으로 발급 즉시 자동 마스킹 수행)를 사용한다.

참고 자료

확인 기준일: November 11, 2026

English version: GitHub Secret Scanning for Actions Workflow Logs: Separate Print-Time Masking (::add-mask::) from Post-Run Scanning Blind SpotsGitHub Secret Scanning for Actions Workflow Logs: Separate Print-Time Masking (::add-mask::) from Post-Run Scanning Blind Spots

추천 글