
오픈소스 라이브러리를 모두 직접 만들 수는 없다. 그렇다고 업데이트를 오래 멈추면 알려진 취약점과 호환성 문제가 쌓인다. 공급망 보안의 현실적인 목표는 외부 코드를 전혀 쓰지 않는 것이 아니라, 새로 들어온 코드가 검증 없이 강한 권한을 얻는 경로를 줄이는 것이다.
그 출발점으로 사용할 수 있는 방법이 새 버전을 공개 직후 설치하지 않고 일정 시간 기다리는 minimum release age, 이른바 dependency cooldown이다. 예를 들어 일반 버전 업데이트에는 7일의 대기 시간을 두고, 긴급 보안 업데이트는 별도의 승인 경로로 처리할 수 있다.
직접 설치한 패키지만 보면 안 된다
애플리케이션이 직접 선택한 패키지는 수십 개여도 lockfile에는 수백 또는 수천 개의 패키지가 기록될 수 있다. 상위 라이브러리 하나가 작은 helper를 새로 추가하면, 개발자가 선택하지 않은 코드도 다음 설치부터 빌드 환경에서 실행될 수 있다.
application
└─ framework
└─ parser
└─ new-helper
따라서 공급망 공격면은 package.json의 직접 의존성 목록보다 실제로 해석된 dependency graph 전체에 가깝다. 시간 지연도 직접 의존성에만 적용하면 부족하다. 새 lockfile에 등장하거나 버전이 바뀐 전이 의존성까지 같은 기준으로 확인해야 한다.
pnpm의 minimumReleaseAge도 이 원칙에 맞게 직접·전이 의존성 전체에 적용된다. 공개 시각 정보가 없을 때 설치를 실패시키거나, 이미 만들어진 lockfile을 다시 검증하는 정책도 함께 설정할 수 있다.
왜 하필 7일인가
7일은 패키지가 안전하다는 보증 기간이 아니다. 공개 직후 아직 취약점 데이터베이스, 커뮤니티 신고와 registry 조치가 따라오지 못한 탐지 지연 구간을 피하기 위한 운영 값이다.
GitHub Dependabot도 일반 version update에 기본 3일 cooldown을 적용하며 기간을 조정할 수 있다. pnpm 공식 문서 역시 악성 릴리스가 공개 후 비교적 빠르게 발견·제거되는 경우를 고려해 새 버전 설치를 늦추는 기능을 제공한다.
3일, 7일, 14일 중 하나가 절대적인 정답은 아니다. 업데이트 빈도, 장애 대응 속도와 위험 선호도에 따라 정해야 한다. 다만 7일은 버전을 장기간 동결하지 않으면서 공개 직후의 불확실성을 피하려는 팀이 시작하기 좋은 보수적인 기준이다.
시간 지연은 lockfile과 함께 작동해야 한다
업데이트 봇이 7일 기다렸다가 PR을 만드는 것만으로는 충분하지 않다. 사람이 직접 lockfile을 바꾸거나 다른 설정으로 생성한 파일을 제출하면 봇의 대기 시간을 우회할 수 있기 때문이다.
정책은 다음 두 곳에서 함께 작동해야 한다.
업데이트 후보 생성
→ 공개 후 7일이 지났는지 확인
→ lockfile 변경분과 전체 graph 검사
→ 필수 CI 통과
→ 검증된 lockfile 그대로 설치
CI에서는 npm install처럼 dependency graph를 다시 해석하는 명령보다 npm ci처럼 기존 lockfile과 manifest가 다르면 실패하고 lockfile을 수정하지 않는 설치 방식을 사용한다. 중요한 것은 “최신을 설치한다”가 아니라 “리뷰한 graph를 재현한다”는 계약이다.
설치 스크립트는 별도의 실행 경계다
패키지는 애플리케이션이 실행될 때만 코드를 수행하는 것이 아니다. preinstall, install, postinstall 같은 lifecycle script는 의존성을 설치하는 동안 실행될 수 있다. 이 시점의 CI가 repository write token, cloud credential 또는 배포 권한을 가지고 있다면 악성 패키지 하나의 영향이 크게 확장된다.
필요하지 않은 설치 스크립트는 기본적으로 실행하지 않고, native module처럼 꼭 필요한 패키지만 이름과 버전을 좁혀 허용하는 편이 안전하다. npm은 ignore-scripts와 install script allowlist를 제공하고, pnpm도 허용된 build script를 제한할 수 있다.
하지만 스크립트 차단만으로 모든 실행을 막을 수는 없다. 테스트나 번들러 plugin처럼 정상 빌드 단계에서 불러오는 코드도 있기 때문이다. 그래서 의존성 설치와 build 단계에는 운영 비밀과 배포 권한을 주지 않는 구조가 더 중요하다.
Dependency · Build
- 외부 package 접근 가능
- 운영 secret 없음
- 배포 권한 없음
Artifact 검증
- digest와 검사 결과 고정
Deploy
- package 설치 없음
- 필요한 환경에만 최소 권한
공급망 공격을 완전히 막지 못해도, 공격 코드가 실행된 위치에서 훔쳐 갈 자격증명과 변경할 운영 자원을 줄이면 피해 범위를 크게 제한할 수 있다.
새 패키지와 새 버전은 다르게 본다
기존에 사용하던 패키지의 patch update와 dependency graph에 처음 등장한 패키지는 같은 위험이 아니다. 새 패키지는 7일이 지났다는 사실만으로 신뢰할 수 없다.
처음 등장한 이름은 repository와 배포 출처가 일치하는지, maintainer와 ownership이 최근 바뀌지 않았는지, install script와 비표준 tarball URL이 있는지, 어느 상위 패키지를 통해 들어왔는지를 별도로 검토해야 한다. 오래 유지되다가 나중에 악성화되는 패키지나 typosquatting은 시간 지연만으로 해결되지 않는다.
반대로 이미 악용 중인 심각한 취약점을 고치는 security patch는 7일을 기다리는 쪽이 더 위험할 수 있다. 이 경우 예외는 패키지 전체가 아니라 정확한 버전, 승인 사유와 만료 시점을 가진 break-glass로 제한하고, 이후 일반 정책으로 되돌아가야 한다.
스캐너와 provenance도 단독 해답은 아니다
CVE와 malware scanner는 알려진 문제를 찾는 데 필요하지만, 아직 신고되지 않은 최초 공격에는 정보가 없다. Provenance와 signature는 산출물이 어디에서 어떤 과정으로 만들어졌는지 확인하는 데 도움을 주지만 코드가 선하다는 사실까지 보장하지는 않는다.
그래서 한 가지 도구보다 여러 얇은 방어를 겹치는 편이 현실적이다.
| 통제 | 줄이려는 위험 |
|---|---|
| 3~7일 release cooldown | 공개 직후 탐지 전 버전의 즉시 유입 |
| 전체 lockfile graph 검사 | 전이 의존성 변경과 수동 우회 |
| frozen install과 integrity | 리뷰한 dependency graph의 변형 |
| install script 제한 | 설치 시점의 임의 코드 실행 |
| 비밀 없는 build | 자격증명 탈취와 권한 확대 |
| 별도 deploy 권한 | build 단계에서 운영 변경 |
| 취약점·출처 검사 | 알려진 위험과 비정상 source |
NIST SSDF가 강조하는 것도 특정 스캐너 하나가 아니라 secure development practice를 SDLC 전체에 통합하는 접근이다. 공급망 보안은 패키지를 한 번 검사하는 행위보다 dependency 변경, build와 deploy의 책임을 분리한 운영 체계에 가깝다.
작게 시작한다면
처음부터 거대한 사내 registry와 승인 시스템을 만들 필요는 없다. 패키지 매니저의 release age 설정, lockfile 고정 설치, install script 제한과 CI 최소 권한부터 적용할 수 있다. 그다음 새 패키지 탐지, dependency path 출력, provenance, 내부 registry 승격과 SBOM을 단계적으로 추가하면 된다.
7일 지연의 핵심은 시간을 버는 것이다. 그 시간 동안 자동화된 검사와 커뮤니티의 탐지가 작동하고, 변경은 리뷰 가능한 lockfile에 머문다. 시간만 믿는 것이 아니라, 시간이 생겼을 때 무엇을 검증하고 어떤 권한을 차단할지 함께 설계해야 한다.