커밋 SHA로 고정한 플러그인이 바뀐다, 에이전트 4종이 빠뜨린 확인 한 줄
9월 17일 공개된 Plugin4Shell은 Claude Code, Codex, GitHub Copilot, Gemini CLI가 고정한 커밋에 실제로 도착했는지 확인하지 않는 결함입니다. Claude Code는 6월, Codex는 8월에 고쳤고 Copilot은 아직 패치가 없습니다.
- 검토해 고정한 커밋 대신 다른 코드가 설치되는 결함이 공개됐습니다.
- Claude Code
2.1.179와 Codex0.146.0은 고쳤고 Copilot은 아직입니다. - GitHub 마켓플레이스는 변종 하나만 막고 Gemini CLI 쪽은 못 막습니다.
보안 업체 AIR Security가 2026년 9월 17일 Plugin4Shell이라는 이름으로 코딩 에이전트 네 제품의 공통 결함을 공개했습니다. Claude Code, OpenAI Codex, GitHub Copilot, Gemini CLI입니다.
깨진 것은 SHA 핀입니다. 플러그인 마켓플레이스는 플러그인마다 검토를 끝낸 커밋 하나를 커밋 해시(SHA)로 지정해 둡니다. 코드를 한 번 읽어 보고, 그 상태를 고정하고, 앞으로도 같은 코드만 돌아간다고 약속하는 장치입니다. 저장소가 나중에 탈취돼도 고정해 둔 커밋은 그대로이므로 안전하다는 것이 전제였습니다.
네 에이전트는 그 커밋을 요청하기는 했습니다. 요청한 커밋에 실제로 도착했는지를 확인하지 않았을 뿐입니다. 플러그인은 에이전트를 쓰는 사람과 같은 권한으로 돌기 때문에, 바뀐 코드는 그 사람의 파일과 저장된 자격 증명, 로그인할 수 있는 시스템에 그대로 닿습니다. Cursor 에이전트에 준 권한이 그대로 침입 경로가 된 8월 사건과 도달 범위가 같습니다.
핀은 꽂혀 있었고, 확인만 없었습니다
공격은 git의 이름 해석 규칙 하나를 이용합니다. 같은 이름이 브랜치로도 커밋 ID로도 유효하면 git은 브랜치를 먼저 고릅니다. 경고 한 줄(refname is ambiguous)만 출력하고 그대로 진행합니다.
Claude Code, Codex, GitHub Copilot은 플러그인 저장소를 복제한 뒤 고정된 SHA를 체크아웃합니다.
git clone <plugin repo> ./
git checkout aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
저장소를 통제하는 쪽이 고정된 SHA와 글자가 똑같은 40자 16진수 이름의 브랜치를 만들고 기본 브랜치로 지정하면, 위 git clone이 그 브랜치를 함께 가져옵니다. 다음 줄의 체크아웃은 커밋이 아니라 그 브랜치로 해석됩니다. 원래 커밋이 저장소에 남아 있는지는 상관이 없습니다.

