올해 npm 생태계에서는 악성 코드가 정상적인 배포 경로를 타고 퍼진 사고가 이어졌습니다. 5월의 TanStack 사고와 8월의 ChainDrop 웜이 대표적입니다. 두 사고 모두 "수상한 패키지를 받아서"가 아니라 "믿던 프로젝트의 정식 릴리스가 오염되어서" 일어났다는 점에서, 패키지 이름만 보고 안심하는 방식으로는 막기 어렵습니다. 이 글은 두 사고의 공식 분석을 바탕으로 수법과 방어를 정리한 것입니다. 수치는 각 보고서가 밝힌 시점 기준이며 이후 변동될 수 있습니다.

사고 1: TanStack (2026년 5월 11일)

  • 피해: @tanstack/* 패키지 42개, 버전 84개가 악성으로 게시되었습니다. 악성 게시는 협정 세계시 19시 20분부터 26분 사이에 있었고, TanStack은 5월 15일에 사고 종료를 알렸습니다. Router와 Start 저장소에서만 일어났고 Query, Table 등 다른 TanStack 패키지는 영향이 없었다고 합니다.
  • 수법: npm 토큰을 훔친 것이 아닙니다. 세 가지 약점이 연결되었습니다. 첫째, 번들 크기를 재는 워크플로가 pull_request_target 이벤트로 포크 저장소의 빌드 코드를 실행했습니다("Pwn Request"로 알려진 패턴). 둘째, 그 코드가 릴리스 워크플로가 나중에 복원하는 캐시를 오염시켰습니다. 셋째, 악성 코드가 GitHub Actions 실행기의 메모리에서 OIDC 토큰을 꺼내 npm에 직접 게시했습니다.
  • 교훈: 팀은 워크플로 구조를 고치고, 외부 액션을 커밋 해시로 고정하고, 캐시를 전부 비웠으며, 같은 패턴이 다른 저장소에 있는지 점검하겠다고 밝혔습니다.

사고 2: ChainDrop 웜 (2026년 8월 4일)

  • 시작: 한 메인테이너의 GitHub 계정이 탈취되어 그의 저장소 세 곳에 악성 커밋이 올라갔습니다. 그 프로젝트의 정식 릴리스 워크플로가 npm OIDC 신뢰 게시로 악성 버전을 올렸기 때문에 출처 증명(provenance)까지 정상으로 붙었습니다. 출처 증명이 있다고 안전한 것은 아니라는 사례입니다.
  • 설치 시 동작: preinstall 훅이 스크립트를 실행하고, 공식 배포처에서 Bun 런타임을 받아 난독화된 2단계 코드를 돌립니다.
  • 탈취 대상과 확산: npm, GitHub, AWS, Kubernetes, Vault 자격 증명과 환경 변수, SSH 키, 클라우드 설정, 암호화폐 지갑 파일, AI 개발 도구의 자격 증명을 노립니다. 훔친 토큰으로 피해자가 게시할 수 있는 다른 패키지에 악성 코드를 심어 다시 게시하는 방식으로 퍼졌습니다.
  • 규모: 분석 업체 StepSecurity는 8월 4일 18시 10분(협정 세계시) 기준으로 패키지 444개, 버전 2,212개를 집계했고 계속 늘어나는 중이라고 밝혔습니다.
  • 지속 수법: 감염된 폴더에 .claude/settings.json의 세션 시작 훅과 .vscode/tasks.json의 폴더 열기 작업을 심어 개발 도구가 실행될 때마다 악성 코드가 다시 실행되게 합니다. 또 탈취한 GitHub 토큰이 폐기되면 동작하는 감시 프로그램을 설치합니다.

개발자가 오늘 적용할 방어

  1. 잠금 파일을 커밋하고 설치는 npm ci로: 의도하지 않은 버전이 들어오는 것을 막습니다.
  2. 설치 스크립트 차단: 두 사고 모두 설치 단계의 스크립트가 출발점입니다. CI와 서버 빌드에서는 --ignore-scripts로 설치하고, 꼭 필요한 패키지만 따로 허용하세요.
  3. 새 버전을 바로 쓰지 않기: 막 게시된 버전은 일정 기간(예: 며칠) 기다렸다가 쓰는 "쿨다운" 정책을 적용하면 초기에 발견되어 내려지는 악성 버전을 피할 수 있습니다.
  4. GitHub Actions 점검: 외부 액션은 태그가 아니라 커밋 해시로 고정하고, pull_request_target에서 포크의 코드를 실행하지 않으며, 릴리스 워크플로는 캐시를 신뢰하지 않게 합니다. CI 실행기의 외부 접속은 허용 목록으로 제한합니다.
  5. 토큰과 계정: GitHub는 2025년 12월 9일 npm 클래식 토큰을 전부 폐기했습니다. 로그인 세션 토큰은 2시간, 쓰기용 세분화 토큰은 최대 90일입니다. 게시 권한이 있는 계정은 피싱에 강한 MFA(보안 키 등)를 쓰고, 오래된 토큰은 정리하세요.
  6. 개발 도구 설정 폴더 감시: .claude/, .vscode/ 같은 폴더에 모르는 파일이나 훅이 생기지 않았는지 코드 리뷰와 정기 점검에 포함하세요. ChainDrop은 바로 이 폴더를 지속 수단으로 썼습니다.

감염이 의심될 때

  • 해당 패키지를 설치한 컴퓨터와 CI 실행기는 침해된 것으로 간주하고, 그곳에서 접근할 수 있었던 자격 증명(npm, GitHub, 클라우드, SSH 등)을 모두 교체합니다.
  • ChainDrop처럼 토큰 폐기에 반응하는 감시 프로그램이 있을 수 있으니, 분석 보고서의 점검 목록을 따라 감시 프로그램을 먼저 제거한 뒤 토큰을 폐기합니다.
  • 잠금 파일과 node_modules에서 보고된 버전과 파일 이름이 있는지 검색합니다.

개발 환경이나 CI 구성을 점검하고 싶은데 어디서부터 해야 할지 막막하다면, 포르본허가 의존성 점검, 설치 정책 정리, 배포 파이프라인 강화까지 언제든 도와드릴 수 있습니다. 아래 문의 양식에 상황을 간단히 남겨 주세요.

참고 자료