-
Shai-Hulud 2.0 악성 npm 패키지가 개발자 머신을 감염시켜 Trigger.dev의 GitHub 조직 접근권을 탈취한 사건 발생
- 감염은 개발자가 pnpm install 실행 시 악성 패키지의 preinstall 스크립트가 실행되며 시작, TruffleHog 도구를 이용해 자격 증명 탈취
- 공격자는 17시간 동안 669개 저장소를 복제하고, 이후 10분간 199개 브랜치에 강제 푸시 및 42개 PR 폐쇄 시도
-
패키지와 프로덕션 시스템은 손상되지 않았으며, 공격은 4분 만에 탐지되어 계정 접근이 차단됨
- 사건 이후 npm 스크립트 비활성화, pnpm 10 업그레이드, OIDC 기반 npm 배포, 브랜치 보호 전면 적용 등 보안 체계 강화
공격 개요
- 2025년 11월 25일, 내부 Slack 디버깅 중 Linus Torvalds 명의의 “init” 커밋이 여러 저장소에 생성되는 이상 징후 발생
- 조사 결과, Shai-Hulud 2.0 공급망 웜이 개발자 머신을 감염시켜 GitHub 자격 증명을 탈취한 것으로 확인
- 이 웜은 500개 이상 npm 패키지를 감염시키고, 25,000개 이상의 저장소에 영향을 미친 것으로 보고됨
- Trigger.dev의 공식 npm 패키지(@trigger.dev/*, CLI)는 감염되지 않음
공격 타임라인
- 11월 24일 04:11 UTC: 악성 패키지 배포 시작
- 20:27 UTC: 독일의 개발자 머신 감염
- 22:36 UTC: 공격자 첫 접근 및 대량 저장소 복제 시작
- 15:27~15:37 UTC (11월 25일): 10분간 파괴적 공격 수행
- 15:32 UTC: 이상 탐지 및 4분 내 접근 차단
- 22:35 UTC: 모든 브랜치 복구 완료
감염 과정
- 개발자가 pnpm install 실행 시, 악성 패키지의 preinstall 스크립트가 실행되어 TruffleHog을 다운로드 및 실행
- TruffleHog은 GitHub 토큰, AWS 자격 증명, npm 토큰, 환경 변수 등을 스캔 후 외부로 유출
- 감염된 머신에서 .trufflehog-cache 디렉터리와 관련 파일이 발견됨
- 감염 원인 패키지는 삭제되어 추적 불가
공격자의 활동
- 감염 후 17시간 동안 정찰 활동 지속
- 미국과 인도 기반 인프라를 이용해 669개 저장소 복제
- 개발자 활동을 모니터링하며 GitHub 토큰을 이용한 접근 유지
- “Sha1-Hulud: The Second Coming”이라는 이름의 저장소 생성, 자격 증명 저장에 사용된 것으로 추정
- 이후 10분간 파괴적 행위 수행
- 16개 저장소에서 199개 브랜치 강제 푸시 시도
- 42개 PR 폐쇄, 일부는 브랜치 보호 설정으로 차단
- 모든 커밋은 “Linus Torvalds <email> / init” 형태로 표시
탐지 및 대응
- Slack 알림을 통해 이상 징후를 실시간 탐지
- 4분 내 감염 계정의 GitHub 접근 차단, 이후 AWS·Vercel·Cloudflare 등 모든 서비스 접근 철회
- AWS CloudTrail 로그 분석 결과, 읽기 전용 API 호출만 존재, 프로덕션 데이터 접근 없음
- AWS는 별도로 Shai-Hulud 관련 의심 행위를 탐지해 경고 발송
피해 및 복구
-
669개 저장소 복제, 199개 브랜치 강제 푸시, 42개 PR 폐쇄
- GitHub의 서버 측 reflog 부재로 복구 어려웠으나, 이벤트 API와 로컬 reflog를 활용해 7시간 내 전체 복원
- npm 패키지 및 프로덕션 인프라는 손상되지 않음
GitHub App 키 노출
- 조사 중 개발자 노트북의 휴지통에서 GitHub App 개인 키 발견
- 해당 키는 고객 저장소에 read/write 권한을 가지며, 즉시 회전 조치
- 데이터베이스(설치 ID 저장)는 손상되지 않아 고객 저장소 접근 증거 없음, 그러나 완전 배제는 불가
- GitHub 지원팀에 추가 로그 요청 및 고객에게 이메일 공지 발송
Shai-Hulud 기술 분석
-
setup_bun.js 실행 시 Bun 런타임을 설치하고 백그라운드에서 bun_environment.js 실행
- TruffleHog을 이용해 $HOME 디렉터리 내 자격 증명 수집
- 수집된 데이터(contents.json, cloud.json, truffleSecrets.json 등)를 랜덤 GitHub 저장소에 3중 base64 인코딩 형태로 업로드
- npm 토큰이 존재할 경우, 감염된 계정의 패키지를 수정·재배포하여 웜 확산
- 자격 증명이 없을 경우 홈 디렉터리 삭제 시도
- 감염 지표 파일: setup_bun.js, bun_environment.js, .trufflehog-cache/ 등
보안 강화 조치
-
npm 스크립트 전면 비활성화 (ignore-scripts=true)
-
pnpm 10 업그레이드: 스크립트 기본 비활성화, minimumReleaseAge(3일) 설정으로 신규 패키지 설치 지연
-
OIDC 기반 npm Trusted Publishers 도입으로 장기 토큰 제거
-
모든 저장소에 브랜치 보호 적용
-
AWS SSO에 Granted 도입, 세션 토큰 암호화
-
GitHub Actions에서 외부 기여자 워크플로 실행 시 승인 필수로 변경
다른 팀을 위한 교훈
- npm 설치 시 실행되는 임의 코드 실행 구조 자체가 공격 표면
-
ignore-scripts=true 설정 및 필요한 패키지만 화이트리스트 관리 필요
-
pnpm minimumReleaseAge로 신규 패키지 설치 지연
-
브랜치 보호와 OIDC 기반 배포는 필수 보안 조치
- 로컬 머신에 장기 자격 증명 저장 금지, CI를 통한 배포만 허용
-
Slack 알림의 소음이 탐지의 열쇠가 되었음
인간적 측면
- 감염된 개발자는 잘못이 없으며, 단순히 npm install 실행만으로 피해 발생
- 공격 중 해당 계정이 수백 개의 무작위 저장소를 자동으로 ‘star’한 흔적 발견
- 사건은 개인의 실수가 아닌 생태계의 구조적 취약성을 드러냄
요약 지표
- 최초 감염 후 첫 공격까지: 약 2시간
- 공격자 접근 유지 시간: 17시간
- 파괴 행위 지속 시간: 10분
- 탐지까지 5분, 차단까지 4분
- 전체 복구 완료까지 7시간
- 복제된 저장소: 669개 / 영향받은 브랜치: 199개 / 폐쇄된 PR: 42개
참고 리소스
- Socket.dev: Shai-Hulud Strikes Again V2
- PostHog, Wiz, Endor Labs, HelixGuard의 분석 보고서
- npm Trusted Publishers, pnpm onlyBuiltDependencies, minimumReleaseAge, Granted 문서