들어가며
공급망 시리즈에서 cooldown은 “새로 게시된 악성 버전”을 막는 시간 기반 게이트라고 했습니다. 한편 yarn npm audit은 이미 의존 트리에 들어와 있는 알려진 CVE를 잡습니다.
그 사이에 있는 마지막 빈틈이 하나 더 있습니다 — 개발자가 PR로 새 의존성을 추가하는 순간입니다. 이 글에서는 GitHub의 dependency-review-action으로 이 단계를 자동 검증하는 방법을 다룹니다.
어떤 위험이 있는가
PR로 새 의존성을 추가할 때 일어날 수 있는 일은 다음과 같습니다.
1. 알려진 CVE가 있는 패키지를 들이는 경우
yarn add some-cool-lib를 실행했을 때, 그 라이브러리(또는 그것의 간접 의존성)에 이미 high 등급 CVE가 등록되어 있는 경우가 있습니다. 리뷰어가 CVE 데이터베이스를 매번 직접 조회하지 않으면 그대로 머지됩니다.
2. 타이포스쿼팅 패키지를 잘못 추가
yarn add react-doom 같은 오타. 또는 의도적으로 인기 패키지와 비슷한 이름의 가짜 패키지가 npm에 올라와 있어, 무심코 그것을 설치하게 되는 경우입니다.
3. 의존성 컨퓨전을 통한 내부 패키지 사칭
내부 사설 패키지와 같은 이름의 가짜 패키지가 공개 레지스트리에 올라와, 빌드 시스템이 잘못 가져오게 되는 경우입니다. Alex Birsan의 2021년 연구에서 Microsoft·Apple·Uber 등 35개 이상 기업에서 코드 실행이 확인된 공격 방식입니다.
4. 라이선스 위반
GPL/AGPL 같은 강한 카피레프트 라이선스의 패키지를 상용 SaaS에 무심코 도입하면, 회사 차원의 법적 리스크가 됩니다. 이 검사도 사람이 매번 하긴 어렵습니다.
5. 메인테이너 변경
같은 패키지라도 메인테이너가 바뀐 뒤 새 버전이 나오는 경우, 신뢰 기반이 달라집니다. 2018년의 event-stream 사례처럼 새 메인테이너가 악성 코드를 의도적으로 삽입한 전례가 있습니다.
cooldown은 이 중 일부만 막을 수 있고(악성 신규 게시), 나머지는 PR 단계에서의 자동 검증이 필요합니다.
실제 사례: 타이포스쿼팅과 의도하지 않은 위험
crossenv vs cross-env (2017)
가장 자주 인용되는 타이포스쿼팅 사례입니다. 정상 패키지 cross-env 대신 한 글자 다른 crossenv를 설치하면 환경 변수와 시스템 정보를 외부로 유출하는 악성 코드가 실행되었습니다. (공식 분석)
이 종류의 패키지는 게시 후 비교적 빨리 npm에서 삭제되지만, 그 사이에 누군가의 PR로 한 번 들어가면 끝입니다. Dependency Review Action을 PR 게이트에 걸어 두면 이런 패키지가 의존성 그래프에 추가되는 시점에 알림이 가도록 만들 수 있습니다.
Alex Birsan의 의존성 컨퓨전 (2021)
Alex Birsan은 Microsoft, Apple, Uber 등 35개 이상 기업의 빌드 시스템에서 코드 실행에 성공했습니다. 방법은 단순했습니다 — 내부 사설 패키지 이름을 추측하거나 GitHub에서 수집한 뒤, 공개 npm에 같은 이름의 패키지를 더 높은 버전으로 게시. 그러면 빌드 시스템이 공개 버전을 우선시해 가져갔습니다.
이 공격의 본질은 “들어와서는 안 될 패키지가 의존성 그래프에 새로 추가되는 것”입니다. Dependency Review Action은 PR diff에서 의존성 그래프 변경을 자동으로 감지하므로, 이런 신규 추가를 가장 빨리 포착할 수 있는 지점이 됩니다.
Dependency Review Action의 동작 방식
actions/dependency-review-action은 PR의 base 브랜치와 head 브랜치의 의존성 그래프 차이를 계산한 뒤, 추가되거나 변경된 의존성에 대해 다음을 검사합니다.
- 알려진 CVE (GitHub Advisory Database 매칭)
- 심각도(severity) —
low/moderate/high/critical - 라이선스 (
allow-licenses/deny-licenses화이트/블랙리스트) - (선택) OpenSSF Scorecard 점수 — 메인테이너 활동성·코드 리뷰 빈도 등
설정한 임계치를 초과하는 변경이 있으면 PR이 실패하고, PR 댓글로 어떤 의존성이 어떤 이유로 막혔는지가 자동으로 표시됩니다.
어떻게 적용하는가
가장 간단한 워크플로우는 다음과 같습니다.
# .github/workflows/dependency-review.yml
name: Dependency Review
on: pull_request
permissions:
contents: read
pull-requests: write # PR에 댓글로 결과 보고
jobs:
review:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@<SHA> # v6
- uses: actions/dependency-review-action@<SHA>
with:
fail-on-severity: high
comment-summary-in-pr: always
옵션 의미:
| 옵션 | 설명 |
|---|---|
fail-on-severity | 이 등급 이상의 CVE가 추가되면 PR 실패. low / moderate / high / critical |
comment-summary-in-pr | always로 두면 PR마다 자동 요약 댓글 |
allow-licenses / deny-licenses | 허용/금지 라이선스 목록 |
권장 시작 설정
처음 도입할 때는 다음 조합이 비용 대비 효과가 좋습니다.
fail-on-severity: high
comment-summary-in-pr: always
high 이상만 막으면 false positive가 거의 없고, 댓글로 시각화되면 리뷰어가 추가 의존성의 위험도를 한눈에 확인할 수 있습니다. 운영 안정성이 확보되면 moderate로 단계적으로 좁혀갈 수 있습니다.
한계
- GitHub Advisory Database에 등재된 CVE만 잡습니다. 0-day나 등재 전 단계의 악성 패키지는 cooldown이나 Socket 같은 행위 분석 SaaS가 보완해야 합니다.
- 이미 머지된 의존성은 검사 대상이 아닙니다. 새 PR의 diff만 봅니다. 이미 있는 의존성에 새 CVE가 추가되는 경우는
yarn npm audit이 잡습니다. - GitHub 외 호스팅에서는 작동하지 않습니다. GitLab·Bitbucket을 쓴다면 Snyk PR Checks 같은 대안을 봐야 합니다.
마무리
PR 단계는 공급망 방어의 마지막 인간 검수 지점입니다. 그 지점에서 의존성 변경의 위험도를 사람이 매번 직접 판단하기란 현실적으로 어렵습니다. Dependency Review Action은 이 판단을 자동화해서, 리뷰어가 “이 PR에 추가되는 새 의존성에 알려진 CVE가 있는가?”를 별도로 조사하지 않아도 되게 만듭니다.
워크플로우 하나, 옵션 두 개로 끝납니다. 적용 비용 대비 막아낼 수 있는 사고 범위가 넓어, 공급망 방어 시리즈의 세 전략 다음으로 도입하기 좋은 네 번째 층입니다.
참고 자료
- GitHub Dependency Review Action 공식 저장소
- About Dependency Review (GitHub Docs)
- Alex Birsan, “Dependency Confusion”
- crossenv malware on the npm registry (npm 공식 분석)
제 블로그가 도움이 되셨나요? 하단의 댓글을 달아주시면 저에게 큰 힘이 됩니다!
앱 홍보
Deku가 개발한 앱을 한번 사용해보세요.Deku가 개발한 앱은 Flutter로 개발되었습니다.관심있으신 분들은 앱을 다운로드하여 사용해 주시면 정말 감사하겠습니다.