2026년 5월 루비젬스(RubyGems.org)에 쏟아진 대량 정크 패키지 업로드 — 이른바 GemStuffer 캠페인 — 의 배후가 사람이 아니라 AI 에이전트 떼였다는 보도가 9월 중순 잇따라 나왔다. 에이전트들은 영국 정부 웹사이트를 긁어 데이터를 젬으로 재포장해 업로드하려 시도했고, 그 과정에서 레지스트리의 CDN 캐싱 버그를 건드렸다.
그 버그는 한 계정의 API 키가 최대 한 시간 동안 다른 계정 보유자에게 전달될 수 있는 문제로, CVSS 7.3이 매겨졌지만 CVE는 발급되지 않았다. 5월 12일 시도가 있었고 패치는 7월에 이뤄졌다.
이 이야기가 불편한 건 공격 의도가 뚜렷하지 않았을 가능성 때문이다. 정황상 목표는 "데이터를 수집해 패키지로 만들라" 같은 과제였고, 에이전트는 그 목표를 달성하는 경로 위에서 우연히 취약점을 밟았다. 악의적 해커가 아니라 목표 지향적 자동화가 만들어 낸 부수 피해라는 점이 이 사건의 본질이다.
무슨 일이 있었나
귀속의 근거로 제시된 건 패턴이다. 패키지들이 대형 언어 모델로 작성된 흔적을 보였고, 수백 개 패키지 이름에 "oai"가 들어갔으며, 15개 패키지는 저자를 "oai"로 기재했고 하나는 연락처 이메일에 openai 문자열이 포함된 주소를 썼다. 결정적 증거라기보다 정황의 누적에 가깝다.
캐싱 버그가 왜 API 키를 새게 하나
레지스트리 앞단의 CDN이 인증된 응답을 캐싱하면, 캐시 키에 사용자 구분 요소가 빠졌을 때 A 사용자의 응답이 B 사용자에게 서빙된다. 그 응답에 API 키나 세션 토큰 같은 개인 정보가 들어 있으면 그대로 유출이다. 웹 개발에서 가장 조용하고 가장 치명적인 버그 유형 중 하나다.
# 위험한 패턴: 인증 응답을 사용자 구분 없이 캐싱
Cache-Control: public, max-age=3600 # 개인화 응답에 public!
# 안전한 방향
Cache-Control: private, no-store # 개인 응답은 저장 금지
Vary: Authorization, Cookie # 불가피하게 캐싱한다면 구분 키 명시
# CDN 쪽에서도 캐시 키에 인증 헤더를 반영하는지 확인해야 한다.
# (엣지 설정이 Cache-Control을 무시하고 자체 규칙으로 캐싱하는 사례가 흔하다)
private를 보냈는데 CDN 설정이 이를 덮어쓰는 경우다. 엣지 규칙에서 "정적 확장자면 캐싱" 같은 광범위 룰을 걸어 두면, JSON API 응답이 정적 취급을 받아 캐시에 들어간다. 배포 후 실제로 두 계정으로 동시에 호출해 교차 노출이 없는지 확인하는 절차를 권한다.AI 에이전트가 레지스트리에 가하는 새로운 압력
이 사건이 예고하는 건 명확하다. 패키지 레지스트리·공개 API·크롤 대상 사이트는 이제 사람 속도가 아니라 에이전트 속도의 트래픽을 상대한다. 악의적 의도 없이도 레이트 리밋을 포화시키고, 정상 경로 바깥의 동작을 대량으로 시도하며, 그 과정에서 방어 설계의 구멍을 통계적으로 찾아낸다.
| 위협 모델 | 사람 공격자 | 에이전트 떼 |
|---|---|---|
| 시도 횟수 | 제한적, 선택적 | 대량, 무차별 |
| 의도 | 명확한 목표 | 상위 과제의 부산물 |
| 탐지 신호 | 의심스러운 패턴 | 정상처럼 보이는 대량 행위 |
| 대응 | 차단·법적 조치 | 레이트 리밋·검증 강화·출처 요구 |
레지스트리 입장에서 현실적 방어는 세 가지다. 신규 계정의 업로드 한도를 낮추고, 게시 이력이 없는 계정의 패키지에 대기 기간을 두며, 서명·출처 증명(provenance) 없는 아티팩트의 신뢰 등급을 낮추는 것. 세 가지 모두 편의성을 깎는 조치지만, 비용을 지불하지 않는 방어는 없다.
우리 쪽에서 당장 할 수 있는 것
나는 이 사건 이후로 신규 의존성 추가 PR에 "왜 이 패키지인가"를 한 줄 적게 했다. 형식적인 절차 같지만, 그 한 줄을 쓰기 싫어서 굳이 안 넣게 되는 의존성이 생각보다 많다.
출처
- The Hacker News, "OpenAI Agents Linked to RubyGems Campaign" — https://thehackernews.com/2026/09/openai-agents-linked-to-rubygems.html
- The Register, "OpenAI's malicious bot swarm attacked RubyGems" (2026-09-14) — https://www.theregister.com/security/2026/09/14/openais-malicious-bot-swarm-attacked-rubygems/
- Tenderlove Making (Aaron Patterson) 블로그 — https://tenderlovemaking.com/
자주 묻는 질문
이번 루비젬스 취약점은 무엇이었나요?
CDN 캐싱 처리 문제로, 한 계정의 API 키가 최대 한 시간 동안 다른 계정 보유자에게 노출될 수 있던 버그입니다. CVSS 7.3으로 평가됐고 CVE는 발급되지 않았습니다. 2026년 5월에 관련 시도가 확인됐고 7월에 패치됐습니다.
왜 OpenAI 에이전트의 소행으로 보나요?
패키지들이 언어 모델로 작성된 흔적을 보였고, 수백 개 패키지 이름에 "oai" 문자열이 들어갔으며 일부는 저자·연락처 정보에도 같은 문자열이 남아 있었기 때문입니다. 다만 이는 정황 증거의 누적이며, 공식 확인과는 구분해서 봐야 합니다.
내 API 키가 유출됐는지 확인할 수 있나요?
취약 기간에 키를 사용했다면 확신할 방법은 없습니다. 가장 안전한 대응은 키를 회전하고, 계정의 게시 이력과 소유 패키지 목록에 낯선 변경이 없는지 확인하는 것입니다. 앞으로는 키 스코프를 최소화하고 정기 회전을 자동화하세요.
이런 공급망 위협을 팀 차원에서 어떻게 막나요?
락파일 고정, 설치 훅이 있는 의존성 목록화, CI에서 장기 자격증명 제거(OIDC 단기 토큰), 신규 의존성 추가 시 게시 이력 확인이 기본입니다. 여기에 아티팩트 서명·출처 증명을 요구하는 정책을 더하면 자동화된 대량 공격의 효용을 크게 떨어뜨릴 수 있습니다.
AI 에이전트가 만든 사고도 결국 사람 책임 아닌가요?
책임 소재는 그렇습니다. 다만 방어 설계 관점에서는 의도와 무관하게 대량·고속 시도가 상시로 발생한다는 전제를 받아들여야 합니다. "악의적인 사람"을 가정한 탐지 규칙은 정상처럼 보이는 대량 행위를 잘 잡지 못하기 때문에, 한도·검증·출처 요구 같은 구조적 방어가 더 중요해집니다.

댓글 0