Gemini CLI는 경로가 다릅니다. 커밋을 받아 온 뒤 FETCH_HEAD를 체크아웃합니다.
git clone --depth 1 <plugin repo> ./
git fetch origin 41d0bc0a4aeb2fbf797dacea39e876d98c95024b
git checkout FETCH_HEAD
git fetch는 올바른 커밋을 가져와 .git/FETCH_HEAD에 기록합니다. 그런데 git checkout FETCH_HEAD가 반드시 그 파일을 읽는 것은 아닙니다. 기본 브랜치 이름이 FETCH_HEAD이면 브랜치가 우선하고 방금 받아 온 커밋은 조용히 버려집니다.
두 변종을 한 번에 막는 확인은 한 줄입니다.
test "$(git rev-parse HEAD)" = "<pinned-sha>" || abort
요청한 ref가 아니라 체크아웃이 끝난 뒤의 HEAD를 봐야 합니다. Gemini CLI 변종이 빠져나가는 곳이 정확히 그 차이입니다. 확인이 놓일 자리도 에이전트 안입니다. 핀을 해석하는 주체가 클라이언트라, 마켓플레이스가 아무리 엄격해도 대신 보장해 주지 못합니다.
이미 설치한 플러그인이 조용히 바뀝니다
클릭이 필요 없는 이유는 자동 업데이트입니다. Claude Code와 Codex는 마켓플레이스 자동 업데이트가 기본값입니다. 세션마다 백그라운드로 원격 저장소에 새 커밋이 올라왔는지 확인합니다. 마켓플레이스가 핀을 새 커밋으로 올리면 같은 체크아웃이 다시 돌고 이미 설치돼 있던 플러그인이 사용자 조작 없이 교체됩니다. 설치 단계도, 확인 창도 없습니다.
연구진이 공개한 공격 순서는 다섯 단계입니다.
1
진짜로 멀쩡한 플러그인을 마켓플레이스에 올리고 커밋 aaa...aaa로 고정합니다.
검토를 통과합니다.
2
사용자들이 설치합니다. 설치본은 모두 검토된 aaa...aaa에 고정됩니다.
3
여전히 멀쩡한 커밋 bbb...bbb로 핀을 갱신하는 PR을 올려 승인받습니다.
4
bbb...bbb라는 이름의 브랜치를 만들어 기본 브랜치로 지정하고 악성 코드를
가리킵니다. 고정된 커밋 자체는 건드리지 않아도 됩니다.
5
핀이 바뀌었으므로 모든 에이전트의 백그라운드 업데이트가 돕니다. 체크아웃이 브랜치로 해석되고 악성 코드가 실행됩니다.
연구진은 검토를 제대로 한 조직일수록 이 방어선에 더 기대고 있었다고 짚었습니다. 커뮤니티 마켓플레이스를 그냥 쓰지 않고 플러그인을 직접 읽은 뒤 검토한 커밋에 고정하는 절차를 만든 팀이 있습니다. 그 절차는 검토도 통과하고 핀도 기록되지만, 설치되는 코드는 다른 코드입니다. 핀 위에 세운 모든 검증 과정이 같은 결함을 물려받습니다.
AIR Security가 같은 주제로 낸 세 번째 연구이기도 합니다. 앞선 두 건은 악성 스킬을 마켓플레이스에 올려 에이전트 26,000대를 장악한 사례와, 이미 쓰이던 스킬 925개의 저장소를 탈취해 에이전트 134,000대에 영향을 준 사례였습니다. SHA 핀은 업계가 바로 그 유형에 내놓은 답이었습니다.
내가 위험한지는 마켓플레이스가 어디에 있느냐로 갈립니다
40자 16진수 브랜치 이름을 허용하느냐가 갈림길입니다. git 자체는 허용합니다. git check-ref-format이 40자 16진수 이름을 통과시키고 프로토콜을 그대로 따르는 호스트도 받아들입니다.
- GitHub은 거부합니다. 커밋 SHA처럼 보이는 브랜치·태그 이름을 아예 만들지 못하게 막습니다.
- Bitbucket과 자체 호스팅 git 서버는 허용합니다. Anthropic 문서도 GitLab, Bitbucket, 자체 호스팅 git을 마켓플레이스 백엔드로 공식 지원한다고 적고 있습니다.
GitHub 대변인은 The Register에 이 구분을 근거로 반박했습니다. "GitHub은 커밋 SHA처럼 보이는 브랜치나 태그 이름 생성을 허용하지 않으며, 이 완화로 보고된 취약점은 GitHub에서 악용될 수 없다"는 내용입니다. 연구진은 마켓플레이스가 Bitbucket 같은 다른 플랫폼에도 놓일 수 있고 Copilot이 그런 마켓플레이스도 지원하므로 여전히 취약하다고 재반박했습니다.
호스트를 GitHub으로 제한해도 Gemini CLI 변종은 남습니다. 그쪽이 쓰는 이름은 FETCH_HEAD이지 40자 해시가 아니기 때문입니다.
벤더 대응은 갈렸습니다.
| 제품 | 상태 | 고친 버전과 날짜 |
|---|---|---|
| Claude Code | 패치됨 |
|
| OpenAI Codex | 패치됨 |
|
| GitHub Copilot | 미패치 | 9월 23일까지 수정본 없음 |
| Gemini CLI | 수정 없음 | 8월 4일 지원 종료 통보, Antigravity 이전 권고 |
연구진은 2026년 5월에 네 제품 모두를 겨냥한 실증 코드를 만들었고 6월에 조율된 공개 절차로 알렸습니다. 통보에서 공개까지 석 달이 걸렸습니다. 2026년 9월 23일 기준으로 CVE 번호도, 공식 CVSS 점수도, 실제 악용 사례 보고도 없습니다.
지금 확인할 것
여기부터는 코딩 에이전트에 마켓플레이스 플러그인을 설치해 쓰는 개발자에게 해당합니다. 플러그인을 하나도 설치하지 않았다면 이번 결함으로 손댈 것은 없습니다. 패치는 각 도구의 일반 업데이트에 들어 있어 별도 요금제나 신청이 필요 없습니다. 한국에서도 버전 업데이트와 설정 변경에 지역 제약이 없습니다.
| 쓰는 도구 | 조건 | 지금 상태 |
|---|---|---|
| Claude Code | 2.1.179 이상 | 자동 업데이트를 켜 뒀다면 6월에 이미 반영됨 |
| OpenAI Codex | 0.146.0 이상 | 8월에 이미 반영됨 |
| GitHub Copilot | 조건 없음 | 패치가 없어 마켓플레이스 출처를 직접 관리해야 함 |
| Gemini CLI | 조건 없음 | 수정 예정이 없어 Antigravity로 옮기는 것이 유일한 해결 |
설치된 버전은 각 도구의 버전 명령으로 확인합니다.
claude --version
codex --version
Claude Code에서는 /plugin marketplace list로 등록된 마켓플레이스 출처를 볼 수 있고, /plugin marketplace auto-update off로 자동 업데이트를 끕니다. 문서에 따르면 자동 업데이트는 기본으로 켜져 있고, git 소스 플러그인은 version 필드가 없으면 해석된 커밋 SHA가 곧 버전이 됩니다.
패치된 클라이언트만 쓴다면 이 글을 읽고 할 일은 버전 확인 한 번으로 끝납니다. 팀이 Copilot을 함께 쓴다면 등록된 마켓플레이스 목록을 열어 GitHub 밖에서 온 것이 있는지부터 봅니다. Bitbucket이나 사내 git 서버에 올려 둔 마켓플레이스가 있다면, 패치가 나올 때까지 그 마켓플레이스의 자동 업데이트를 끄는 것이 지금 쓸 수 있는 유일한 완화책입니다.