가이드

깃허브 Actions 시크릿 넣기: 조건부 단계에서 반드시 걸리는 함정

읽는 데 약 8분#GitHub Actions#가이드#자동화#트러블슈팅

한줄 요약: 깃허브 Actions에 비밀값(시크릿)을 넣는 것 자체는 쉽습니다. 사람들이 걸리는 곳은 그다음, 조건부 단계를 쓸 때입니다. if: 조건 안에서 시크릿을 직접 쓰면 워크플로가 0초 만에 실패합니다. 공식 문서가 이 제약을 명시하고 있고, 우회법도 한 줄입니다. 이 글은 넣는 절차보다 이 함정을 먼저 다룹니다.

조건부 단계에서 이렇게 막힙니다

“시크릿이 있으면 메일을 보내고, 없으면 그 단계를 건너뛴다”처럼 조건에 따라 실행 여부를 정하는 스텝은 비개발 직장인이 자동화를 만들 때 흔히 쓰게 됩니다. 바로 이 자리에서 워크플로 파일의 첫 push가 죽습니다.

실제로 겪은 화면입니다. 워크플로 파일을 처음 push했더니 0초 만에 실패했습니다.

Invalid workflow file: .github/workflows/briefing.yml#L1 (Line: 41, Col: 13): Unrecognized named-value: ‘secrets’. Located at position 1 within expression: secrets.GMAIL_APP_PASSWORD != ‘’

실패한 깃허브 Actions 실행 화면. Status는 Failure, Total duration은 비어 있고, Annotations 영역에 1 error와 함께 Invalid workflow file 에러 원문이 보인다
실제로 뜬 실패 화면입니다. Total duration이 비어 있는 것에 주목하세요

이 실행에는 잡(job, 워크플로 안의 실행 단위)이 하나도 만들어지지 않았습니다. 러너가 켜지기도 전에, 워크플로 파일을 읽는 단계에서 죽었다는 뜻입니다. 그래서 “0초 만에 실패”입니다. 코드 내용이 틀린 게 아니라, 문법이 애초에 허용하지 않는 자리에 시크릿을 썼기 때문입니다.

깃허브 공식 문서의 컨텍스트 표에 이 제약이 그대로 나와 있습니다. 표의 허용 목록에서, 스텝(step, 잡 안에서 순서대로 실행되는 한 줄 한 줄)의 if 자리에는 secrets가 없습니다.

쓰는 위치 secrets env
jobs.<job_id>.env (잡 레벨) 사용 가능 해당 없음
jobs.<job_id>.steps.env (스텝 레벨) 사용 가능 사용 가능
jobs.<job_id>.steps.run (스텝 실행) 사용 가능 사용 가능
jobs.<job_id>.steps.if (스텝 조건) 사용 불가 사용 가능
jobs.<job_id>.if (잡 조건) 사용 불가 사용 불가

표의 마지막 줄을 눈여겨보세요. 잡 레벨의 if(jobs.<job_id>.if)는 secrets는 물론 env도 쓸 수 없습니다. 스텝의 if에서 막혔다고 잡의 if로 옮기면 그 자리에서 또 막힙니다. 이 글이 다루는 우회법은 첫째 줄, 잡 레벨 env입니다. (깃허브 공식 문서, Contexts, 2026년 8월 24일 확인)

비밀값을 코드에 안 적고 여기 넣는 이유

AI가 짜 준 코드에는 대개 API 키나 비밀번호를 넣는 자리가 그대로 있습니다. 거기에 실제 값을 적어 넣고 깃허브에 올리면, 저장소가 비공개여도 그 값이 기록에 그대로 남습니다. 나중에 저장소에 접근하는 사람 누구나 볼 수 있다는 뜻입니다. 시크릿은 이 값을 코드 밖, 깃허브가 암호화해서 보관하는 자리에 따로 넣고, 워크플로가 실행되는 동안에만 불러와 쓰는 방식입니다. 코드에는 이름만 남습니다. 자동화가 Gmail로 메일을 보낼 때 쓰는 앱 비밀번호가 이 자리에 들어가는 대표적인 값입니다.

넣는 절차: Settings에서 세 번이면 끝납니다

절차 자체는 짧습니다.

