요약
2026년 8월 4일, Shai-Hulud npm 공격이 재발하여 다음 경로를 통해 레지스트리에 침투했습니다. keyv 그리고 cacheable 캐싱 라이브러리 계열에서 시작되어, 이후 수백 개의 다른 패키지로 스스로 확산되었습니다. 이는 자가 복제형 자격 증명 탈취 프로그램입니다. 이를 분석한 연구원들은 이 캠페인이 GitHub 저장소 설명에서 스스로를 “Shai-Hulud: Here We Go Again”이라고 밝히고 있음을 발견했는데, 이는 2025년 9월 발생한 npm 공급망 공격 및 지난 6월에 분석했던 샤이-훌루드와 메갈로돈 캠페인.
집계 수치는 누가, 언제 조사했는지에 따라 차이가 있습니다. JFrog은 1,700개 이상의 버전에 걸쳐 400개 이상의 패키지를 기록했습니다. CyberScoop은 초기 공격 물결이 4시간도 채 되지 않아 440개 이상의 패키지를 휩쓸었으며, 최종 집계 결과 월간 설치 횟수가 총 20억 건에 달하는 860개 이상의 패키지가 영향을 받았다고 보도했습니다. 400개 이상의 패키지로 보시면 되며, 일부 집계에 따르면 800개가 넘는 것으로 나타납니다.
주목해야 할 수치는 패키지 수가 아닙니다. 바로 그 4시간입니다. 한 명의 유지보수자 계정이 해킹당했고, 단 하루 오후 만에 이 보안 침해는 아무런 잘못도 없었고 이를 알 수도 없었던 사람들이 관리하는 수백 개의 무관한 프로젝트로 확산되었습니다.
이 캠페인을 연구 대상으로 삼기에 유용한 점은, 이 캠페인이 완전히 서로 다른 두 가지 경로를 통해 침투했으며, 업계의 방어 수단 대부분이 이 두 경로 중 어느 쪽에도 초점을 맞추고 있지 않다는 사실입니다. 침투하기 위해서는 유지보수 담당자를 해킹하고 소스 트리에 악성 코드를 삽입해야 했습니다. 반면 확산 과정에는 코드를 작성할 필요가 전혀 없었습니다. 단지 게시할 수 있는 능력만 있으면 충분했습니다.
그건 서로 다른 두 가지 문제입니다. 각각에 대해 서로 다른 해결책이 필요합니다.
제가 Shai-Hulud npm 공격의 영향을 받고 있을까요?
다른 내용을 읽기 전에 먼저 여기부터 시작하세요.
다음의 어떤 버전을 직접 설치했거나, 귀사의 CI가 설치한 경우 잠재적인 보안 위험에 노출될 수 있습니다. keyv, cacheable, cacheable-request, cache-manager, flat-cache, file-entry-cache, 또는 그 아래에 있는 모든 것 @cacheable 2026년 8월 4일 또는 그 이후의 기간을 대상으로 합니다. 지금까지 기록된 유독 물질 유출 사고는 다음과 같습니다. keyv 6.0.0, cacheable 2.5.1, @cacheable/memory 2.2.1, @cacheable/net 2.1.1, @cacheable/node-cache 3.1.2 및 @cacheable/utils JFrog의 분석에 따르면 2.5.1 버전에서, cache-manager 7.2.10 및 cacheable-request 2020년 1월 3일, 다음 기관에서 확인함 위즈. CyberScoop 보도에 따르면 flat-cache 그리고 file-entry-cache 첫 번째 목록이 공개된 지 약 30분 이내에 이어서 공개되었습니다. 공개된 모든 목록은 배포되는 동안에도 계속 추가되고 있었으므로, 본인의 락파일에서 어떤 항목이 해결되었는지 확인해 보시기 바랍니다.
다음 항목에 해당하는 패키지를 사용하는 경우, 두 번째 파동을 통해 노출된 것으로 간주됩니다. @onereach, @or-sdk, @nebula.js, @qlik, @ornikar 또는 @servicetitan scopes 패키지나, 웜이 침투한 컴퓨터에 관리자의 npm 토큰이 저장되어 있던 수백 개의 다른 패키지 중 어느 것이든 해당됩니다. 8월 4일 또는 그 이후에 수행된 모든 npm install 명령은 실제로 어떤 패키지가 설치되었는지 확인할 때까지 의심스러운 것으로 간주하십시오.
한 가지 세부 사항이 폭발 반경을 상당히 좁혀줍니다. 페이로드는 npm에서 시작되어 preinstall 라이프사이클 훅이며, npm 12 이상 버전에서는 이러한 훅이 기본적으로 실행되지 않습니다. 빌더 및 개발자 컴퓨터가 npm 12 이상 버전을 사용 중이고 기본 스크립트 정책이 적용되어 있거나, 다음 명령어로 설치한 경우 --ignore-scripts, 만약 악성 타르볼이 node_modules, 단, 해당 패키지에 대해 라이프사이클 스크립트가 명시적으로 다시 활성화된 경우는 예외입니다. JFrog는 이 점을 분명히 밝혔으며, 이는 이번 사건에서 가장 유용한 긍정적인 소식입니다.
한 가지 세부 사항이 그 폭을 넓혀줍니다. keyv 그리고 flat-cache 대부분의 팀이 선택하지 않는 패키지들입니다. 이 패키지들은 ESLint나 HTTP 클라이언트 라이브러리의 전이적 종속성으로, 여러 계층 아래에 위치하게 되며, 해당 프로젝트에서는 이 패키지들이 존재한다는 사실조차 알지 못합니다.
바로 그 때문에 이번 공격은 이전 공격들보다 더 광범위하게 퍼졌습니다. CyberScoop이 인용한 Wiz 텔레메트리 자료에 따르면, 침해된 패키지 중 가장 널리 퍼진 것은 해당 업체가 모니터링하는 클라우드 환경의 약 46%에서 발견된 반면, 이전 Shai-Hulud 2.0 공격의 경우 약 28%에 그쳤습니다. Shai-Hulud npm 공격은 아무도 신경 쓰지 않는 인프라를 통해 침투함으로써 그 영향력을 확대했습니다.
Shai-Hulud npm 공격이 어떻게 전개되었는가
침입 경로는 한 유지보수 담당자의 GitHub 계정이었습니다. Wiz는 이를 다음과 같이 설명합니다. keyv 그리고 cacheable “GitHub 관리자 계정이 해킹당해 악성 패키지 버전이 공개된” 사건으로 인해 패키지가 침해되었으며, 사건의 경과를 다음과 같이 설명합니다. 공격자는 먼저 해킹당한 계정을 이용해 IDE 지속성 페이로드를 keyv 저장소 자체를, 그리고 얼마 지나지 않아 새로운 버전을 공개했으며 keyv 탑재물을 운반하며.
그 순서가 중요합니다. 코드는 레지스트리에 등록되기 전에 프로젝트에 포함되었으므로, npm에 배포된 릴리스는 해당 프로젝트의 자체 게시 절차를 우회한 것이 아니라 그 절차를 통해 나온 것입니다. 해당 절차의 관점에서 볼 때, 권한이 있는 사용자가 리포지토리에 코드를 올리고 릴리스를 생성한 것입니다. 그것이 바로 그 절차가 존재하는 이유입니다.
각 패키지에는 두 개의 파일이 들어 있었습니다. 첫 번째는, setup.mjs,는 다음으로 연결된 작은 난독화 드로퍼입니다. package.json ~처럼 "preinstall": "node setup.mjs". 이 파일은 페이로드를 포함하지 않습니다. 이 파일은 Bun 자바스크립트 런타임을 다운로드하여 압축을 풀고, 이를 이용해 두 번째 파일을 실행합니다. 이 두 번째 파일은 대략 710~728킬로바이트 크기의 심하게 난독화된 자바스크립트 파일로, 이름은 math_init.js 일부 패키지에서는 그리고 Math_Symbol.js 다른 사람들 속에서.
이미 설치되어 있는 Node 런타임 대신 Bun을 사용하는 것은 의도적인 선택입니다. 이를 통해 피해자의 Node 버전에 관계없이 페이로드에 예측 가능한 실행 환경을 제공할 수 있으며, 실제 작업은 패키지 관리자가 감시하지 않는 프로세스에서 수행되도록 합니다.
첫 번째 문: 독이 든 원천 나무
For keyv 그리고 cacheable 패키지의 경우, 악성 코드는 업스트림 Git 저장소에 존재했습니다. 이는 겉으로 보이는 것보다 훨씬 더 중요한 문제입니다.
이는 소스 코드가 신뢰할 수 있고 공개된 아티팩트만이 의심스럽다는 가정 하에 구축된 어떤 것이라도 이 타협안이 견뎌낸다는 것을 의미합니다. 소스 코드에서 다시 빌드하면 백도어도 함께 재현됩니다. 아티팩트를 소스 코드와 대조해 검증하면, 그 아티팩트가 유효한 것으로 확인됩니다. 출처 증명서를 확인해 보면, 이는 진품입니다. 왜냐하면 빌드가 주장하는 대로, 주장하는 커밋에서 시작하여 주장하는 워크플로우를 통해 실제로 수행되었기 때문입니다. 이 증명서는 아티팩트를 소스 코드와 빌드 프로세스에 연결하며, 둘 다 실제 존재했습니다. 단지 소스 코드가 이미 변조되어 있었을 뿐입니다.
이 캠페인은 그 논리를 다른 대상을 겨냥해 극한까지 밀어붙였습니다. JFrog은 OpenSearch를 겨냥한 특수한 경우의 로직을 발견했습니다. opensearch-js GitHub Actions OIDC를 사용하여 신뢰할 수 있는 게시 기능을 제공하는 저장소: 해당 저장소가 opensearch-js 그리고 누구의 GITHUB_WORKFLOW_REF 일치함 release-drafter.yml, 페이로드는 다음 범위로 설정된 Actions OIDC 토큰을 요청했습니다. npm:registry.npmjs.org, 이를 게시 토큰으로 교환하고, 악성 타르볼을 게시한 뒤, Fulcio와 Rekor를 통해 Sigstore 번들을 생성함으로써 결과물이 신뢰할 수 있는 워크플로 컨텍스트에서 비롯된 유효한 출처 정보를 갖도록 했습니다.
이 문을 막을 수 있는 가장 확실한 방법은 누군가 또는 무언가가 diff를 읽어내는 것입니다.
두 번째 문: 아무도 코드를 작성하지 않은 400개의 패키지
바로 이곳에서 캠페인의 규모가 커졌으며, 이곳의 메커니즘은 입문 단계와 달라져 이에 대항하는 방어 방식도 달라집니다.
이 페이로드는 다음에서 npm 토큰을 수집합니다. ~/.npmrc 그리고 레지스트리를 기준으로 해당 토큰의 유효성을 확인합니다. 쓰기 권한이 있는 토큰을 발견하면 bypass_2fa true로 설정하면, JFrog의 해당 루틴 설명을 인용하자면 다음과 같은 작업을 수행합니다. 현재 latest 타르볼, 실행 중인 샘플을 다음 위치로 복사합니다. package/math_init.js, 로더를 다음 위치에 기록합니다. package/setup.mjs, 패키지의 전체 내용을 대체합니다. scripts 객체와 집합 scripts.preinstall 에 node setup.mjs, 패치 버전을 올린 다음, 직접 인증을 통해 다시 패키징하고 게시합니다. PUT npm 레지스트리에.
그 과정을 다시 한 번 읽어보시고 무엇이 빠져 있는지 확인해 보세요. 커밋도, 풀 리퀘스트도, CI 실행도, 그 어떤 형태의 저장소 접근도 없었습니다. 수백 개에 달하는 이 보조 패키지들의 소스 트리는 처음부터 끝까지 깨끗했고, 지금도 여전히 깨끗한 상태입니다. 악성 코드는 공격자의 컴퓨터에서 압축된 타르볼 내부에만 존재했으며, 도난당한 인증 정보를 이용해 레지스트리로 바로 푸시되었기 때문입니다.
패키지 수로 따지면 이번 사건의 대부분은 다음과 같습니다. 리포지토리에 아무런 변경도 가해지지 않았고, 코드 검토 절차가 우회된 적도 없으며, CI에서 비정상적인 사항이 감지된 적도 없음에도 불구하고, 사용자들이 어쨌든 악성코드를 설치해 버린 수백 명의 유지보수자입니다.
페이로드가 실은 것
이 자격 증명 수집기는 이번 캠페인을 위해 작성된 것이라기보다는 여러 캠페인을 거치며 축적된 목록을 바탕으로 한 것처럼 매우 철저하게 구성되어 있습니다. 이 수집기는 리눅스, macOS, 윈도우 전반에 걸쳐 수백 개의 구성된 경로를 탐색하며, 패키지 관리자 토큰, 구성 파일 및 인스턴스 메타데이터 서비스에서 추출한 클라우드 자격 증명, 쿠버네티스 서비스 계정 토큰, HashiCorp Vault KV 저장소 전체, SSH 키, VPN 프로필, 브라우저 자격 증명 저장소, Stripe 및 Slack 토큰, 그리고 OpenAI, Anthropic, Cursor, Gemini를 포함한 AI 서비스용 키 등을 수집합니다.
GitHub Actions 러너에서는 러너가 이미 복호화한 시크릿에 대해서도 프로세스 메모리를 덤프합니다. 이 모든 과정의 기반에는 200개 이상의 글로브 패턴을 샅샅이 훑어내는 작업이 자리 잡고 있습니다. .env 파일, 개인 키, Terraform 상태 파일 및 KeePass 데이터베이스.
암호화된 결과는 피해자 본인의 계정으로 생성된 공개 GitHub 저장소에 저장되며, 설명은 “Shai-Hulud: Here We Go Again” 형식을 따릅니다. Ox Security는 GitHub에서 검색 가능한 이러한 저장소가 800개 이상이라고 집계했습니다. 대체 HTTP 채널은 실제 사용되기 두 달 이상 전인 2026년 5월 22일에 등록된 도메인을 가리키고 있습니다.
Ox Security는 다른 분석 보고서들에서는 다루지 않은 또 다른 방해 기전에 대해서도 보고하고 있습니다. 바로 도난당한 GitHub 토큰이 취소될 경우, 악성코드가 감염된 시스템에 대해 삭제 명령을 내리는 것입니다. 이는 독립적으로 확인된 사실이 아니라 한 공급업체가 보고한 내용으로 간주해야 하지만, 문제 해결 순서를 계획할 때 이 점을 고려해야 합니다. 호스트를 격리하기 전에 자격 증명을 취소하면 예상치 못한 문제가 발생할 수 있습니다.
또한 귀사의 편집장도 공격 대상으로 삼았습니다
GitHub 측의 전파 과정은 레지스트리보다는 개발자의 컴퓨터를 대상으로 하며, 이 부분이 여전히 어딘가의 저장소에 눈에 띄지 않은 채 남아 있을 가능성이 가장 높습니다.
도난당한 GitHub 개인 액세스 토큰을 사용하여, 이 페이로드는 리포지토리당 최대 50개의 브랜치에 걸쳐 5개의 파일을 커밋합니다: .vscode/tasks.json, .vscode/setup.mjs, .claude/math_init.js, .claude/settings.json 그리고 .claude/setup.mjs. 그 tasks.json 이 항목은 “환경 설정”이라는 이름의 작업을 정의하며, 이 작업은 node .claude/setup.mjs, 그리고 settings.json 항목은 a를 등록합니다 SessionStart 훅. 개발자가 VS Code에서 리포지토리를 열거나 AI 코딩 세션을 시작할 때 실행됩니다.
커밋의 제목은 다음과 같습니다. chore: update config 그리고 그들을 저자로 표기하는 위조된 공동 저자 명단을 포함하고 있으며, [email protected]. 도난당한 토큰에 워크플로 범위가 포함된 경우, 페이로드는 GitHub Actions 시크릿을 아티팩트에 압축하여 다운로드함으로써 이를 유출하는 GitHub Actions 워크플로를 작성하며, 이는 dependabot/github_actions/format/setup-formatter.
이 이름들은 하나하나 훑어보는 수준에서도 눈에 띄지 않도록 신중하게 선택된 것들입니다. Dependabot처럼 보이는 브랜치, CodeQL처럼 보이는 워크플로, 설정 정리 작업처럼 보이는 커밋, 포맷터처럼 보이는 아티팩트, 그리고 현재 많은 팀이 리포지토리에 정식으로 커밋하고 있는 AI 어시스턴트처럼 보이는 공동 작성자 정보 등이 바로 그 예입니다.
기존 스캐너들이 왜 Shai-Hulud npm 공격을 탐지하지 못했는가
대부분의 조직이 의존하고 있는 도구는, 이번 공격이 제기하지 않는 질문에 답하기 위해 개발된 것입니다.
취약점 스캐너는 사용자의 종속성 트리를 알려진 CVE 데이터베이스와 비교합니다. 8월 4일 당시에는 해당 항목에 대한 CVE가 없었습니다. keyv 6.0.0 버전이었던 탓에 해당되는 내용이 없었습니다. 보안 권고문이 발표되었을 때쯤에는 4시간의 대응 시간이 이미 지나버렸고, 웜은 이미 다른 곳으로 이동한 후였습니다.
이는 스캐너에 대한 비판이 아닙니다. 알려진 취약점 탐지는 별개의 업무이며, 스캐너는 이 부분을 잘 수행하고 있습니다.
레지스트리 측 계정 제어 기능 역시 의도된 대로 작동했습니다. 2단계 인증은 비밀번호만 알고 있는 사용자로부터 게시 계정을 보호합니다. 하지만 해당 계정이 자동화를 위해 생성한 토큰에 대해서는 보호하지 못하며, 웜은 특히 2단계 인증을 우회하도록 표시된 토큰을 노렸습니다. 바로 그 토큰들이 사람의 개입 없이 작동하기 때문입니다. 많은 자동화된 npm 게시 설정은 역사적으로 바로 그런 종류의 인증 정보, 즉 사람의 개입 없이 게시할 수 있는 토큰에 의존해 왔습니다.
출처 확인과 서명 검증은 가장 까다로운 사례입니다. 이번 캠페인에서 이 두 가지가 명시된 대로 정확히 작동했음에도 불구하고 결과적으로 ‘녹색’ 판정을 받았기 때문입니다. OpenSearch 공격 시도가 가장 명확한 예시입니다. 진정으로 신뢰받는 워크플로를 통해 게시된 악성 타르볼에, Fulcio와 Rekor를 통해 생성된 Sigstore 번들이 이에 맞춰 짝을 이루고 있는 경우입니다.
출처(Provenance)는 “이 아티팩트가 주장하는 출처와 전달 경로를 통해 온 것인가”라는 질문에 답을 주며, 그 대답은 ‘예’였습니다. 이 질문은 답할 가치가 있으며, 여기서 무의미해지는 것은 아닙니다. 하지만 녹색 체크 표시는 아티팩트가 어디서 왔는지만 알려줄 뿐, 그곳에서 온 것이 안전한지는 알려주지 않습니다.
접근성 분석에서도 동일한 형태의 공백이 나타납니다. 취약한 코드 경로가 절대 호출되지 않는다는 사실을 확인하는 것은 CVE 작업의 우선순위를 정하는 데 유용합니다. A preinstall 훅은 애플리케이션에서 호출할 수 있을 필요가 없습니다. 이 훅은 애플리케이션이 이를 호출하기 훨씬 전에, 의존성 설치 과정의 일부로 실행됩니다.
문 두 개에는 서로 다른 자물쇠 두 개가 필요합니다
이 캠페인의 두 진입 경로는 완전히 다른 요인에 의해 차단되므로, 어떤 TuxCare 메커니즘이 어떤 문을 닫는지 정확히 파악해 두는 것이 좋습니다.
npm용 SecureChain은 TuxCare가 자체 빌드 인프라에서 소스 코드를 재컴파일하여 자체 카탈로그에 게시하는 오픈 소스 구성 요소를 제공하며, 각 아티팩트에는 출처 증명서와 CycloneDX SBOM이 함께 제공됩니다. 고객은 패키지 관리자를 공개 레지스트리 대신 SecureChain으로 설정하므로, TuxCare가 빌드 시 구성 요소를 해결하는 기본 소스가 됩니다.
이를 실행하는 방법에는 두 가지가 있습니다. SecureChain 카탈로그에 포함된 항목만 사용하거나, 그 앞에 레지스트리 관리자를 배치하여 카탈로그에 아직 포함되지 않은 항목은 공용 레지스트리로 전달되도록 하는 방법입니다.
출판권에 대한 두 번째 문이 닫히다
먼저 전파 메커니즘부터 살펴보겠습니다. 이 부분이 사건의 더 큰 부분을 차지할 뿐만 아니라 설명하기도 더 명확하기 때문입니다. 웜의 방식은 공개된 타르볼을 가져와 두 개의 파일을 삽입한 뒤, scripts 블록을 생성하고, 패치 버전을 올린 뒤, 탈취한 토큰을 이용해 그 결과를 레지스트리에 등록합니다. 이 공격에는 무엇보다도 한 가지가 필수적입니다. 바로 리졸버가 패키지를 검색하는 위치에 새 버전의 패키지를 배치할 수 있는 능력입니다.
SecureChain 카탈로그에 포함된 모든 구성 요소의 경우, 빌드에서 해결하는 버전은 TuxCare가 자체 CI 환경에서 해당 프로젝트의 소스 코드를 기반으로 빌드한 버전입니다. 공격자가 재게시한 타르볼은 해당 경로에 존재하지 않으며, 공개 레지스트리에 아무리 많이 게시한다고 해도 그곳에 포함될 수 없습니다. 수백 개에 달하는 2차 패키지에 포함된 악성 파일들은 npm에 푸시된 타르볼 내부에만 존재했습니다. 소스 코드에는 애초에 존재하지 않았습니다. 소스 코드로부터 빌드하더라도 이러한 파일들은 재현되지 않으며, 해당 위치에 아무것도 없기 때문에 탐지할 대상도 없습니다.
이 메커니즘을 명확히 설명해 둘 필요가 있습니다. 이 캠페인을 대규모로 확산시킨 기술은 빌드 결과물이 어디에서 해결되는지에 관계없이 게시 권한에 접근해야 합니다. 만약 공개 레지스트리라면, 도난당한 토큰 하나만으로도 충분합니다. 하지만 SecureChain 카탈로그라면, 도난당한 npm 토큰으로도 공격자는 아무것도 얻을 수 없습니다. npm에 게시한다고 해서 빌드 결과물 앞에 버전이 붙는 것은 아니기 때문입니다.
차이점 확인을 위한 첫 번째 문이 닫히다
시작점은 더 어려운 문제이며, 업계가 대체로 회피해 온 부분이기도 한데, 솔직히 말해 누군가는 코드를 살펴봐야 하기 때문이다.
SecureChain은 패키지 온보딩 파이프라인에서 악성 코드 탐지 기능을 실행합니다. 이 탐지기는 새 패키지 버전과 이전 버전 간의 소스 코드 차이점을 분석하여, 공급망 침해와 관련된 행동 패턴—의심스러운 빌드 시 실행, 난독화, 데이터 유출 및 기타 악의적 의도를 나타내는 지표—을 찾아냅니다. 빌드 시 실행 후크는 명시적으로 탐지 범위에 포함되며, 여기에는 npm도 포함됩니다. preinstall 그리고 postinstall, 파이썬 setup.py 실행 경로 및 Maven 플러그인 호출.
플래그가 지정된 버전은 릴리스에서 제외됩니다. 이 버전은 카탈로그에 등록되지 않으므로, 애초에 고객의 빌드에 반영되지 않습니다.
8월 4일에 그 탐지기가 무엇을 관측하고 있었을지 생각해 보자. 마지막으로 깨끗했던 때와의 차이점은 keyv 그리고 keyv 6.0.0에는 다음과 같은 이름의 새 파일이 포함되어 있습니다. setup.mjs, 약 710킬로바이트 크기의 심하게 난독화된 자바스크립트로 구성된 두 번째 새 파일, 그리고 preinstall 다음에 실린 기사 package.json 이전에는 캐싱 라이브러리가 전혀 없었던 곳에서.
이 모든 것은 탐지기가 감지하도록 설계된 신호들입니다. 소스 트리에 난독화된 콘텐츠가 나타나는 것, 이전에는 없었던 빌드 시간 실행 후크가 등장하는 것, 그리고 이 두 가지가 모두 키-값 쌍을 저장하는 것이 유일한 역할인 라이브러리의 동일한 릴리스에 포함된 것입니다. 이 탐지기는 알려진 악성 파일과 일치하는지 여부를 확인하기보다는 변경 사항이 어떤 기능을 하는지에 초점을 맞추기 때문에, 동일한 기법을 다른 파일 이름으로 재포장하더라도 탐지망을 통과할 수 없습니다.
소스 수준 탐지는 첫 번째 취약점을 직접 해결하는 SecureChain의 메커니즘입니다. 소스 자체가 악성 코드인 경우, 소스를 기반으로 재구축하는 것은 소용이 없습니다. 이것이 바로 지난 6월 우리가 출처 추적(provenance)에 대해 제기했던 주장이며, 이번 공격 캠페인으로도 그 주장은 변하지 않았습니다. 오히려 같은 날 오후에 두 가지 실패 양상이 모두 드러남으로써 그 주장은 더욱 명확해졌습니다.
SecureChain을 사용하지 않는 완화 조치
빌드가 공개 레지스트리에서 직접 해결되는 경우, 이 캠페인의 효과를 제한했을 제어 요소들은 다음과 같으며, 효과가 나타나는 순서대로 나열했습니다.
모든 개발자 컴퓨터와 모든 CI 실행 환경에서 npm 12 이상으로 업데이트하십시오. preinstall 기본 변경 사항은 [특정 위치]에 있는 악성 타르볼과 node_modules 그리고 실행된 악성 타르볼. 아직 업그레이드할 수 없다면, 다음 명령어로 설치하십시오. --ignore-scripts 그리고 실제로 빌드 단계가 필요한 패키지는 명명된 예외로 처리합니다.
빌드가 패키지가 출시된 당일에 해당 버전을 설치하지 않도록 최소 출시 기간을 설정하세요. 현재 네 가지 주요 패키지 관리자 모두 이 기능을 기본적으로 제공하고 있습니다. npm의 경우 min-release-age 11.10.0부터 bun에는 minimumReleaseAge 1.3.0 버전부터는 pnpm과 yarn 모두 패키지별 제외 기능을 지원하는 대응 기능이 추가되었습니다. 3~7일 동안의 비용은 매우 적으며, 이번 캠페인의 4시간 전체 기간을 충분히 커버할 수 있었을 것입니다. 이 글에서 단 한 가지 설정 변경 사항만 기억하신다면, 바로 이것입니다.
npm 토큰의 쓰기 권한을 점검하고, 웜이 노렸던 바로 그 조합인 2단계 인증 우회 가능성도 함께 확인해야 합니다. 게시용 토큰은 유효 기간을 짧게 설정하고, 해당 토큰이 필요한 패키지로 범위를 제한하며, 사람에게가 아니라 자동화 시스템에 발급해야 합니다. 워크플로가 지원하는 경우 신뢰 기반 게시 방식을 선호하되, OpenSearch 사례에서 알 수 있듯이 이는 신뢰 경계를 제거하는 것이 아니라 워크플로 정의로 옮겨놓는다는 점을 이해해야 합니다.
이 페이로드는 러너 프로세스의 메모리를 덤프하므로, CI 러너를 기밀 정보를 다루는 프로덕션 시스템으로 간주하십시오. OIDC 신뢰 정책의 적용 범위를 전체 조직이 아닌 특정 리포지토리 및 특정 워크플로 파일로 제한하고, 다음 조건 하에서 변경 사항에 대한 검토를 의무화하십시오. .github/workflows.
편집기와 AI 코딩 도구가 저장소에서 자동으로 실행하는 내용을 확인해 보세요. 이 .vscode/tasks.json 그리고 .claude/settings.json Vector가 제대로 작동하는 이유는 프로젝트를 열면 해당 프로젝트와 함께 제공된 구성 파일이 실행되기 때문입니다. 코드 검토 시에는 해당 파일들을 실행 가능한 콘텐츠로 취급해야 합니다. 실제로 그렇기 때문입니다.
탐지
기본 브랜치뿐만 아니라 모든 리포지토리의 모든 브랜치를 검색하여 다음 다섯 개의 후크 파일을 찾아보세요: .vscode/tasks.json “환경 설정” 작업이 포함된, .vscode/setup.mjs, .claude/setup.mjs, .claude/math_init.js, 그리고 .claude/settings.json ~와 함께 SessionStart hook. 제목이 다음과 같은 커밋을 찾으세요. chore: update config, 특히 공동 저자 명단에 [email protected].
파일 이름을 찾기 위해 의존성 트리와 패키지 캐시를 검색합니다. setup.mjs, math_init.js, Math_Symbol.js 그리고 router_runtime.js. JFrog은 [... ]에서 추출한 페이로드의 SHA-256 해시 값을 공개했습니다. keyv 6.0.0이므로, 가능한 경우 파일 이름만 기준으로 삼기보다는 이 버전을 기준으로 일치 여부를 확인하십시오.
GitHub 감사 로그에서 다음을 찾아보세요. dependabot/github_actions/format/setup-formatter 브랜치, 다음과 같은 이름의 워크플로가 실행됩니다. codeql_analysis.yml 또는 아티팩트를 빠르게 생성 및 삭제하는 “Run Copilot” 명령어와, 이름이 format-results. 소속 조직의 계정 아래에 생성된 저장소 중에서 설명이 “Shai-Hulud: Here We Go Again” 형식과 일치하는 것을 찾아보세요.
네트워크 측면에서는 다음 항목에 대한 연결을 확인하십시오. npm-cache.com, 그리고 블록체인과 통신할 이유가 전혀 없는 빌드 인프라에서 공개 이더리움 RPC 엔드포인트로 전송되는 아웃바운드 트래픽의 경우입니다. 페이로드는 이더리움 메인넷 계약에서 현재 C2 도메인 목록을 읽어오기 때문에, 두 가지 모두 유용한 탐지 신호이지만 어느 쪽도 신뢰할 수 있는 차단 기준은 아닙니다.
우선순위 순으로 정리한 Shai-Hulud npm 공격 대응 체크리스트
- 다음 중 어느 쪽인지 판단하십시오.
preinstall스크립트가 실행되었을 가능성이 있습니다. 2026년 8월 4일 또는 그 이후에 종속성을 설치한 모든 컴퓨터와 러너에서 npm 버전을 확인하고, 다음 사항을 확인하십시오.--ignore-scripts적용되어 있었습니다. 후크가 실행되지 않았다면, 남은 작업은 사고 대응이 아니라 의존성 관리입니다. - 취소하기 전에 먼저 격리하십시오. 먼저 tarball, npm 로그, CI 로그, GitHub 감사 로그 및 러너 이미지를 보존하십시오. 토큰 취소 시 보고된 삭제 동작을 고려하여, 페이로드를 실행한 것으로 의심되는 모든 호스트에서 자격 증명 교체에 앞서 시퀀스를 격리하십시오.
- 영향을 받는 모든 패키지에 대해 2026년 8월 4일 이전에 공개된 버전의 잠금 파일을 고정하고, 패키지 관리자의 캐시를 지워 악성 tarball 파일이 로컬에 남아 있지 않도록 하십시오.
- 먼저 npm 토큰을 취소하십시오. 쓰기 권한이 있거나 2단계 인증 우회 기능이 있는 토큰부터 시작하여, GitHub 개인 액세스 토큰과 Actions 토큰 순으로 처리하십시오.
- 수집기가 읽을 수 있는 모든 항목을, 마치 실제로 읽은 것으로 가정하고 회전 처리하십시오: 클라우드 인증 정보, 쿠버네티스 서비스 계정 토큰, Vault 내용, 데이터베이스 인증 정보, SSH 키, VPN 프로필, Stripe 및 Slack 토큰, 그리고 AI 서비스용 API 키 등입니다.
- 모든 리포지토리의 모든 브랜치에서 5개의 훅 파일과 마커 커밋을 찾아 모두 제거합니다.
- 깨끗한 이미지에서 CI 러너와 개발자 환경을 다시 구축하십시오. 개별 파일을 정리하는 것만으로는 시스템이 깨끗하다는 것을 보장할 수 없습니다.
- npm 신뢰할 수 있는 게시자 및 GitHub OIDC 신뢰 정책을 검토하고, 특히 릴리스 자동화 워크플로우에 주의를 기울여야 합니다. 해당 워크플로우가 구체적으로 표적이 되었기 때문입니다.
- 그 후에야 예방 조치로 넘어가야 합니다: 릴리스 연령 하한선, npm 12, 토큰 범위 지정, 워크플로 검토 요건 등입니다.
왜 이것이 계속 효과가 있는 걸까
배후에 대한 규명은 아직 불분명하다. CyberScoop이 인용한 연구원들은 2026년 초 1,000개 이상의 패키지를 침해한 공격 주체인 ‘TeamPCP’와의 직접적인 연관성을 입증할 확실한 증거는 아직 확보하지 못했으나, 그 가능성은 배제할 수 없다고 밝혔다. 여러 기업이 영향을 받은 모든 패키지에서 동일한 페이로드와 동일한 패턴을 보고했는데, 이는 모방범이 아닌 단일 공격 주체나 긴밀하게 협력하는 집단에 의한 소행임을 시사한다.
저작권 귀속 문제는 구조적 문제만큼 흥미롭지 않습니다. 같은 기법이 계속해서 통하는 이유는, 그것이 실제로는 기법이 아니라 패키지 생태계가 어떻게 구축되는지에 대한 관찰 결과이기 때문입니다. 즉, 패키지 버전을 공개할 수 있는 사람이라면 누구나 해당 패키지가 설치된 모든 컴퓨터에서 코드를 실행할 수 있습니다. 이는 npm의 버그가 아닙니다. 이것이 바로 라이프사이클 스크립트의 존재 이유이며, 수천 개의 합법적인 패키지가 이러한 동작에 의존하고 있습니다.
여기에 더해, 게시 권한은 개인이 보유하고 있으며, 개인이 토큰을 소유하고 있고, 토큰은 노트북의 파일에 저장되어 있으며, 페이로드가 가장 먼저 하는 행동이 바로 해당 파일을 찾아내는 것임을 고려해야 합니다. 이 웜의 확산 방식은 특별한 기능이 아닙니다. 이는 관리자가 아닌 악성코드가 수행하는 평범한 게시 워크플로우일 뿐입니다.
6월에는 ‘친환경 출처 확인’이 신뢰 여부를 결정하는 데 있어 하나의 신호일 뿐, 최종 판단 기준은 아니라는 주장이 제기되었습니다. 8월에 제기된 주장은 그 범위가 더 좁고 기계적인 성격을 띠고 있습니다. 즉, 빌드 과정에서 참조하는 버전이 도난당한 토큰을 가진 공격자가 가장 최근에 푸시한 버전이라면, 그 외의 모든 통제 수단은 여러분이 내리지 않은 결정의 결과물에 불과하다는 것입니다.
그러니 의도적으로 그 결정을 내리세요. 8월 4일에 빌드가 어떤 버전을 해결했는지 확인한 다음, 다음 릴리스 물결이 닥치기 전에 릴리스 기간 하한선을 설정하세요. 다음 릴리스 물결은 분명히 올 테니까요. 만약 빌드가 해결한 버전이 단순히 게시 토큰을 보유한 사람이 아닌, 누군가가 재빌드하고 검토하는 카탈로그에서 나오기를 원하신다면, npm용 SecureChain에 대해 저희와 상담해 주세요.
자주 묻는 질문
이것이 2025년 9월에 있었던 샤이-훌루드 작전과 같은 건가요?
같은 계보에 속하며, 스스로도 그렇게 명명하고 있지만, 동일한 사건은 아닙니다. 2026년 8월 발생한 Shai-Hulud npm 공격은 다음 경로를 통해 침투했습니다. keyv 그리고 cacheable 이전에 감염된 패키지들보다는, CyberScoop은 이를 ‘Mini Shai-Hulud’의 변종으로 설명하고 있습니다. Wiz가 집계한 환경 내 유포 현황 수치에 따르면, 이 변종은 이전 변종들보다 훨씬 더 광범위하게 확산되었습니다.
이 패키지의 GitHub 저장소는 문제가 없어 보입니다. 그렇다면 제가 설치한 버전도 문제가 없다는 뜻일까요?
아닙니다. 그리고 바로 이 점이 가장 간과되기 쉬운 세부 사항입니다. 수백 개에 달하는 2차 패키지의 경우, 공격자는 소스 저장소를 전혀 건드리지 않았습니다. 악성 코드는 npm에 게시된 타르볼에만 존재했습니다. 깨끗한 저장소라고 해서 패키지 관리자가 실제로 무엇을 다운로드했는지에 대한 정보를 알려주지는 않습니다. 소스가 아니라 lockfile이 해결한 버전을 확인하십시오.
유효한 서명이나 출처 증명서가 있으면 해당 패키지가 안전한 것일까요?
아닙니다. OpenSearch 사례에서 이 캠페인은 Fulcio와 Rekor를 통해, 신뢰할 수 있는 워크플로우에서 게시된 악성 타르볼에 대한 진정한 Sigstore 번들을 생성했습니다. 출처 추적은 출처를 확인하는 것이지, 콘텐츠를 평가하는 것은 아닙니다.
저희는 npm을 프록시하는 사설 레지스트리를 사용하고 있습니다. 보안상 문제가 없을까요?
단, 단순히 아티팩트를 전달하는 것 이상의 기능을 수행할 때에만 해당됩니다. 캐싱 프록시는 조작된 버전을 기꺼이 캐싱하여 제공하며, 대개 업스트림보다 더 빠르게 처리합니다. 중요한 것은 이를 통해 전달되는 내용을 검사하거나, 재빌드하거나, 지연시키는 과정이 있는지 여부입니다.
저희는 빌드할 때마다 취약점 스캐너를 실행합니다. 그런데 왜 이 문제를 포착하지 못했을까요?
확인된 취약점은 없었습니다. 스캐너는 사용자의 종속성 트리를 CVE 데이터베이스와 대조하는데, 8월 4일 당시 악성 코드가 삽입된 버전은 출시된 지 몇 시간밖에 되지 않았고 관련 권고 사항도 게시되지 않은 상태였습니다. 알려진 취약점 스캔과 악성 코드 탐지는 서로 다른 문제이므로, 각각 다른 도구가 필요합니다.
이 상황에서 SecureChain은 어떻게 대응했을까요?
두 가지 진입 경로에 따라 상황이 다릅니다. 타르볼 재게시로 인해 보안이 침해된 수백 개의 패키지의 경우, SecureChain은 업스트림에서 가장 최근에 공개한 버전을 프록시하는 대신 TuxCare가 소스 코드에서 직접 빌드한 버전을 제공하기 때문에, 빌드 과정에서 참조하는 경로에는 손상된 버전이 포함되어 있지 않습니다. 초기 keyv 그리고 cacheable 타협안으로, 코드가 먼저 저장소에 등록된 경우, 악성 코드 탐지기가 온보딩 시 버전 차이를 확인하여 릴리스를 보류하므로, 악성 코드가 포함된 버전은 카탈로그에 절대 등록되지 않습니다. 두 방법 모두 다음 전반에 걸쳐 적용됩니다. 시큐어체인 카탈로그에 수록된 모든 구성 요소에 대해.

