공급망 보안 표준
모든 프로젝트에 적용됩니다. 규칙 검토: 2026-11-11 (Shai-Hulud / Mini Shai-Hulud 웜 사후).
머신 수준 기본값 (~/.npmrc)
minimum-release-age=1440 # pnpm 전용 — 분 (1440 = 24시간)
min-release-age=1 # npm 12+ 전용 — 일 (1 = 24시간)
두 패키지 관리자는 서로 다른 키 이름과 서로 다른 단위를 사용합니다.
2026-08-21 각 도구의 소스 코드를 읽어 확인: pnpm은
Date.now() - minimumReleaseAge * 60 * 1e3 (분)으로 계산합니다. npm의 min-release-age
설정 정의에는 hint: '<days>'가 포함되어 있습니다. 두 줄 모두 필요합니다. 하나가 다른 하나를
대체하지 않으며, 1440을 npm 키에 복사하면 1440 일을 의미하여
사실상 모든 설치를 차단합니다.
npm은 설치 시 Unknown user config "minimum-release-age"를 출력하고 이후 주요 버전에서
작동이 중단될 것이라고 경고합니다. 이 키가 ~/.npmrc에 존재하는 한 이 경고는 피할 수 없습니다:
pnpm 11은 글로벌 설정 쓰기를 제거했습니다(pnpm config set --global은 이제 프로젝트별
pnpm-workspace.yaml로 리디렉션됨). 따라서 ~/.npmrc가 글로벌 pnpm 게이트가
존재할 수 있는 유일한 장소입니다. 경고를 수락하고 키를 삭제하지 마십시오.
다음에 의존하지 마십시오 — 검증 결과 무효
-
block-exotic-subdependencies— npm이나 pnpm의 소스 코드에 나타나지 않습니다. 이 키는 아무런 기능도 한 적이 없습니다. 외부 종속성은 이 키가 아니라 검토를 통해 잡아야 합니다. -
ignore-scripts=true— 이 스킬은 이전에 이 키가 글로벌로 설정되어 있다고 주장했습니다. 설정되어 있지 않으며,npm config get ignore-scripts는false를 반환하므로 수명 주기 스크립트는 설치 시 실행됩니다.2026-08-21 결정: 그대로 둡니다. 활성화하지 마십시오. Postinstall은 주요 npm 손상 벡터이므로, 이는 알려져 있고 수용된 격차(fix 해야 할 간과가 아님)입니다. 글로벌로 활성화하면 모든 프로젝트에서 네이티브 빌드(esbuild, swc, sharp, node-gyp)가 중단되고 중단 현상은 원인과 먼 곳에서 나타납니다. 위의 24시간 릴리스 에이지 게이트가 대신 이 위험을 감당합니다. 특정 프로젝트에 스크립트 격리가 필요한 경우, 해당 프로젝트에만 범위를 지정하십시오 — 절대 글로벌로 설정하지 마십시오.
이러한 주장을 이 파일이 아닌 도구에 대해 확인하십시오 — 감사 시 여기에 있는 세 항목 모두 잘못된 것으로 판명되었습니다.
공급망 공격 분류 체계 (감사 전 참고)
1. 타이포스쿼팅 / 종속성 혼동 (Typosquatting / Dependency Confusion)
패키지 이름이 인기 패키지와 거의 동일하거나, 내부 스코프 이름과 일치하는 공개 패키지.
2025년에만 454,648개의 악성 npm 패키지가 게시되었습니다.
탐지: npm info <pkg> | grep -E 'created|repository' (익숙하지 않은 패키지에 대해). 내부 레지스트리 이름과 교차 참조.
2. 유지보수자 계정 탈취 (Maintainer Account Takeover)
공격자가 유지보수자를 피싱하여 post-install payload가 포함된 새 버전을 게시합니다.
2025년 9월 Shai-Hulud는 chalk, debug, ansi-styles(주간 다운로드 26억 회)를 공격했습니다.
Axios(주간 1억 회 이상)는 2026년 3월 DPRK 사회 공학을 통해 공격받았습니다.
탐지: npm diff <pkg>@<last-known-good> <pkg>@latest -- package.json scripts — 패치 버전에 추가된 새 수명 주기 스크립트 확인.
3. 저장소 탈취 / 네임스페이스 점유 (Repo Jacking / Namespace Squatting)
공격자가 버려진 GitHub 사용자 이름이나 npm 네임스페이스를 등록하고, 저장소를 재생성하여 악성 코드를 제공합니다.
탐지: package.json의 repository 필드 확인; GitHub 계정 생성일이 패키지의 첫 npm 게시일보다 나중인지 플래그.
4. CI/CD 파이프라인 주입 (pull_request_target / 캐시 중독)
공격자가 권한 있는 게시 작업과 캐시를 공유하는 pull_request_target이 있는 저장소에 PR을 엽니다.
pnpm 저장소를 중독시킵니다. 다음 릴리스 워크플로우에서 npm OIDC 토큰이 유출됩니다.
TanStack 벡터 (2025년 11월, 2026년 5월 Mini Shai-Hulud, 300개 이상 패키지, 주간 1600만 다운로드).
탐지:
grep -r "pull_request_target" .github/workflows/
grep -r "cache" .github/workflows/ | grep -i "pnpm\|npm\|store"
하드 규칙: 워크플로우가 PR 코드를 체크아웃하거나 권한 있는 작업과 캐시를 공유하는 경우 pull_request_target을 절대 사용하지 마십시오.
5. Docker 이미지 중독 (Docker Image Poisoning)
공격자가 Docker Hub 자격 증명을 손상시키고 기존 이미지 태그(latest 포함)를 악성 바이너리로 덮어씁니다.
Trivy aquasec/trivy 태그 0.69.4–0.69.6 및 latest가 2026년 3월에 중독되었습니다.
KICS 2026년 4월 — 5개 태그가 덮어쓰기됨; 비밀번호가 포함된 Terraform/K8s 구성을 스캔합니다.
탐지: docker inspect <image> --format '{{.Id}}' vs. 구성에 고정된 다이제스트.
하드 규칙: 모든 이미지를 @sha256: 다이제스트로 고정하십시오. 보안 도구 이미지에 latest를 절대 사용하지 마십시오.
6. GitHub Actions 태그 재지정 (GitHub Actions Tag Re-pointing)
공격자가 액션 유지보수자의 토큰을 손상시키고, 기존 버전 태그를 악성 코드를 가리키도록 이동시킵니다. tj-actions/changed-files CVE-2025-30066 (2025년 3월, 23,000개 이상 저장소) 모든 CI 비밀번호를 로그에 덤프했습니다. 2026년 5월: 인기 액션 태그가 사칭 커밋으로 리디렉션되었습니다. 탐지:
grep -r "uses:.*@v[0-9]" .github/workflows/ # 태그 고정 플래그 (나쁨)
grep -r "uses:.*@[a-f0-9]\{40\}" .github/workflows/ # SHA 고정 (올바름)
하드 규칙: 모든 서드파티 액션을 버전 태그가 아닌 전체 40자 커밋 SHA로 고정하십시오.
actions/* (퍼스트파티)는 주요 버전 태그를 사용할 수 있습니다.
7. 잠금 파일 변조 / 무결성 우회 (PackageGate급)
공격자가 잠금 파일을 수정하여 악성 패키지 버전을 대체하거나 무결성 해시를 제거하여 다음 설치가 자동으로 다른 tarball을 가져오도록 합니다. 2026년 1월 6개의 제로데이 잠금 파일 취약점이 문서화되었습니다 (Koi Security). 탐지:
grep -E '"resolved".*git\+|tarball' package-lock.json pnpm-lock.yaml
grep -v "integrity" pnpm-lock.yaml | grep "resolved"
하드 규칙: CI는 pnpm install --frozen-lockfile 또는 npm ci를 사용해야 합니다 — 절대 npm install을 사용하지 마십시오.
8. 하위 종속성 섀도잉 (Subdependency Shadowing)
공격자가 3+ 단계 깊이의 전이 종속성을 대상으로 합니다. @ctrl/tinycolor는 Shai-Hulud 전파 노드였습니다.
탐지: npm ls --all 2>/dev/null | grep -E "postinstall|preinstall". PR에 socket scan을 사용하십시오.
9. 사회 공학 유지보수자 인계 (Social Engineering Maintainer Handoff)
공격자가 채용 담당자나 비즈니스 파트너로 위장하여 npm 게시 권한을 얻습니다.
UNC1069 (DPRK)는 2026년 3월 Axios 유지보수자를 대상으로 했으며, Socket CEO, Lodash 창시자, Fastify 리더에게도 시도했습니다.
탐지: npm owner ls <pkg> (중요한 종속성에 대해); 의심스러운 릴리스 전에 새 협업자를 모니터링하십시오.
10. 항의성 멀웨어 (Protestware - 의도적 유지보수자 사보타주)
유지보수자가 의도적으로 파괴적인 코드를 주입합니다.
colors.js v1.4.44–1.4.2 (2022년 1월, 무한 루프), node-ipc v10.1.1–10.1.3 (2022년 3월, 러시아 IP에서 파일 삭제).
2026년 5월 3개의 새로운 악성 node-ipc 버전.
탐지:
npm diff <pkg>@<prev> <pkg>@<new> -- lib/
grep -r "networkInterfaces\|geoip\|fs.rmdir\|fs.unlink" node_modules/<pkg>/
node-ipc, colors, faker를 알려진 양호한 정확한 버전으로 고정하십시오.
11. 웜형 자가 복제 멀웨어 (Wormable Self-Replicating Malware)
Payload가 npm 토큰/PAT를 훔쳐 피해자가 유지보수하는 다른 패키지의 악성 버전을 자율적으로 게시합니다. 2025년 9월 Shai-Hulud에서 처음 관찰됨; 2026년 5월 Mini Shai-Hulud (300개 이상 패키지, 주간 1600만 다운로드, npm + PyPI 동시 공격). IOC 탐지:
find .github/workflows -name "*.js" # workflows의 bundle.js
grep -ri "shai-hulud" . # 웜 문자열
find node_modules -name "data.json" -newer package.json # 유출 준비
find node_modules -name "cloud.json"
12. 휴면 / 환경 조건부 Payload (Dormant / Environment-Conditional Payloads)
AWS_SECRET_ACCESS_KEY, GITHUB_TOKEN, 또는 CI=true가 있을 때만 payload가 활성화됩니다 — 샌드박스 스캐너를 회피합니다.
탐지:
grep -r "process.env.AWS_SECRET\|process.env.GITHUB_TOKEN\|process.env.NPM_TOKEN" node_modules/
이러한 변수를 읽는 패키지 중 정당한 이유가 없는 패키지를 플래그하십시오.
13. 보안 도구를 통한 공격 벡터 (Security Tool as Attack Vector)
공격자는 CI에서 비밀과 인프라 설정에 대한 높은 접근 권한으로 실행되기 때문에 보안 스캐닝 도구(Trivy, KICS, Semgrep)를 특히 표적으로 삼습니다. Trivy와 KICS는 모두 2026년 3~4월에 공격을 받았습니다. 하드 규칙: 모든 보안 도구 Docker 이미지를 태그가 아닌 다이제스트로 고정하십시오. 공급업체 릴리스 노트를 기준으로 다이제스트를 대역외에서 확인하십시오.
14. 비레지스트리 / CDN 종속성 (Non-Registry / CDN Dependencies)
Git URL, GitHub tarball 또는 사용자 지정 CDN(예: cdn.sheetjs.com)에서 확인된 패키지는
레지스트리 보안 게이트와 출처 증명을 우회하며, 릴리스 에이지 게이트의 적용도 받지 않습니다 —
릴리스 에이지 게이트는 레지스트리 버전에만 적용되기 때문입니다. 일부는 무결성 해시가 완전히 누락되어 있습니다.
이것들을 자동으로 차단하는 것은 없습니다. 검토를 통해 잡아야 합니다.
탐지:
grep -E '"resolved".*github\.com|cdn\.' package-lock.json pnpm-lock.yaml
grep -E '"version":\s*"(git\+|https?:)' package.json
프로젝트별 npm/pnpm 위생
- 잠금 파일을 커밋하십시오. CI는
pnpm install --frozen-lockfile(또는npm ci)을 사용해야 합니다. CI에서 절대npm install을 사용하지 마십시오. package.json에packageManager를 고정하여 pnpm/npm 버전을 잠급니다.- 손상된 네임스페이스의 패키지를 정확한 버전으로 고정하십시오:
@tanstack/*,posthog-node,@mistralai/*,@langchain/mistralai,@ctrl/*,ngx-*,rxnt-*,@zapier/*,@postman/*,@uipath/*,agent-mcp,@semgrep/*,node-ipc,colors,faker. package.json에 와일드카드"*"버전을 사용하지 마십시오.pkg.pr.newURL을 사용하지 마십시오 — 이는 일시적인 PR 미리보기 빌드이지, 변경 불가능한 아티팩트가 아닙니다.- npm/yarn 프로젝트의 경우, 로컬
.npmrc에 동일한 npmrc 게이트를 미러링하십시오. - 새 프로젝트에는 pnpm을 선호하십시오. 보안 때문에 작동 중인 프로젝트를 마이그레이션하지 마십시오.
GitHub Actions 강화
하드 규칙
- 워크플로우가 PR 코드를 체크아웃하거나, PR 코드에서 install/build/test를 실행하거나, 권한 있는 워크플로우와 캐시를 공유하는 경우 절대
pull_request_target을 사용하지 마십시오. pull_request_target는 PR 메타데이터(레이블, 댓글, 제목)를 체크아웃 없이 읽는 경우에만 허용됩니다.- PR 트리거 작업과 게시/릴리스 작업 간에 캐시를 공유하지 마십시오.
- CI에서 변경 가능한 브랜치(
@main,@master)에 대해curl | bash또는wget | sh를 절대 사용하지 마십시오. 항상 설치 스크립트를 특정 릴리스로 고정하고 체크섬을 확인하십시오.
workflow_run 가드
workflow_run을 사용하여 권한 있는 작업을 부모 워크플로우 이후에 연결할 때,
if:는 다음 모두를 명시해야 합니다:
if: >
github.event_name == 'workflow_dispatch' ||
(github.event.workflow_run.conclusion == 'success' &&
github.event.workflow_run.head_branch == 'main' &&
github.event.workflow_run.head_repository.full_name == github.repository &&
github.event.workflow_run.event == 'push')
액션 고정
- 모든 서드파티 액션을
@v1/@v6가 아닌, 전체 40자 커밋 SHA로 고정하십시오. - 감사 가능성을 위해 사람이 읽을 수 있는 태그를 주석으로 추가하십시오:
docker/login-action@65d0a9e # v3 - 퍼스트파티
actions/*는 주요 버전 태그를 사용할 수 있습니다. - 현재 SHA 얻기:
gh api repos/<owner>/<action>/git/ref/tags/<tag> - 자동화:
npx pin-github-action .github/workflows/*.yml - 작업별로
permissions:를 최소화하십시오. 기본값은contents: read입니다. 게시 단계에만id-token: write를 추가하십시오.
npm 게시 워크플로우
- npm 계정에 2FA를 요구하십시오:
npm profile enable-2fa auth-and-writes. - 장기 사용
NPM_TOKEN비밀 대신 신뢰할 수 있는 게시(OIDC)를 사용하십시오. --provenance(또는provenance: true)를 추가하여 소비자가 출처를 확인할 수 있도록 하십시오.main브랜치에 태그 푸시로 트리거되는release.yml에서만 게시하십시오. PR 트리거 워크플로우에서는 절대 게시하지 마십시오.
Docker / 컨테이너 위생
- 모든
FROM을 다이제스트로 고정하십시오:FROM node:22-slim@sha256:<hash> - 프로덕션 Dockerfile 또는 CI에서
latest또는 태그 없는 이미지를 절대 사용하지 마십시오. - 보안 도구 이미지(Trivy, KICS, Semgrep, Snyk, Grype, Hadolint)가 가장 위험합니다 — 이것들을 먼저 고정하십시오.
- 현재 다이제스트 얻기:
docker buildx imagetools inspect node:22-slim --format '{{json .Manifest.Digest}}' - 프라이빗 레지스트리 이미지의 경우: 변경 불가능한 태그 + 다이제스트로 게시하십시오.
개발자 도구 및 설정 파일
~/.cursor/mcp.json,~/.claude/settings.json또는 유사한 설정 파일에 API 토큰이나 비밀번호를 평문으로 저장하지 마십시오. 환경 변수 또는 키체인을 사용하십시오.- MCP 서버 패키지를 정확한 버전으로 고정하십시오(
[email protected],mcp-remote@latest아님). - 공급망 사고 후에
~/.vscode/tasks.json및~/.cursor/에서 예상치 못한 자동 실행 훅이 있는지 확인하십시오.
탐지 / 모니터링
- 워크플로우가 있는 모든 저장소의 CI에 zizmor 또는
snyk-labs/github-actions-scanner를 설치하십시오. - 패키지를 게시하는 모든 저장소(게시 가능한 모든 라이브러리)에 socket.dev GitHub App을 추가하십시오.
- npm 보안 권고 RSS 및 PyPI 멀웨어 피드를 구독하십시오.
IOC 빠른 스캔 체크리스트
# Shai-Hulud / 웜 IOC
grep -ri "shai-hulud" .
find .github/workflows -name "*.js"
find node_modules -name "cloud.json"
# 손상된 워크플로우 패턴
grep -r "pull_request_target" .github/workflows/
grep -r "curl.*|.*sh\|wget.*|.*sh\|base64.*eval" .github/workflows/
# 고정되지 않은 액션
grep -r "uses:.*@v[0-9]" .github/workflows/
# 잠금 파일 외부 종속성
grep -E '"resolved".*git\+|cdn\.' package-lock.json pnpm-lock.yaml
grep -v integrity pnpm-lock.yaml | grep resolved
# Docker
grep -r "FROM.*:latest\|FROM.*[^@]$" Dockerfile* docker-compose*.yml