공급망 공격은 대형 라이브러리 문제만이 아니다 — 오늘 바로 실천할 수 있는 방법
Source: Dev.to

개요
2026년 5월, Shai‑Hulud라는 웜이 @tanstack/react-router를 포함한 42개의 TanStack 패키지를 침해했습니다. 이 웜은 약 3시간 동안 활동했으며, 그 기간 동안 의존성을 설치한 모든 사람에게 영향을 미쳤습니다.
“재미있는 사실 1”
@tanstack/react-router만 해도 주당 1,270만 건의 다운로드를 기록합니다 → 3시간 동안 약 22만 5천 건이 다운로드된 셈이죠. 이번 공격으로 총 42개의@tanstack/*패키지가 영향을 받았습니다.
공급망 공격은 예전엔 “다른 사람의 문제”처럼 느껴졌습니다. 큰 라이브러리가 침해되고, 유지보수자가 이를 고치면 끝이었죠. Shai‑Hulud 웜은 그 인식을 바꾸었습니다. 피해자가 관리하는 모든 패키지에 자동으로 퍼져, 일반 개발자들을 원치 않는 악성코드 배포자로 만들었습니다.
무슨 일이 있었나요?
- 워크플로우가
pull_request_target을 사용했습니다 – 이 옵션은 기본 레포지토리의 신뢰된 권한(시크릿 접근, 공유 빌드 캐시 등)으로 실행됩니다. - 벤치마킹을 위해 포크된 코드를 체크아웃하고 실행했습니다. 이 위험한 조합으로 낯선 코드가 레포지토리의 신뢰를 이용해 실행되었습니다.
- 공격자는 즉시 무언가를 훔칠 필요가 없었습니다. 공유 캐시를 오염시킨 뒤, 정식 릴리스 파이프라인이 몇 시간 뒤에 실행될 때 변조된 캐시를 사용해 TanStack의 유효한 인증 정보로 악성 패키지를 배포했습니다.
핵심 인사이트 – 잘못된 설정이 눈에 띄지 않았습니다. 벤치마킹 목적은 합리적이었지만, pull_request_target + “PR 코드를 실행”이라는 조합이 안전하다고 가정한 것이 실수였습니다. 언제나 위험한 조합이죠.
“재미있는 사실 2”
웜은 ‘데드맨 스위치’를 설치했습니다: 60초마다 훔친 GitHub 토큰으로api.github.com/user를 폴링하는 백그라운드 서비스. 토큰이 폐기되면(GitHub이 40x 응답) 서비스가rm -rf ~/를 실행해 사용자의 홈 디렉터리를 삭제합니다. 자격 증명을 폐기하기 전에 모니터 서비스를 비활성화하고 제거해야 했습니다.
좋은 소식
pull_request_target 기반 공격은 공개 레포지토리에서만 가능하며, 여기서만 외부인이 PR을 열 수 있습니다.
나쁜 소식
공격의 다른 부분(워크플로우 간 캐시 오염, OIDC 토큰 과다 권한 부여)은 프라이빗 레포지토리에도 영향을 줄 수 있습니다. GitHub Actions 워크플로우에 유사한 잘못된 설정이 있다면 말이죠.
지금까지는 라이브러리 유지보수자만의 문제처럼 들릴 수 있습니다. 그렇지 않습니다. 수백만 다운로드를 가진 라이브러리를 관리하지 않아도, 잘못된 시점에 npm install만 하면 됩니다. 침해된 패키지가 node_modules에 들어오는 순간, 여러분도 체인의 일부가 됩니다.
유사 사고를 예방하려면?
npm – .npmrc
min-release-age=7
ignore-scripts=true
min-release-age=7은 7일 이내에 발행된 패키지를 차단합니다.ignore-scripts=true는 설치 시 라이프사이클 스크립트(preinstall,prepare등)의 실행을 방지합니다 — 악성optionalDependency가 이용한 정확한 경로입니다.- ⚠️ npm CLI v11이 필요합니다. Node 24로 업그레이드하거나 npm v11을 수동으로 설치하세요.
pnpm (≥ 10.16) – pnpm-workspace.yaml
minimumReleaseAge: 10080 # minutes → 7 days
minimumReleaseAge는 기본값이 1일이며, 7일로 설정하면 더 강력하게 제한합니다.- 이 설정을 무시하는 버그를 피하려면 pnpm 10.16 이상이 필요합니다.
- pnpm v10부터는 기본적으로
preinstall/postinstall스크립트를 실행하지 않으며(ignore-scripts와 동일한 효과).
Yarn Classic (v1)
- ❌
min-release-age지원되지 않음; 동등한 옵션이 없습니다. yarn install --ignore-scripts플래그는 사용할 수 있지만, 영구 설정은 불가능합니다.
Yarn Berry (v2+) – .yarnrc.yml
npmMinimalAgeGate: 10080 # minutes → 7 days
enableScripts: false # ignore‑scripts와 동등
- ⚠️ Yarn 4.10 이상이 필요합니다. 전역 적용되며 범위를 지정할 방법이 없습니다.
Dockerfile
ENV npm_config_min_release_age=3
npm ci는.npmrc설정과 환경 변수(npm_config_*)를 모두 따릅니다.- ⚠️ 여전히 Node 24 또는 npm v11이 필요합니다. 그렇지 않으면
ENV라인이 조용히 무시됩니다.
옵션 A – Docker에서 Node 24로 업그레이드
FROM node:24-alpine
# npm v11이 포함되어 있어 min‑release‑age가 동작합니다
ENV npm_config_min_release_age=3
옵션 B – 현재 Node 버전을 유지하면서 npm v11을 수동 설치
FROM node:22-alpine
RUN npm install -g npm@11
ENV npm_config_min_release_age=3
출처
- https://dev.to/luzaramburo/supply-chain-attacks-aren-t-just-a-big-library-problem-heres-what-you-can-do-today-4g5c
- GitHub Security Advisories (Shai‑Hulud 웜 분석)
- npm 문서 –
min-release-age,ignore-scripts - pnpm 문서 –
minimumReleaseAge - Yarn 2+ 문서 –
npmMinimalAgeGate,enableScripts
참고 자료
- Snyk 블로그 – TanStack npm 패키지 침해
- TanStack – npm 공급망 침해 사후 분석
- TanStack Router – 커밋 히스토리
- npm
min-release-age설정 - npm + Node 버전 관계 (npm v11이 Node 24에 포함된다는 확인)
- pnpm
minimumReleaseAge설정 - pnpm v11 릴리즈 노트 (설정이 기본 1일로 활성화됨)
- Yarn Berry
npmMinimalAgeGate(Yarn 4.10에서 도입) - Yarn Berry
enableScripts - 표지 이미지 – Unsplash
