Executive Summary
이스라엘 보안업체 AIR가 9월 17일 Plugin4Shell이라는 이름의 취약점을 공개했다. 코딩 에이전트는 마켓플레이스가 심사를 마친 플러그인을 커밋 해시로 못 박아 두고 그 해시를 체크아웃하는데, 체크아웃이 끝난 뒤 정말 그 해시에 도착했는지는 확인하지 않는다. 이 글은 그 빠진 확인 한 단계가 무엇을 속일 수 있는지, 그리고 이름으로 내용을 식별하는 다른 파이프라인도 같은 질문을 받아야 하는지를 본다.
공격자는 40자 커밋 해시와 똑같은 이름의 브랜치를 자기 저장소에 만든다. git은 그런 이름을 커밋 객체보다 참조 쪽으로 먼저 읽으므로, 고정해 둔 커밋 자리에 브랜치에 담긴 악성 코드가 들어앉는다. Claude Code와 Codex는 설치된 플러그인을 배경에서 자동 갱신하는 것이 기본 설정이라, 사용자가 승인 창을 누른 적도 없는데 코드가 실행된다. 영향받은 네 종 가운데 둘은 패치됐고, GitHub Copilot에는 패치가 없으며, Gemini CLI는 패치 대신 제품이 지원 종료됐다.
3절까지는 AIR의 공개 문서와 더 레지스터, 헬프넷시큐리티 보도를 따라가며 확인된 사실만 적는다. 실제 악용이 보고된 사례는 아직 없고, CVE 번호도 붙지 않았다. 4절부터는 학습 데이터셋이나 의존성을 해시와 버전 문자열로만 식별하는 파이프라인에 같은 가정이 놓여 있는지를 다룬다. 그 부분은 소스 문서에 없는 이 글의 해석이다.
주요 수치
출처: AIR, Plugin4Shell (2026-09-17) · 더 레지스터 (2026-09-17)
0회
공격에 필요한 사용자 동작
설치해 둔 플러그인의 배경 자동 갱신이 대신 실행한다. 승인 창도 클릭도 공격 경로에 없다
넷 중 둘
패치가 나온 에이전트
Claude Code와 Codex만 수정됐다. GitHub Copilot은 패치 없이 남았고 Gemini CLI는 지원 종료로 처리됐다
0건
보고된 실제 악용 사례
연구로 찾아낸 결함이지 사고 기록이 아니다. 인용한 세 소스에 피해 사례는 실리지 않았다
13만 4천
같은 팀 이전 연구가 닿은 에이전트
스킬 925개를 가로챈 SkillJacking의 집계다. Plugin4Shell에는 수백만 대라는 자체 추정만 있고 제3자 검증 수치는 없다
이름은 같은데, 가리키는 것이 다르다
플러그인을 커밋 해시로 고정하는 관행은 왜 안전하다고 여겨졌을까. 40자 16진수 해시는 저장소 내용에서 계산해 낸 값이다. 파일 한 줄만 달라져도 값이 달라지므로, 해시를 적어 두면 그 이름이 가리키는 내용은 하나뿐이라는 보장이 생긴다. 마켓플레이스가 플러그인을 심사한 뒤 특정 해시로 묶는 것은 이 보장을 빌려 쓰는 일이다. 버전 번호나 브랜치 이름은 나중에 다른 내용을 가리키게 만들 수 있지만 해시는 그렇지 않다는 것이 전제였다.
AIR 연구팀은 그 보장에 조건이 하나 더 붙어야 한다는 사실을 찾아냈다. 에이전트가 체크아웃을 끝낸 뒤 실제로 그 해시에 도착했는지 확인해야 한다. 영향받은 네 종 모두 그 확인을 하지 않았다. Claude Code와 Codex, GitHub Copilot이 플러그인을 가져올 때 실행하는 명령은 이렇게 두 줄이다.
git clone <plugin repo> ./ git checkout aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
git에서 40자 16진수 문자열은 커밋 객체의 이름일 수도 있고 브랜치나 태그의 이름일 수도 있다. 두 가지가 같은 이름으로 존재하면 git은 참조 쪽을 먼저 읽는다. 그래서 공격자가 자기 저장소에 aaaa…aaaa라는 이름의 브랜치를 만들어 악성 코드를 채우고 기본 브랜치로 지정하면, 에이전트는 고정된 커밋을 요구했는데도 그 브랜치를 받아 온다. 고정은 그대로 남아 있고, 고정이 가리키는 내용만 바뀐다.
여기에는 조건이 둘 붙는다. 브랜치 이름을 해시처럼 짓는 것을 막지 않아야 하고, 그 브랜치가 저장소의 기본 브랜치여야 한다. 첫 조건은 git의 기본 동작이다. git check-ref-format이 40자 16진수 이름을 통과시키고, 프로토콜을 따르는 호스트도 받아 준다. 둘째 조건은 공격이 성립하는 범위를 정한다. 기본이 아닌 브랜치는 원격 추적 참조로만 내려오므로 체크아웃이 커밋 쪽으로 되돌아간다. 두 조건이 맞으면 git은 refname is ambiguous라는 경고 한 줄을 남기고 브랜치를 잡는다. 이름이 겹쳤다는 사실을 도구가 모르는 것이 아니라, 알고도 경고로만 알린다. 그 뒤로는 고정된 커밋이 저장소에 남아 있는지조차 상관없어지고, 에이전트는 고정된 커밋으로 설치를 마쳤다고 보고한다.
Gemini CLI는 트릭이 달랐다. 이 에이전트가 실행하는 명령은 세 줄이고, 마지막 줄이 해시가 아니라 git 내부 이름을 가리킨다.
git clone --depth 1 <plugin repo> ./ git fetch origin 41d0bc0a4aeb2fbf797dacea39e876d98c95024b git checkout FETCH_HEAD
FETCH_HEAD는 방금 가져온 것을 가리키는 git의 예약된 이름이다. 그런데 저장소의 기본 브랜치 이름을 FETCH_HEAD로 지으면 그 이름이 브랜치 쪽으로 읽히고, 바로 앞 줄에서 해시로 가져온 커밋은 무시된다. 이 변종에서 공격자가 흉내 낸 것은 해시가 아니라 git의 예약어다. 그러니 이번 사건을 "해시와 같은 이름의 브랜치" 한 가지 수법으로 옮기면 Gemini CLI 대목이 부정확해진다.
겉모습이 다른 두 변종에서 빠진 확인은 하나다. AIR가 벤더에 권고한 수정도 한 줄이다. 체크아웃이 끝나면 실제로 받아 온 커밋을 읽어 고정된 SHA와 맞춰 보고, 다르면 중단하라는 것이다. 이름을 적어 두는 일과 그 이름에 도착했는지 확인하는 일은 다른 일이고, 네 종은 앞쪽만 하고 있었다.
심사가 끝난 뒤에 코드가 바뀐다
이 결함이 왜 마켓플레이스 심사로 걸러지지 않는지는 공격이 시간 순으로 어떻게 진행되는지를 보면 드러난다. AIR가 정리한 단계는 다섯이다. 심는 시점과 터지는 시점이 떨어져 있고, 심사는 심는 시점에만 걸린다.
마지막 단계가 이 사건의 성격을 정한다. Claude Code와 Codex가 플러그인을 알아서 갱신하도록 기본 설정돼 있기 때문이다. 갱신이 도는 순간 앞 절의 체크아웃이 다시 실행되고, 바꿔치기가 이미 심어져 있으면 사용자에게는 아무 일도 일어나지 않는다. 승인 창 하나 뜨지 않고, 클릭 한 번 없이 지나간다. 연구팀은 이 대목을 이렇게 썼다. 설치 시점의 버그만이 아니라는 점이 이것을 무클릭으로 만들고, 같은 git checkout이 배경 자동 갱신에서 다시 돌아간다는 것이다. 방아쇠는 고정이 바뀌는 순간이다. 마켓플레이스가 새 커밋으로 재고정하면 그 변경 자체가 배경 갱신을 부르고, 공격자는 원래 심사받은 커밋을 지울 필요도 없다. 저장소에서 사라진 것은 하나도 없다.
코드가 실행된다는 말의 크기는 권한 범위에서 나온다. 플러그인은 에이전트에 붙는 순간 그 에이전트를 돌리는 직원의 권한을 그대로 물려받는다. 사내 시스템과 운영 환경, 민감한 데이터에 직원이 닿는 만큼 닿는다. 그래서 공격자는 권한을 따로 올릴 필요가 없다. 직원의 기기에서 코드를 한 번 실행하는 것으로 그 직원이 가진 범위를 통째로 얻는다. AIR와 더 레지스터, 헬프넷시큐리티가 위험의 크기를 모두 이 범위로 잡았다.
피해자에게 필요한 조건은 더 짧다. AIR는 피해자가 해야 할 일이라고는 자기가 신뢰하는 마켓플레이스에서, 심사를 받고 보안 모델이 의도한 그대로 고정된 플러그인을 설치해 두는 것뿐이라고 적었다. 피싱도, 수상한 링크도, 설정 실수도 필요하지 않다. 보안 모델이 시킨 대로 행동한 사용자가 그대로 뚫린다.
AIR는 한 걸음 더 나간 조직이 오히려 이 결함의 정면에 선다고 짚었다. 커뮤니티 마켓플레이스에서 받아 쓰는 데 그치지 않고 플러그인을 직접 검토해 검토한 커밋에 고정해 둔 조직은 그 고정을 안전장치로 삼고 있는데, 이번 결함은 그 장치만 조용히 무효로 만든다. 심사는 통과하고, 고정은 기록되고, 다른 코드가 설치된다. 고정 위에 세운 검증 절차는 무엇이든 같은 실패를 함께 물려받는다는 것이 원문의 표현이다.
공격자가 처음부터 악성 플러그인을 심어야 하는 것도 아니다. AIR가 제시한 두 번째 시나리오는 정상 저자의 저장소를 가로채는 경로이고, 둘 다 공격자가 마켓플레이스를 장악할 필요가 없다. 이 팀은 앞서 스킬 925개를 유지보수 담당자 손에서 가로챈 SkillJacking으로 에이전트 13만 4천 대가 닿는 범위를 보였고, 그 전 연구에서는 플러그인 하나가 삭제되기 전까지 에이전트 2만 6천 대 이상에 퍼졌다고 밝혔다. 탈취가 실제로 규모를 갖고 일어난다는 사실은 이미 보인 셈이고, Plugin4Shell은 그 탈취를 막으려고 만든 장치를 무력화한다.
여기서 한 가지는 분명히 짚는다. 지금까지 이 수법이 실제로 악용된 사례는 보고되지 않았다. 침해 신고도, 데이터 유출 보고도 세 소스 가운데 어디에도 없다. 이번 건은 연구팀이 개념 증명을 만들어 벤더에 알린 뒤 공개한 취약점이고, 이미 벌어진 사고가 아니다. CVE 번호도 아직 붙지 않았다.
같은 결함, 네 갈래 결말
AIR는 2026년 5월에 네 종 모두에서 작동하는 개념 증명을 확보하고 6월에 벤더에 알렸다. 같은 날 같은 내용을 받은 네 회사의 대응이 서로 달랐다. 패치라는 말이 늘 같은 모양으로 끝나지 않는다는 것이 이 표에 남아 있다.
| 에이전트 | 벤더 | 상태 | 확인 시점 |
|---|---|---|---|
| Claude Code | 앤트로픽 | 2.1.179에서 패치 | 6월 17일 수정 확인 |
| Codex | 오픈AI | 0.146.0에서 패치 | 8월 12일 수정 확인 |
| GitHub Copilot | 마이크로소프트·깃허브 | 패치 없음 | 수정 확인 없음 |
| Gemini CLI | 구글 | 패치 대신 지원 종료 | 8월 4일 지원 종료 공지 |
AIR가 공개 문서에 적은 벤더별 대응이다. 구글은 후속 환경인 Antigravity로 옮기라고 권한다. 그 환경이 이 공격에 닿지 않는 이유는 결함을 고쳤기 때문이 아니라, 우회할 마켓플레이스 플러그인 SHA 고정 장치 자체가 없기 때문이다. 기존 Gemini CLI 설치본에는 수정이 오지 않으므로 노출된 채로 남는다. 깃허브는 에이전트 패치와 별도로, 자사 호스팅 저장소에서는 이 공격이 성립하지 않는다고 반박했다.
네 칸에서 해석이 갈리는 곳은 GitHub Copilot이다. 깃허브 대변인은 더 레지스터에 이렇게 답했다. 커밋 SHA를 닮은 브랜치나 태그 이름은 사용자가 만들 수 없게 막았으므로, 보고된 취약점은 깃허브에서 악용될 수 없다는 것이다. 에이전트 쪽에 패치가 오지 않은 것은 사실이고, 플랫폼이 이름 모양을 검사해 그 경로를 닫았다는 주장이다. AIR 연구진은 그 완화가 충분하지 않다고 본다.
AIR가 든 근거는 호스트 범위다. 연구팀은 마켓플레이스가 비트버킷 같은 다른 플랫폼에도 올라갈 수 있고 Copilot이 그런 플랫폼의 마켓플레이스까지 지원하므로 여전히 노출된다고 더 레지스터에 답했다. 공개 문서에도 같은 논지가 있다. 마켓플레이스가 SHA 꼴 이름을 거부하는 호스트만 허용하면 브랜치 이름 변종은 둔화시킬 수 있지만, 그것은 사실상 깃허브만 허용하는 것이고 에이전트가 공식으로 지원하는 호스트를 금지하는 셈이라고 적었다. 비트버킷과 자체 운영 git 서버를 마켓플레이스 백엔드로 인정하는 목록은 앤트로픽 자사 문서에 있다고 원문이 짚는다. 패치를 낸 벤더의 문서가 그 호스트들을 정식 구성으로 인정한다. 요약하면 이 완화는 플러그인이 깃허브에 있을 때만 성립한다.
완화가 닿지 않는 자리는 원문이 따로 짚었다. 호스트를 골라 허용하는 방식은 브랜치 이름 변종을 둔화시키는 데까지고, Gemini CLI의 변종에는 아무것도 하지 못한다는 것이다. 깃허브의 규칙이 겨냥하는 이름은 SHA를 닮은 이름이고, Gemini CLI를 뚫은 이름은 FETCH_HEAD였다. 이 이름은 SHA를 닮지 않았다. 이름의 모양을 검사하는 방어는 모양이 다른 이름을 걸러 내지 못한다. 깃허브의 답변도 자사 플랫폼에서 보고된 취약점이 악용될 수 없다는 범위까지만 말한다.
네 갈래로 갈린 데에는 답이 왔는지 아닌지의 차이도 있다. AIR는 6월부터 마이크로소프트에도 같은 내용을 알렸지만 답을 받지 못했다고 더 레지스터에 밝혔고, 그 회사가 지금 받고 있는 제보량 때문인 듯하다고 덧붙였다. 더 레지스터가 마이크로소프트에 요청한 논평도 기사가 나갈 때까지 오지 않았다. 깃허브 대변인이 매체에 답한 것과 별개로, 연구팀 쪽으로는 답이 오지 않았다. 앤트로픽은 6월 17일, 오픈AI는 8월 12일에 수정이 확인됐고, 공개일인 9월 17일까지 Copilot에는 패치가 오지 않았다.
마켓플레이스가 이 문제를 끝낼 수 없는 이유도 원문에 있다. 고정된 해시가 실제로 지켜졌는지는 에이전트 안에서 해석되므로, 에이전트 쪽 수정만이 그 보장을 되돌린다는 것이다. 마켓플레이스가 심사를 아무리 촘촘히 해도 심사 이후에 바뀌는 내용은 심사 밖에 있다.
규모를 말하는 수치는 조심해서 읽을 값이다. AIR는 수백만 대의 에이전트가 영향을 받는다고 적었는데, 이것은 발견자 자체 추정이고 제3자 검증을 거치지 않았다. 더 레지스터는 마이크로소프트 발표를 인용해 포춘 500 기업의 약 90%가 Copilot을 쓴다고 전했다. 이 값은 Copilot 사용률이고 이 취약점에 노출된 비율이 아니다. 노출은 마켓플레이스에서 플러그인을 설치했는지, 그 플러그인이 어느 호스트에 있는지에 달려 있고, 그 분포를 공개한 문서는 없다.
발견자의 이해관계도 함께 봐야 정확하다. AIR는 이번 공개 문서에 자사 마켓플레이스와 필터를 쓰는 기업은 Plugin4Shell의 영향을 받지 않았다고 밝혔다. 이 회사는 9월 1일 시드 두 라운드로 5,000만 달러를 모았다고 공개하며 스텔스에서 나왔고, 에이전트가 쓰는 스킬과 애드온을 계속 재검증하는 제품을 판다. 페블러스도 그때 이 회사를 다뤘다. 이번 연구의 저자 셋 가운데 니브 호프만은 이 회사의 공동창업자 겸 최고기술책임자다. 연구 내용 자체를 의심할 근거는 없지만, 심각도 표현과 파급 추정이 해결책을 파는 회사에서 나왔다는 점은 수치를 옮길 때 함께 옮길 정보다.
이름과 내용을 갈라 확인하고 있는가
여기서부터는 코딩 에이전트를 떠나, 같은 가정이 놓인 다른 자리를 본다. 데이터 파이프라인에서 어떤 자산의 출처를 적는 방법은 대개 이름이다. 학습 데이터셋의 해시, 모델 카드의 버전 태그, 패키지 레지스트리의 버전 문자열, 계보 도구가 기록하는 원천 URL이 모두 이름이다. 네 자리 모두 이름으로 내용을 식별하고, 그 식별이 곧 확인이라고 전제한다.
Plugin4Shell이 보여 준 것은 그 전제가 조건부라는 사실이다. 이름은 내용을 지목할 뿐이고, 지목한 곳에 정말 그 내용이 있는지는 별도로 확인해야 한다. 코딩 에이전트에서는 그 확인이 git rev-parse HEAD 한 줄이었고, 그것이 없어서 네 종이 함께 뚫렸다.
먼저 범위를 분명히 한다. 학습 데이터의 해시가 실제로 위조된 사례가 보고된 적은 없다. 원문이 일반화한 범위는 고정 위에 세운 검증 절차까지고, 그것을 데이터 파이프라인으로 옮긴 것은 소스 문서에 없는 우리 독법이다. 이 절은 사례를 옮기는 것이 아니라 질문을 옮긴다. 이름으로 자산을 가려내면서 받아 온 내용이 그 이름과 맞는지 대조하지 않는 파이프라인이라면, 구조적으로 같은 질문을 받는다. 아래 다섯 가지는 감사 로그와 파이프라인 코드에서 답을 뽑을 수 있는 물음들이다.
- 내려받은 뒤 해시를 다시 계산해 적어 둔 값과 대조하는 단계가 있는가. 기록만 남기고 다시 계산하는 단계가 빠진 파이프라인이 흔하다.
- 원천 URL로만 식별되는 자산이 몇 개인가. URL은 내용에서 계산된 값이 아니므로, 그 끝의 파일이 바뀌어도 URL은 그대로 남는다.
- 버전 문자열이나 태그로 고정한 의존성이 몇 개인가. 태그는 다른 커밋으로 옮길 수 있고, 옮긴 기록은 대개 우리 쪽 로그에 남지 않는다.
- 이름 해석이 여러 갈래일 수 있는 자리를 목록으로 갖고 있는가. git 저장소, 컨테이너 이미지 태그, 심볼릭 링크가 걸린 스토리지 경로가 후보다.
- 사람 승인 없이 자동으로 갱신되는 외부 자산이 무엇인가. 아무도 보지 않는 시점에 도는 갱신이 바꿔치기가 노리는 자리다.
다섯 가지 모두 코드와 로그에서 답이 나오고, 답하는 사람이 자기 파이프라인을 후하게 보든 박하게 보든 값이 달라지지 않는다. 계보를 잘 기록하고 있다는 자평과 다른 점은 거기에 있다.
페블러스가 이 소식을 주목하는 이유
페블러스가 오래 붙들어 온 물음은 어떤 값이 어디서 와서 무엇을 거쳐 지금 모습이 됐는가다. 이번 사건은 그 물음이 보안 영역에서 되돌아온 사례에 가깝다. 계보를 기록한다는 말에는 두 가지 일이 섞여 있다. 어디서 왔다고 적는 일과, 적은 것이 맞는지 확인하는 일이다. 도구가 늘어난 것은 앞쪽이고, 뚫린 것은 뒤쪽이었다.
해시는 계보를 적는 수단 가운데 가장 신뢰받는 이름이다. 내용에서 계산된 값이라 위조가 어렵다는 성질이 그 신뢰의 근거다. 그런데 위조된 것은 해시가 아니었다. 해시를 담은 이름이 어떻게 해석되는지가 바뀌었다. 해시 자체의 성질은 그대로인데, 그 해시를 찾아가는 길목에서 다른 것이 응답했다. 데이터 계보에서 가장 단단하다고 믿었던 이름조차 해석되는 자리를 하나 지나야 한다는 뜻이다.
실무에서 이 구조가 문제가 되는 순간은 대개 자동화가 촘촘해진 뒤다. 사람이 매번 확인하던 자리를 파이프라인이 대신하면서, 확인의 내용도 함께 옮겨졌는지는 따로 점검하지 않는다. 자동 갱신이 무클릭 공격을 완성한 자리가 정확히 그 지점이다. 이것은 우리 판단이고, 소스 문서에 데이터 파이프라인을 두고 그런 서술이 있는 것은 아니다.
코딩 에이전트를 팀 표준 도구로 쓰고 있다면 다음 네 가지를 한 번 확인해 볼 만하다.
- 팀이 쓰는 에이전트 버전이 패치 이후인가. Claude Code는 2.1.179, Codex는 0.146.0부터 수정이 들어 있다.
- 설치해 둔 플러그인 목록을 최근에 본 적이 있는가. 쓰지 않는 것이 남아 있으면 자동 갱신은 그것까지 돈다.
- 그 플러그인들이 어느 호스트의 저장소에서 오는가. 깃허브 바깥 호스트는 이름 모양 검사에 기대 둘 수 없다.
- 우리 데이터 파이프라인에서 이름이 같으면 같은 것으로 처리하는 코드가 어디에 있는가. 그 자리마다 내려받은 내용을 다시 대조하는 줄이 붙어 있는지 확인할 만하다.
여기까지 읽어 주셔서 감사하다. 이 글이 인용한 기술 설명과 패치 상태는 AIR의 공개 문서와 더 레지스터 보도에서 누구나 원문으로 확인할 수 있다. 여러분의 파이프라인에서는 같은 이름과 같은 내용을 어느 지점에서 갈라 확인하고 계신지 나눠 주시면 좋겠다.
참고문헌
1차 소스
- 1.Nevo, O., Granat, D., & Hoffman, N. (2026, 9월 17일). "Plugin4Shell - Zero Click RCE Vulnerability found in top 4 most popular coding agents, millions of agents affected." AIR Security Blog. — 취약점 발견 원문. 공격 5단계, 벤더별 대응, 완화 한계를 서술.
보도
- 2.Lyons, J. (2026, 9월 17일). "AI coding agents' 0-click RCE flaw could hand attackers keys to the kingdom." The Register. — 깃허브 대변인 반박, AIR의 재반박(호스트 범위), 마이크로소프트 무응답을 수록한 유일한 보도.
- 3.Markovic, S. (2026, 9월 18일). "Zero-click RCE vulnerability hit four major AI coding agents, two remain unpatched." Help Net Security. — 독립 보도로 벤더별 패치 현황을 교차 확인.
저자 이해관계 확인
- 4.Niv Hoffman. Crunchbase. — 연구 저자 니브 호프만이 AIR 공동창업자 겸 CTO임을 확인.
- 5.Calcalist (CTech). (2026, 9월 1일). "Six-month-old AIR Security raises $50 million to build a firewall for AI agents." CTech by Calcalist. — 니브 호프만의 공동창업자·CTO 직함 교차 확인.