Medium
최소 릴리스 기간은 저평가된 공급망 방어 수단입니다
패키지 매니저 설정 한 줄(minimumReleaseAge)로 공급망 공격의 절반 이상을 막을 수 있다. 핵심은 '먼저 들어가지 말고 남들을 먼저 보내라' 라는 시간 격리 전략이다.
핵심 요약
axios 침해처럼 메인테이너 토큰 탈취 → 악성 버전 배포 → 4시간 내 제거 패턴은 레지스트리 공격의 전형이다. minimumReleaseAge 는 패키지 매니저가 '배포된 지 N일이 안 된 버전은 후보에서 제외' 하도록 만드는 설정으로, semver 범위(^, ~)로 최신을 풀어내는 일반 워크플로에서 자연스럽게 끼어든다.
주요 매니저별 설정은 같은 개념이지만 이름·단위가 제각각이다.
| 도구 | 키 | 단위 | 예시(7일) |
|---|---|---|---|
| Bun | minimumReleaseAge | 초 | 604800 |
| pnpm (v10.16+) | minimumReleaseAge | 분 | 10080 |
| npm (v11.10+) | min-release-age | 일 | 7 |
| Yarn 4 (v4.10+) | npmMinimalAgeGate | duration string | "7d" |
| Renovate | minimumReleaseAge | duration | "7 days" |
| Dependabot | cooldown.default-days | 일 | 7 |
보안 업데이트는 우회가 가능해야 한다(Renovate/Dependabot 모두 CVE 패치는 cooldown 무시). 그리고 이 설정은 잠금 파일/--ignore-scripts/SHA-pinned Actions/Socket.dev 같은 동작 분석을 대체하지 않는다.
공급망 보안을 '스캐너로 잡는다' 가 아니라 '시간으로 거른다' 로 본다. 악성 패키지의 평균 노출 시간은 수 시간 단위(axios 4h, Solana 5h) 에 불과하므로, 새 버전을 N일 동안만 격리해두면 커뮤니티가 대신 검증해 준다. minimumReleaseAge 는 추가 도구가 아니라 install 시점의 해상도(resolution)에 끼어드는 지연 게이트다.
프론트엔드/Node 진영에서 공급망 공격은 더 이상 가설이 아니다. 2026년 3월 axios 침해, ua-parser-js, Solana web3.js, Ledger Connect Kit — 모두 4~5시간 안에 치고 빠지는 공격이었다. 면접에서도 이제는 다음 세 가지를 분리해 답할 수 있어야 한다.
- 차단 가능한 공격: 단기 악성 배포 —
minimumReleaseAge7일이면 11/21 차단 - 차단 불가능한 공격: 장기 침투(XZ Utils), 메인테이너 파괴(colors.js), 빌드 시스템 침해(SolarWinds)
- 다른 레이어가 필요한 것: 잠금 파일,
--ignore-scripts, SHA-pinned Actions, SBOM
즉 심층 방어(defense-in-depth) 의 한 레이어로 위치를 잡을 수 있어야 한다.
학습 포인트
면접 답변으로 연결할 학습 포인트입니다.
지연(delay)이 곧 방어가 되는 통계
21건의 주요 공급망 침해를 분석한 결과 악성 버전이 레지스트리에 떠 있던 시간이 거의 항상 수 시간이었다.
- axios: 4시간
- Solana web3.js: 5시간
- ua-parser-js: 4시간
- Ledger Connect Kit: 5시간
이 패턴이 깨지지 않는 이유는 공격자도 발각 전에 끝내야 하기 때문이다. 7일 격리는 "커뮤니티의 평균 검출 시간 < 우리의 설치 시점" 을 강제로 보장한다.
'몇 시간만 늦게 받자'로 충분하다고 생각하는 것. 실제 검출까지 평균 시간을 분석하면 7일 가 21건 중 11건을 막는 손익 분기점이다.
`install` 해상도에 끼어드는 게이트
이 설정은 새 버전을 후보에서 제외하는 방식이라 semver 범위로 의존성을 푸는 일반 워크플로에서 자동 작동한다.
# package.json: "axios": "^1.7.0"
# 어제 1.7.9 (악성) 가 배포되었다고 가정
npm install
# minimumReleaseAge=7 → 1.7.9 는 '나이 부족' 이라 후보 제외
# 7일 이상 된 1.7.8 로 해결됨
반면 잠금 파일을 완전히 고정해 npm ci 만 쓰면 효과가 줄어든다(다음 업데이트 시점까지 보호 무의미). 업데이트 빈도가 높을수록 가성비가 좋다.
잠금 파일로 완전 고정하면 이미 안전하다 고 착각하는 것. 잠금은 이전에 본 버전 만 보호하고, 업그레이드 순간에는 동일한 위험이 다시 생긴다.
막을 수 없는 공격의 분류
21건 중 8건은 7일 게이트로도 못 막는다. 면접에서 이 한계까지 같이 말해야 깊이가 산다.
- 장기 침투: XZ Utils(2년), event-stream(2개월) — 인내심 있는 공격
- 메인테이너 파괴: colors.js, node-ipc — 신뢰받는 당사자가 직접 망가뜨림
- 빌드 시스템 침해: SolarWinds, 3CX — 레지스트리 밖에서 주입
- CDN/인프라: Polyfill.io 도메인 탈취, Codecov bash 스크립트
- 정상 코드의 취약점: Log4Shell — 악성 배포가 아니라 버그
각 범주마다 짝이 되는 다른 레이어(Socket.dev 행위 분석, SHA-pinned Actions, SBOM, 재현 가능한 빌드)가 따로 필요하다.
minimumReleaseAge 만 켜면 공급망 보안이 끝났다고 보는 것. 이건 하나의 레이어일 뿐 SCA/잠금/CI 격리를 대체하지 않는다.
읽는 순서
- 1이론
원문에 인용된 21건 중 5건(axios, ua-parser-js, Solana web3.js, Ledger Connect Kit, XZ Utils)의 침해 타임라인을 표로 정리하세요. 노출 시간 과 공격 유형 두 컬럼만으로 7일 게이트로 잡을 수 있는 것/없는 것을 구분합니다.
- 2구현
팀 레포 하나를 골라
pnpm또는npm의minimumReleaseAge(혹은min-release-age) 를 7로 설정하고, 의도적으로 어제 배포된 패키지를 추가해 설치 거부 로그를 캡처하세요.--ignore-min-release-age같은 우회 플래그도 같이 검증합니다. - 3실무
Renovate(
renovate.json) 또는 Dependabot(.github/dependabot.yml)에 cooldown 을 추가하되 보안 업데이트는 0일 로 우회시키세요. PR 자동화 흐름에서 cooldown 이 어떻게 적용되는지 실제 PR 한두 개로 확인합니다. - 4설명
팀 위클리에 10분짜리 발표를 합니다 — '왜 우리가 7일 게이트를 켰는가, 그리고 무엇이 여전히 우리를 못 막는가'. 막을 수 없는 5가지 카테고리와, 그것에 대응하는 다른 레이어(잠금/
--ignore-scripts/SHA-pinned/SBOM)를 같이 보여줍니다.
면접 연결 질문
[감점 답변] '대부분의 공격을 막을 수 있다'. [좋은 답변] 막을 수 있다: 2026년 axios — 메인테이너 토큰이 탈취되어 4시간 노출, 7일 게이트면 후보에서 제외됨. 막을 수 없다: XZ Utils — 메인테이너 권한을 2년에 걸쳐 빼앗은 후 정식 배포한 케이스. 시간 격리로는 정상 메인테이너의 정상 릴리스를 구별할 수 없다. 즉 단기 hit-and-run vs 장기 인사이드 위협 으로 갈린다.
[감점 답변] '필요하면 끄면 된다'. [좋은 답변] 세 축을 분리한다.
- CVE 핫픽스: Renovate/Dependabot 의 보안 업데이트는 cooldown 우회 — 이미 기본 동작
- 내부 패키지:
@org/*스코프는 allowlist 로 제외 — 자체 빌드된 패키지에는 의미 없음 - 긴급 비즈니스 요구: PR 단위로
--ignore-min-release-age같은 명시적 플래그 사용 + 코드 리뷰 의무화
핵심은 기본은 안전, 우회는 문서화된 명시 동작 으로 만드는 것.
[감점 답변] '잠금 파일이 있으니 의미 없다'. [좋은 답변] npm ci 시점은 보호 무의미(이미 잠긴 버전 사용)지만, npm install / npm update / Renovate PR 시점에 끼어들어 작동한다. 따라서 의존성을 영원히 동결할 게 아니라면 의미가 있다. 오히려 잠금만 믿고 업데이트 시점 검증을 안 하는 팀이 가장 위험하다 — 업그레이드하는 순간 0일 패키지가 그대로 들어오기 때문이다.
자기 점검
패키지가 다운로드된 후 검사한다고 오해하는 것. 실제로는 레지스트리 메타데이터의 publishedAt 을 보고 후보 목록에서 빼버린다.
이름이 같으면 단위도 같다고 가정하는 것. Bun 은 초, pnpm 은 분 으로 같은 키지만 단위가 다르다.
'시간 격리 = 공급망 보안 완성' 이라는 착각. 시간 격리는 여러 레이어 중 하나다.