단계 화면 하는 일
1 저장소 Settings > Secrets and variables > Actions 이 경로로 들어갑니다
2 오른쪽 위 초록색 New repository secret 버튼 새 비밀값 등록 화면을 엽니다
3 Name과 Secret을 입력하고 Add secret 이름은 코드가 부르는 이름과 똑같이 맞춥니다
깃허브 저장소 Settings 화면의 Secrets and variables, Actions 탭. Repository secrets 표에 등록된 비밀값 두 개가 이름만 보이고 값은 가려져 있으며, 오른쪽 위에 초록색 New repository secret 버튼이 있다
Settings 안의 Secrets and variables, Actions 화면입니다. 목록에는 이름만 남고 값은 절대 보이지 않습니다
새 시크릿 등록 폼. Name 입력 칸에 YOUR_SECRET_NAME이라는 예시 문구가 있고, 그 아래 값을 넣는 빈 Secret 칸과 초록색 Add secret 버튼이 있다
New repository secret을 누르면 나오는 입력 폼입니다. Name은 코드가 부르는 이름 그대로 적습니다

Name 칸에는 아무 문자나 넣을 수 있는 게 아닙니다. 깃허브가 정한 규칙이 있습니다.

이름 규칙 내용
쓸 수 있는 문자 영문자, 숫자, 밑줄(_)만. 공백 불가
시작 문자 숫자로 시작 불가. GITHUB_로 시작 불가(깃허브가 예약)
대소문자 구분하지 않음. 깃허브가 내부적으로 대문자로 저장
유일성 범위 같은 저장소(또는 조직, 엔터프라이즈) 안에서 이름이 겹칠 수 없음

코드가 secrets.GMAIL_APP_PASSWORD로 부르고 있다면 Name 칸에도 정확히 GMAIL_APP_PASSWORD를 적어야 합니다. 대소문자를 다르게 적어도 동작에는 문제가 없지만, 화면에는 항상 대문자로 표시됩니다.

Secret 칸은 위 캡처에서 보듯 한 줄짜리 입력창이 아니라 여러 줄을 받는 넓은 박스입니다. 채팅창이나 메모장에서 값을 복사해 붙여넣을 때 앞뒤로 빈 줄이나 공백이 함께 딸려 오기 쉬운 자리입니다. 붙여넣은 뒤에는 값의 앞뒤에 원래 없던 줄바꿈이나 스페이스가 끼어들지 않았는지 한 번 확인하는 것이 안전합니다.

로그의 ***가 항상 지켜주는 게 아닙니다

조건부 단계 함정만 걸리는 게 아닙니다. 시크릿을 처음 다루는 사람이 실제로 헷갈리는 지점이 하나 더 있습니다. 워크플로 실행 로그에서 시크릿 값은 ***로 가려져 나옵니다. 워크플로 로그에서 민감한 정보를 자동으로 가린다고 공식 문서가 명시합니다.

문제는 이 자동 마스킹이 Settings에 시크릿으로 등록해 둔 값에만 걸린다는 것입니다. 값을 시크릿으로 등록하지 않고 워크플로 파일에 그대로 적어 넣거나, 다른 변수에 옮겨 담아 출력하면 그 값은 가려지지 않고 로그에 그대로 남습니다.

로그에 찍히는 값 가려지나
Settings에 등록한 시크릿 자동으로 *** 처리
시크릿으로 등록하지 않은 값(하드코딩 등) 안 가려짐. 로그에 그대로 남음

그래서 워크플로가 값을 제대로 받았는지 확인하려고 로그에 값을 찍어 봤을 때 ***만 보인다면, 그건 실패가 아니라 마스킹이 정상 작동 중이라는 신호입니다. 반대로 값이 그대로 텍스트로 찍혀 있다면, 그 값은 시크릿으로 등록되지 않았거나 다른 경로로 새고 있다는 뜻입니다.

남의 풀 리퀘스트에는 시크릿이 안 넘어갑니다

앞서 본 Settings 화면 캡처를 다시 보세요. 표 위에 안내 문구가 하나 있었습니다.

Anyone with collaborator access to this repository can use these secrets and variables for actions. They are not passed to workflows that are triggered by a pull request from a fork.

포크(fork, 다른 사람이 내 저장소를 복사해 만든 사본)에서 보낸 풀 리퀘스트가 내 워크플로를 실행시켜도, 그 실행에는 내 시크릿이 전달되지 않습니다. GITHUB_TOKEN이라는 특수한 값 하나만 예외이고 나머지 시크릿은 모두 막힌다고 공식 문서도 같은 내용을 명시합니다.

저장소가 공개(public)라면 누구나 포크해서 풀 리퀘스트를 보낼 수 있습니다. 그 풀 리퀘스트가 내 워크플로를 돌려도 시크릿은 비어 있는 채로 실행됩니다. 조건부 단계가 그냥 건너뛰어지거나, 외부 API를 부르는 단계가 실패한다면, 파일이 깨진 게 아니라 깃허브가 의도적으로 시크릿을 안 준 것입니다. 남의 풀 리퀘스트가 내 저장소에 등록된 시크릿을 훔쳐 가지 못하게 막는 안전장치입니다.

