
NPM 샤이‑훌루드 웜 사건, 지금 웹 개발자가 진짜로 걱정해야 할 것

이번 공격이 보여준 것은 "취약점"이 아니라 "생태계의 구조"입니다
요즘 프론트엔드나 백엔드 모두 NPM 없이는 배포를 시작하기도 어렵습니다. 그래서 NPM이 뚫릴 때마다 개발자의 머릿속에는 자연스럽게 같은 질문이 떠오릅니다. "내 프로젝트는 안전한가, 아니면 이미 털렸는데 모르는 것인가."
이번 샤이‑훌루드 웜 사건이 불편한 이유는 단순한 악성 패키지 한두 개 문제가 아니기 때문입니다. 패키지 생태계 전체 구조를 정교하게 악용한 자동화 공격이라는 점이 더 중요합니다. 개인 개발자의 부주의가 아니라, 현대 웹 개발의 기본 전제가 된 도구 체인이 그대로 공격 면으로 노출됐다는 점에서 의미가 달라집니다.
이번 시리즈 공격은 처음에는 Nx 빌드 시스템의 GitHub Actions 취약한 워크플로에서 시작됐습니다. 탈취된 NPM 토큰으로 정상 프로젝트에 telemetry.js를 끼워 넣고, postinstall 스크립트로 자동 실행하게 만들었습니다. 이후에는 Bun 런타임을 끌어들인 setup_bun.js, bun_environment.js로 진화했고, 단순 설치 이상을 수행하는 완전한 웜 형태로 재구성되었습니다.
단순 악성 코드가 아니라 "자기 증식하는 공급망"이 되었다
많은 개발자가 여전히 악성 패키지를 "실수로 설치하면 당하는 것" 정도로만 인식합니다. 하지만 샤이‑훌루드 웜의 핵심은 사용자의 클릭을 거의 필요로 하지 않는 자기 복제 구조에 있습니다. 한번 유지 관리자의 권한만 따내면, 그가 보유한 다른 패키지에 차례로 악성 코드를 밀어 넣고, 그 패키지를 사용하는 수많은 프로젝트 안으로 자연스럽게 확산됩니다.
공격자가 한 번 뚫은 계정에서 끝내지 않고, 그 계정이 가진 모든 저장소를 순회하면서 NPM 패키지를 업데이트하고, GitHub 저장소를 공개로 바꾸는 동작을 반복했습니다. 이 구조 때문에 한 명의 유지 관리자가 타격을 받아도, 그 뒤에는 수십, 수백 개의 조직이 줄줄이 따라 들어갑니다. 문제는 기술 난이도가 높지도 않았다는 점입니다. 이미 존재하는 도구와 워크플로를 조합해서 만든 자동화 스크립트에 가까웠습니다.
"좋은 도구"가 공격자에게도 좋은 무기가 된다는 역설
이 공격에서 흥미로운 지점은 도구 선택입니다. 트러플허그 같은 보안 점검 도구가 그대로 비밀 정보 수집용 스캐너로 재활용되었습니다. LLM 클라이언트가 설치된 환경에서는 대화형 모델을 이용해 추가 비밀 정보를 유추하려는 시도도 있었습니다. 이후 버전에서는 Bun 런타임을 설치해 의존성 실행 환경까지 통째로 가져왔습니다.
개발 생산성을 높이려고 도입한 자동화 도구가, 공격자 손에 들어가면 더 조용하고 더 깊게 파고드는 침투 도구가 되었습니다. 즉, "보안 도구를 쓰니 안전하다"가 아니라, "어떤 도구든 공격자도 똑같이 쓴다"는 전제를 출발점으로 삼아야 한다는 메시지입니다. 편리한 것은 항상 양쪽 모두에게 편리합니다.
왜 NPM과 Node는 같은 실수를 반복하는가
많은 개발자가 이쯤에서 이런 생각을 합니다. "preinstall, postinstall이 위험하다는 이야기는 예전부터 있었는데, 왜 여전히 그대로인가." 이 질문이야말로 이번 사건의 본질을 건드리는 지점입니다.
샤이‑훌루드 웜은 NPM 생태계가 오래전에 허용한 설계 결정, 특히 설치 스크립트에 대한 무차별 권한을 그대로 활용했습니다. 설치 순간에 운영체제 수준의 권한으로 코드를 실행할 수 있다는 점이 치명적인 전제입니다. 보안 업계에서는 이미 오랜 시간 동안 나쁜 패턴으로 지적해 왔지만, 생태계는 속도를 잃지 않기 위해 위험을 그대로 안고 달려온 셈입니다.
너무 큰 플랫폼, 너무 느린 변경, 그리고 그 사이의 공격자
NPM과 Node는 이미 수많은 조직과 프로젝트의 기반입니다. 이 정도 규모가 되면 구조적 변경이 사실상 정치적 결정에 가깝습니다. preinstall, postinstall을 강하게 제한하거나 샌드박스에 가두려면, 기존 패키지와 빌드 파이프라인이 대규모로 깨질 수 있습니다. 생태계 전체의 마이그레이션 비용이 상상을 초월하니, "알지만 못 건드리는 위험"이 됩니다.
공격자는 바로 이 지점을 노립니다. 패키지 유지 관리자는 성능과 자동화를 위해 설치 스크립트 사용을 당연하게 여깁니다. CI 시스템은 이 스크립트를 검증 없이 신뢰합니다. 보안팀은 수천 개의 의존성 전부를 리뷰할 수 없습니다. 결과적으로 모두가 위험을 인지하지만, 이해관계 때문에 아무도 브레이크를 세게 밟지 못하는 상태가 형성됩니다.
"내 코드"가 아니라 "내가 신뢰한 사람의 코드"가 문제다
이번 공격은 한 가지 불편한 사실을 다시 상기시킵니다. 실제로 돌고 있는 서비스의 상당 부분은 직접 작성한 코드가 아니라, 얼굴도 모르는 유지 관리자가 만든 패키지에 의해 움직입니다. 더 위험한 부분은 그 유지 관리자의 CI 설정과 토큰 관리 습관입니다. 공격자가 노린 지점은 바로 이 "신뢰의 꼬리"였습니다.
개발자는 종종 자신의 저장소만 잘 관리하면 안전하다고 생각합니다. 하지만 이미 체인은 훨씬 길게 이어져 있습니다. Nx 같은 빌드 시스템, LLM 클라이언트, Bun 런타임, 각종 도구용 패키지까지, 그 중 어느 하나라도 뚫리면 그 뒤에 매달린 프로젝트가 줄줄이 열립니다. 보안이 "내 소스코드 품질" 문제가 아니라 "신뢰 관계 설계" 문제라는 인식 전환이 필요합니다.
실무자가 지금 당장 손댈 수 있는 현실적인 방어선
이쯤에서 자연스럽게 나오는 고민이 있습니다. "NPM과 Node가 구조를 안 고치는데, 팀 단위에서 할 수 있는 게 무엇인가." 모든 위험을 없앨 수는 없지만, 피해를 제한할 방법은 분명히 존재합니다. 핵심은 도구를 바꾸기보다, 사용하는 방식의 기본 원칙을 세우는 쪽에 더 가깝습니다.
자동 실행을 정상으로 보지 않는 개발 문화 만들기
첫 번째 축은 설치 단계에서의 자동 실행을 예외 상황으로 취급하는 태도입니다. preinstall, postinstall을 사용하는 패키지는 이유를 명확히 확인하고, 대체 가능한지부터 검토하는 방식이 기본값이 되어야 합니다. 팀 내에서 "설치 시 자동으로 뭔가를 하는 패키지는 기본적으로 의심한다"는 규칙을 합의하면, 최소한 공격 표면을 좁힐 수 있습니다.
또한 내부 패키지에서도 설치 스크립트를 습관적으로 쓰지 않는 편이 좋습니다. 특정 초기화 작업이 필요하다면, 명시적인 CLI 명령이나 별도 스크립트로 분리해서 사람이 의도적으로 실행하는 절차로 전환하는 것이 안전합니다. 자동화를 포기하자는 이야기가 아니라, 권한이 큰 작업일수록 클릭 한 번 이상의 인지 과정을 거치게 만들자는 접근입니다.
비밀 정보 관리와 토큰 권한을 "설정의 문제가 아니라 구조의 문제"로 보기
두 번째 축은 자격 증명 관리입니다. 샤이‑훌루드 웜이 노린 자원은 매우 직관적입니다. SSH 키, 환경 변수, GitHub 토큰, NPM 계정 정보, 그리고 지갑 관련 데이터입니다. 문제는 이 정보들이 개발자의 개인 노트북과 CI 환경 곳곳에 중복 저장된다는 점입니다.
실무에서는 먼저 개인 개발 환경에 남아 있는 오래된 키와 토큰을 정리하는 작업만으로도 위험을 크게 줄일 수 있습니다. 사용하지 않는 토큰을 폐기하고, 필요한 토큰은 권한 범위를 최소화한 뒤, 가능하면 단일 프로젝트 단위로 분리해야 합니다. 또한 CI에서 사용하는 자격 증명은 저장소나 조직 단위로 분리하고, 토큰이 유출되더라도 전체 계정이 털리지 않도록 설계해야 합니다. 이 단계에서 이미 "귀찮음"이 강력한 방어 장벽과 그대로 연결됩니다.
이 전략이 맞지 않는 사람, 그리고 시작 전 반드시 체크할 것
많은 개발자가 마음속으로 이렇게 반문합니다. "현실적으로 이걸 다 지키면 개발 속도가 너무 떨어지지 않나." 이 우려는 충분히 타당합니다. 스타트업이나 작은 팀에서는 릴리즈 속도가 생존과 직결되기 때문입니다. 그래서 팀의 상황에 따라 우선순위를 명확히 나누지 않으면, 어느 순간 보안 수칙이 전부 "알지만 못 지키는 체크리스트"로 변합니다.
속도와 안전 사이, 각 팀이 받아들일 "최소 기준"을 먼저 정해야 한다
보안 원칙을 전부 흡수하기 어려운 팀이라면, 몇 가지는 과감히 미루고, 몇 가지는 "절대선"으로 못 박는 편이 현실적입니다. 예를 들면, 새로운 외부 패키지를 도입할 때 설치 스크립트 존재 여부를 반드시 확인하고, 가능하면 스크립트 없는 대안을 먼저 찾는 기준을 세울 수 있습니다. 또 하나의 절대선은 개인 계정 토큰을 조직 전체 빌드에 사용하지 않는 원칙입니다. 계정 하나가 뚫렸을 때 피해 범위를 자동으로 제한하는 구조입니다.
이런 최소 기준을 세운 뒤에는, 팀의 상황에 따라 단계적으로 확장하는 전략이 적절합니다. 예를 들어, 자주 사용하는 핵심 패키지 목록을 만들고, 정기적으로 해당 유지 관리자의 활동과 보안 이슈를 점검하는 주기를 두는 방식이 있습니다. 속도를 위해 포기하는 것과, 정보 부족 때문에 방치하는 것을 분리해 의식적으로 선택하는 과정 자체가 리스크 관리의 시작점입니다.
지금 바로 할 수 있는 첫 행동은 "맵 작성"이다
가장 현실적인 첫 행동은 복잡한 도구 도입이 아닙니다. 현재 프로젝트가 의존하는 핵심 패키지와, CI에서 사용하는 주요 토큰과 키를 한 번에 볼 수 있는 간단한 맵을 만드는 일입니다. 이 맵을 작성하는 순간, 그동안 막연했던 공급망이 실제 이름과 책임자로 구성된 구조로 눈앞에 나타납니다. 그 다음부터는 위험을 "추상적인 NPM 보안 이슈"가 아니라 "이 세 개 패키지와 이 두 개 토큰" 같은 수준으로 다룰 수 있습니다.
샤이‑훌루드 웜은 NPM 생태계가 불안정해서 터진 단일 사건이 아닙니다. 이미 오래전에 쌓인 편의와 신뢰의 층이 지금 어떤 방식으로 악용되는지 보여주는 사례에 가깝습니다. 사건을 단순 공포로 소비하면 남는 것은 불안뿐입니다. 대신 각 팀의 현실에 맞게, 무엇을 포기하고 무엇을 지킬지, 그리고 어디서부터 가위질을 시작할지 결정하는 계기로 삼는 편이 훨씬 생산적인 선택입니다.
출처 및 참고 :
이 노트는 요약·비평·학습 목적으로 작성되었습니다. 저작권 문의가 있으시면 에서 알려주세요.
