증상: 분명히 설정했는데 값이 없다고 한다
GitHub Actions 워크플로우는 돌리면 이런 상황이 생긴다.
Error: API_KEY is undefined
Error: Cannot read properties of undefined (reading 'length')
아니면 시크릿 값이 빈 문자열로 들어오거나, 더 황당하게는 배포가 그냥 조용히 실패한다. Secrets 탭에서 분명히 등록했는데 워크플로우가 못 읽는다.
CI 처음 세팅할 때, 또는 레포를 포크하거나 환경(Environment)을 새로 만들었을 때 이 문제를 자주 만난다.
원인 분류
1. 시크릿 이름 대소문자 불일치
가장 흔한 실수. Secrets UI에서 API_KEY로 등록했는데 워크플로우에서 ${{ secrets.api_key }}로 참조하면 빈 값이 온다. 시크릿은 대소문자 구분한다.
# 잘못된 예
env:
API_KEY: ${{ secrets.api_key }} # 실제 이름이 API_KEY면 못 읽음
# 올바른 예
env:
API_KEY: ${{ secrets.API_KEY }}
2. Environment 시크릿인데 job에 environment 지정 안 함
GitHub에서 환경(Environment)을 별도로 만들어서 거기에 시크릿을 등록했다면, job에서 그 environment를 명시해야 한다. 안 하면 해당 시크릿은 아예 조회 자체가 안 된다.
# 잘못된 예 — environment 지정 없이 env 시크릿 접근 시도
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- run: echo ${{ secrets.PROD_DB_URL }} # production environment의 시크릿이면 빈 값
# 올바른 예
jobs:
deploy:
runs-on: ubuntu-latest
environment: production # ← 이게 있어야 environment 시크릿 접근 가능
steps:
- run: echo ${{ secrets.PROD_DB_URL }}
3. Fork PR에서는 시크릿 접근이 차단된다
외부 기여자가 fork해서 올린 PR의 워크플로우는 보안 정책상 시크릿에 접근할 수 없다. pull_request 이벤트 트리거를 쓰면 이 제한이 적용된다.
Warning: Context access might be invalid: secrets
의도적인 제한이다. 악의적인 코드가 PR로 들어와서 시크릿을 탈취하는 걸 막기 위한 것.
4. 시크릿 참조 문법 오류
# 틀린 문법들
${{ secret.API_KEY }} # secrets가 아니라 secret (오타)
${{ secrets[API_KEY] }} # 대괄호 안에 따옴표 필요
${{ env.secrets.API_KEY }} # env와 secrets 혼용
# 올바른 문법
${{ secrets.API_KEY }}
${{ secrets[env.SECRET_NAME] }} # 동적 키 참조는 이렇게
해결 방법
Step 1. 시크릿 이름 확인
# GitHub CLI로 현재 등록된 시크릿 목록 확인
gh secret list
# environment 시크릿 확인
gh secret list --env production
워크플로우 YAML의 이름과 정확히 일치하� 체크.
Step 2. 시크릿이 실제로 전달되는지 테스트
값을 직접 출력하면 안 된다 (마스킹됨). 대신�길이나 존재 여부를 확인:
steps:
- name: Check secrets
run: |
if [ -z "${{ secrets.API_KEY }}" ]; then
echo "API_KEY is EMPTY"
else
echo "API_KEY is set (length: ${#API_KEY})"
fi
env:
API_KEY: ${{ secrets.API_KEY }}
Step 3. Environment 시크릿 설정
레포 Settings → Environments → 환경 선택 → Environment secrets에서 등록했다면:
jobs:
deploy:
runs-on: ubuntu-latest
environment: production # 반드시 명시
steps:
- name: Deploy
env:
DB_URL: ${{ secrets.DB_URL }}
API_KEY: ${{ secrets.API_KEY }}
run: ./deploy.sh
environment 이름은 Environments 탭에 있는 이름과 정확히 일치해야 한다.
Step 4. Fork PR 시크릿 접근 처리
내부 PR이라면 pull_request_target 이벤트 사용을 고려할 수 있다. 단, pull_request_target은 base 브랜치의 코드를 실행하므로 PR 코드를 checkout해서 실행하면 보안 취약점이 된다.
# 안전한 패턴 — PR 코드는 테스트만 하고, 배포는 main 병합 후에
on:
pull_request:
# 시크릿 없이 테스트만
push:
branches: [main]
# 여기서 시크릿 써서 배포
Step 5. Organization 시크릿 접근 권한
조직(organization) 레벨 시크릿은 레포별로 접근 권한이 따로 있다. 조직 Settings → Secrets → 해당 시크릿 → Repository access에서 해당 레포가 포함되어 있는지 확인.
상황별 체크리스트
| 증상 | 원인 | 조치 |
|---|---|---|
| 시크릿 값이 빈 문자열 | 이름 대소문자 불일치 | gh secret list로 정확한 이름 확인 |
| environment 시크릿 못 읽음 | job에 environment 미지정 | environment: 필드 추가 |
| Fork PR에서만 실패 | 보안 정책으로 차단 | 시크릿 없이 동작하도록 CI 설계 변경 |
| 조직 시크릿 못 읽음 | 레포 접근 권한 없음 | Organization 설정에서 레포 추가 |
| 로컬에서는 되는데 CI에서만 | 환경변수 주입 누락 | env: 블록에서 명시적 주입 확인 |
자주 하는 실수 — 디버깅 시 값 출력하려다 마스킹에 막히는 경우
# 이렇게 하면 *** 로 마스킹되어 아무 의미 없음
- run: echo ${{ secrets.API_KEY }}
# 디버깅 목적이면 이렇게
- run: |
echo "Length: ${#MY_SECRET}"
echo "First char: ${MY_SECRET:0:1}"
env:
MY_SECRET: ${{ secrets.API_KEY }}
시크릿 값 자체를 로그에 출력하는 건 GitHub이 자동 마스킹한다. 길이나 첫 글자 정도로 존재 여부 확인하는 게 현실적인 디버깅 방법이다.
실무 팁: 시크릿 관리 패턴
# 공통 시크릿은 레포 레벨에
# 환경별 시크릿(prod DB URL 등)은 environment 레벨에 분리
jobs:
test:
runs-on: ubuntu-latest
# environment 없음 — 레포 레벨 시크릿만 접근
steps:
- run: npm test
env:
NPM_TOKEN: ${{ secrets.NPM_TOKEN }} # 레포 레벨 시크릿
deploy-prod:
runs-on: ubuntu-latest
environment: production # environment 레벨 시크릿 접근
needs: test
steps:
- run: ./deploy.sh
env:
DB_URL: ${{ secrets.DB_URL }} # production environment 시크릿
API_KEY: ${{ secrets.API_KEY }} # production environment 시크릿
시크릿을 환경별로 분리하면 실수로 개발용 키를 프로덕션에 쓰는 사고를 막을 수 있다.
정리
GitHub Actions 시크릿이 안 읽히는 케이스는 이름 대소문자 불일치, environment 미지정, fork PR 보안 정책 세 가지가 대부분을 차지한다. gh secret list로 정확한 이름 확인하고, environment 시크릿이면 job에 environment: 명시하는 것만 체크해도 80%는 해결된다.
댓글 없음:
댓글 쓰기