AI 인사이트

npm Sigstore 서명 우회 사건 — 공급망 신뢰 체계의 맹점

seunghyeonlab 2026. 5. 23. 23:03

hero

"서명이 붙어 있으면 안전하다"는 전제가 실전에서 무너졌다. 2026년 5월, 633개의 악성 npm 패키지 버전이 Sigstore 프로버넌스 검증을 그대로 통과했다. 공급망 보안을 신뢰하는 팀이라면 지금 바로 lock 파일과 CI 계정 설정을 점검해야 한다.


1. 왜 지금 이걸 봐야 하나

npm 생태계에는 수십만 개의 패키지가 매일 게시된다. 의존성 트리가 깊어질수록 "이 패키지가 진짜 안전한가"를 사람이 일일이 확인하는 건 불가능하다. 그래서 등장한 게 Sigstore 프로버넌스였다.

Sigstore는 "이 패키지가 어느 GitHub 저장소에서, 어느 CI 파이프라인을 거쳐 빌드됐는지"를 암호학적으로 서명해 기록한다. npm 레지스트리에 서명이 붙어 있으면 출처가 검증된 패키지로 간주했다. Dependabot이나 Renovate 같은 자동 업데이트 봇도 이 신호를 신뢰 기준 중 하나로 활용한다.

2026년 5월 19일, 이 전제가 깨졌다. audit-grid 계열 패키지 633개 버전이 유효한 Sigstore 서명을 달고 레지스트리에 등록됐다. 악성 패키지임이 확인됐고 npm은 해당 버전을 내렸지만, 이미 다운로드된 버전이 CI 캐시나 lock 파일에 남아 있을 가능성이 있다.

문제는 개발자 개인의 실수가 아니라 보안 체계 자체의 설계 경계에 있다. 지금 자신의 프로젝트가 안전한지 확인하지 않으면, 다음 배포에서 이 코드가 프로덕션으로 흘러들어 갈 수 있다.


2. 핵심 아이디어

Sigstore는 절차를 검증하지, 사람을 검증하지 않는다.

공격자는 서명을 위조하지 않았다. GitHub 계정을 탈취한 뒤, 그 계정 권한으로 정상적인 CI 흐름을 그대로 실행했다. Sigstore는 "이 빌드가 올바른 절차를 따랐는가"를 확인하는 시스템이고, 절차는 실제로 올바르게 실행됐다. 시스템이 속은 게 아니라, 설계 범위 밖의 시나리오를 만난 것이다.

비유하면 이렇다. 건물 출입 시스템이 "사원증 카드가 유효한가"만 확인하는데, 공격자가 진짜 사원의 카드를 훔쳐 들어왔다. 카드 리더기는 정상 동작했고, 경보도 울리지 않았다.

아래 표로 이번 공격 구조를 정리했다.

단계 공격자가 한 일 시스템 판단
1 GitHub 계정 탈취
2 탈취 계정으로 CI 파이프라인 실행 "정상 흐름"
3 npm publish 실행 "권한 있음"
4 Sigstore 서명 발급 요청 "절차 준수 → 서명 발급"
5 악성 패키지 633개 버전 레지스트리 등록 "출처 검증됨" 표시

결론은 하나다. "서명 있음"은 "계정이 안 뚫렸다"는 전제 아래서만 의미 있다. 계정 보안이 뚫리면 서명 체계 전체가 무력화된다. 토큰 관리와 MFA 강제화가 서명 검증보다 먼저 챙겨야 할 레이어인 이유가 여기에 있다.


3. 바로 따라하는 방법

lock 파일에서 audit-grid 흔적 확인

# npm
grep -r "audit-grid" package-lock.json

# yarn
grep -r "audit-grid" yarn.lock

# pnpm
grep -r "audit-grid" pnpm-lock.yaml

출력이 없으면 해당 패키지는 없는 것이다. 버전 번호가 나오면 즉시 제거하고 lock 파일을 재생성해야 한다.

# 의심 패키지 제거 후 재설치
npm uninstall audit-grid
npm install

CI 계정 MFA 상태 점검

GitHub Actions에서 npm publish 권한을 가진 계정이 MFA를 사용하는지 확인한다.

# GitHub CLI로 본인 계정 MFA 상태 확인
gh api user --jq '.two_factor_authentication'

true가 나와야 정상이다. false라면 지금 바로 GitHub 계정 설정에서 MFA를 활성화해야 한다.

npm 토큰도 점검한다. 불필요한 publish 권한 토큰이 있다면 폐기한다.

# 현재 등록된 npm 토큰 목록 확인
npm token list

# 특정 토큰 폐기
npm token revoke <token-id>

Dependabot / Renovate 신뢰 정책 재검토

Renovate를 사용하는 경우 renovate.json에서 Sigstore 서명 유무만으로 패키지를 자동 머지하는 설정이 있는지 확인한다.

// renovate.json — 자동 머지 조건을 서명 단독에서 복합 조건으로 강화
{
  "packageRules": [
    {
      "matchUpdateTypes": ["patch"],
      "automerge": true,
      "automergeType": "pr",
      "requiredStatusChecks": ["ci/tests", "security/audit"]
    }
  ]
}

Sigstore 서명 외에 실제 테스트 통과와 보안 감사 체크를 함께 요구하는 구조로 바꾸는 게 핵심이다.


4. 운영할 때 조심할 점

캐시에 박힌 악성 버전
CI 파이프라인이 node_modules를 캐싱한다면, lock 파일을 수정해도 이전 캐시가 그대로 사용될 수 있다. 의심스러운 경우 CI 캐시를 완전히 비우고 처음부터 설치해야 한다.

자동화 봇의 사각지대
Dependabot과 Renovate는 취약점 DB(OSV, NVD 등)에 등록된 패키지를 감지한다. 하지만 이번처럼 악성 패키지가 DB에 등록되기 전 짧은 시간 동안 배포된 경우, 자동화 봇은 아무것도 알아채지 못한다. 자동 머지 설정을 최소화하고 PR 리뷰 단계를 남겨두는 이유가 바로 이것이다.

npm publish 권한 분리
하나의 GitHub 계정이 코드 리뷰 권한과 npm publish 권한을 동시에 가지는 구조는 위험하다. publish용 토큰은 별도 서비스 계정으로 분리하고, 그 계정은 저장소 쓰기 권한을 주지 않는 것이 원칙이다.

Sigstore 자체는 여전히 유효한 도구다
이번 사건은 Sigstore가 나쁜 시스템이라는 뜻이 아니다. 프로버넌스 서명은 "어떤 코드가 어떤 절차로 빌드됐는지" 추적하는 데 실질적인 가치가 있다. 다만 그 가치는 계정 보안이 전제됐을 때만 유효하다. 두 레이어를 병행해야 한다.


마무리

공급망 보안에서 "서명 있음"은 출발점이지 종착점이 아니다. 이번 사건은 계정 탈취 하나로 암호학적 서명 체계 전체를 우회할 수 있다는 걸 633개 버전으로 실증했다. 오늘 당장 lock 파일에서 audit-grid를 grep하고, CI 계정 MFA 상태를 확인하고, 자동 머지 정책을 점검하는 것으로 시작하면 된다.

다음 글에서는 npm publish 권한을 서비스 계정으로 분리하고 OIDC 기반 토큰으로 교체하는 실제 GitHub Actions 설정을 다룬다.


🐦 X에서 더 빠르게: @baegseungh7061
📚 이 시리즈 더 보기: AI 인사이트
💌 새 글 알림: X 팔로우 또는 블로그 RSS 구독