우회법: 잡 레벨 env로 한 번 받습니다

해결책은 위 표의 첫째 줄에 있습니다. 시크릿을 스텝의 if에서 직접 보는 대신, 잡 레벨 env로 한 번 받아 두고 if는 그 env를 보게 하면 됩니다.

steps.if에서 secrets를 직접 씀 (실패)

  1. 쓴 위치 jobs.<job_id>.steps.if if: secrets.GMAIL_APP_PASSWORD != ''
  2. 표의 허용 목록 steps.if에는 secrets가 없습니다 문법이 그 자리를 허용하지 않습니다
  3. 결과 파싱 단계에서 죽습니다 잡이 하나도 안 만들어지고 0초 만에 실패

Unrecognized named-value: 'secrets'

jobs.env로 받아서 씀 (성공)

  1. 쓴 위치 jobs.<job_id>.env GMAIL_APP_PASSWORD: ${{ secrets.GMAIL_APP_PASSWORD }}
  2. 표의 허용 목록 jobs.env에는 secrets가 있습니다 여기서 값을 한 번 받을 수 있습니다
  3. 결과 if: env.GMAIL_APP_PASSWORD != '' 정상적으로 실행됩니다

env를 한 단계 거치면 통과합니다

같은 비밀값인데 secrets를 직접 쓴 줄은 죽고, jobs 레벨 env로 한 번 받은 줄은 삽니다

실제로 성공한 워크플로에서 조건부 단계와 관련된 부분만 옮기면 이렇습니다. (앞뒤로 다른 스텝이 더 있는 실제 파일에서 발췌했습니다.)

name: weekly-briefing
on:
  schedule:
    - cron: '*/5 * * * *'
  workflow_dispatch:

jobs:
  brief:
    runs-on: ubuntu-latest
    env:
      GMAIL_USER: ${{ secrets.GMAIL_USER }}
      GMAIL_APP_PASSWORD: ${{ secrets.GMAIL_APP_PASSWORD }}
      MAIL_TO: ${{ secrets.MAIL_TO }}
    steps:
      - name: 메일 발송
        if: env.GMAIL_APP_PASSWORD != ''
        run: python send_mail.py

이 파일에는 이 함정을 겪은 뒤 남긴 주석이 그대로 있습니다. env: 블록 바로 위에 이렇게 적혀 있습니다.

secrets 컨텍스트는 스텝의 if에서 못 쓴다. job env로 한 번 받아 둔다.

schedule 트리거는 등록 직후 안 보일 수 있습니다

예약 실행(schedule)은 등록하자마자 오지 않을 수 있습니다. 실제로 이 저장소에서도 처음에는 실행 기록이 보이지 않다가, 나중에 정상적으로 실행됐습니다.

넣어 뒀다고 안전한 게 아닙니다

시크릿은 Settings에 저장되어 있는 동안만 깃허브가 암호화해서 지킵니다. 워크플로가 실행되는 동안에는 그 값이 풀려서 러너 안에서 쓰입니다. 그 값을 로그에 찍거나, 파일로 저장해 결과물(artifact)로 올리거나, 외부로 그대로 전달하는 코드가 워크플로 안에 있다면 그 순간 새어 나갑니다. 워크플로가 실행되는 동안 시크릿 값이 출력되지 않도록 주의하라고 공식 문서도 별도로 경고합니다.

저장소 자체의 공개 범위를 바꿀 계획이 있다면 한 가지 더 확인해야 합니다. 비공개 저장소를 공개로 바꾸면 그동안 쌓인 Actions 실행 기록과 로그까지 전부 공개된다고 깃허브 문서가 안내합니다.

과거 어느 실행에서든 시크릿이 로그에 찍힌 적이 있었다면, 저장소를 공개로 바꾸는 순간 그 기록도 함께 공개됩니다. 시크릿을 등록하는 것은 “코드에 안 적었다”는 뜻이지, “이 값을 어떻게 다루든 안전하다”는 뜻이 아닙니다.

이 글의 화면과 에러 문구는 2026년 8월 24일에 확인했습니다. 깃허브의 화면 구성과 문서는 바뀔 수 있습니다. 실제 동작이 다르면 Contexts 공식 문서에서 최신 표를 확인하세요.

댓글

    핀번호는 내 댓글을 지울 때 필요합니다.

    뉴스레터

    새 글을 메일로 받아보세요

    AI 자동화 튜토리얼과 저자 코멘터리를 보냅니다. 스팸 없이, 새 글이 올라올 때만.

    구독하기 ›