OSV-Scanner와 GitLab CI로 취약점 패치 반영을 앞당기는 5단계

AI로 취약점을 찾는 속도는 빨라졌지만, 패치가 우리 조직의 리포지터리에 반영되는 속도까지 함께 빨라지지는 않습니다. 취약점을 찾은 뒤 패치를 만들고 취약점 정보를 공개하기까지 시간이 걸리고, 패치가 나온 뒤에도 우리 리포지터리에 반영하기까지 또 시간이 걸리기 때문입니다.
나온 패치를 반영하는 일은 해를 넘겨 늦어지기도 합니다. 일례로 npm 패키지 semver의 취약점 CVE-2022-25883은 2023년에 패치가 나왔는데요. 애플리케이션 보안 기업 Black Duck의 2026년 오픈 소스 보안·위험 분석(OSSRA) 보고서에 따르면, Black Duck이 2024년 11월부터 1년 동안 감사한 상용 코드베이스의 21%에 이 취약점이 여전히 남아 있었습니다.
이처럼 패치가 나온 뒤에도 우리 리포지터리에 반영되기까지 시간이 오래 걸리는 현상을 저는 '반영 지연'이라고 부르려 합니다. 패치가 언제 나올지는 우리가 정할 수 없지만, 나온 패치를 얼마나 빨리 반영할지는 우리 조직의 몫입니다.
이 글에서는 먼저 AI로 취약점을 빨리 찾는데도 패치 반영이 늦어지는 이유를 구조적 원인과 조직 차원의 원인으로 나눠 살펴봅니다. 이어서 조직 차원의 원인에 대응하는 방법으로, 취약점 탐지와 패치 검증, 취약한 버전의 재유입 차단을 CI가 맡는 패치 사이클을 제안하고요. 오픈 소스 취약점 스캐너인 OSV-Scanner와 GitLab CI로 이 패치 사이클을 진단부터 정기 스캔까지 다섯 단계로 직접 만들고, 작동 결과를 확인합니다. 마지막으로 패치 사이클을 운영할 때 유의할 네 가지 사항을 정리합니다.
AI로 취약점을 빨리 찾을 수 있는데 왜 패치 반영은 늦어지나요?
이제 AI로 취약점을 발견하는 속도는 크게 빨라졌습니다. 그러나 평소 사용하는 오픈 소스 패키지에서 취약점이 발견돼도 패치가 나와서 우리 조직의 리포지터리에 반영되려면 시간이 여전히 오래 걸리는데요. 그 원인은 구조적 원인과 조직 차원의 원인으로 구분할 수 있습니다.
취약점 패치가 우리 리포지터리에 반영되기까지
구조적 원인: 취약점 검증과 패치 개발, 비공개 조율
취약점을 발견한 뒤 패치가 나오고 취약점 정보가 공개되기까지 시간이 걸리는 이유는 크게 두 가지입니다.
- 취약점 검증과 패치 개발: 새롭게 발견한 취약점이 실제로 위험한지 확인하고, 패치를 만들어 테스트하고, 취약점 정보와 패치를 언제 어떻게 공개할지 정하는 데 여전히 사람의 손길과 시간이 필요합니다. 기업은 자기 코드에서 찾은 취약점을 직접 수정할 수 있지만, 오픈 소스 프로젝트는 다릅니다. 외부에서 찾아 제보한 취약점을, 대개 자원봉사로 일하는 메인테이너가 패치해야 하기에 시간이 더 오래 걸리죠.
- Anthropic이 제보한 취약점 중 패치가 확인된 것은 약 8%: 일례로 2026년 10월 2일 기준으로 Anthropic이 오픈 소스 프로젝트 591곳에 제보한 취약점 6157건 가운데, 패치가 나왔다고 Anthropic이 파악한 것은 516건에 그쳤습니다. 6157건 중 4824건(약 78%)은 메인테이너의 요청 등으로 외부 검증 없이 바로 보낸 것이라 모두가 실제 취약점은 아닐 수 있는데요. 패치는 만드는 데 시간이 오래 걸리는 만큼 패치 수는 실제 진척을 늦게 반영하는 지표이기도 합니다.
- 여러 메인테이너가 일손 부족을 호소: 2026년 5월 업데이트에서 Anthropic은 여러 메인테이너가 일손이 크게 부족하다고 전했습니다. 일부는 패치를 설계할 시간이 필요하다며 취약점 제보 속도를 늦춰 달라고 요청했다고 하죠. 당시 Anthropic의 AI 모델 Claude Mythos Preview가 찾은 고위험·치명적 취약점은 패치까지 평균 2주가 걸렸습니다.
- 비공개 조율: 취약점을 찾는 즉시 공개하지 않고, '조정된 취약점 공개(Coordinated Vulnerability Disclosure)' 절차에 따라 일정 기간 뒤에 공개하는 업계 관행도 한 요인입니다. 이는 공격자가 취약점을 먼저 알고 공격하기 전에 메인테이너가 패치를 만들고 사용자가 패치로 업데이트할 시간을 주기 위한 목적이죠.
- 기본 공개 기한은 제보 후 90일: 보통 취약점을 발견한 보안 연구자나 기업은 먼저 프로젝트 메인테이너에게 이를 비공개로 알립니다. 이어서 메인테이너가 패치를 만들어 배포하고 일정 기간이 지나면, 발견한 쪽과 메인테이너가 시점을 맞춰 취약점의 세부 내용을 공개하는데요. 공개 기한은 보통 메인테이너에게 알린 날로부터 90일이고, 그 전에 패치가 나오면 사용자가 업데이트할 시간을 두고 세부 내용을 공개합니다. 이 유예 기간은 저마다 달라서 Google의 보안 연구팀 Project Zero는 패치 후 30일, Anthropic은 보통 45일을 둡니다.
- 권고문이 없으면 스캐너도 탐지하기 어려움: 의존성 스캐너(SCA)는 주로 공개된 권고문을 바탕으로 취약점을 탐지합니다. GitHub의 Dependabot도 GitHub이 검토한 권고문이 GitHub Advisory Database에 올라와야 알림을 보내는데요. GitHub은 메인테이너에게 패치가 나오면 권고문을 공개해 알리도록 안내하죠. 따라서 패치가 나오기 전 비공개 조율 기간에는 우리가 쓰는 패키지에 취약점이 있다는 사실을 알기 어렵고요. 패치가 나온 뒤에도 권고문이 나오지 않으면 Dependabot처럼 권고문에 기대는 스캐너는 이 취약점을 탐지하지 못합니다. Anthropic은 메인테이너가 권고문 없이 패치만 배포한 사례가 있다고 밝힙니다. 메인테이너 대신 보안 연구자가 CVE를 받거나 보안 업체가 자체 연구로 데이터베이스를 보강하기도 하지만, 조용히 수정된 취약점을 모두 잡아내지는 못합니다.
조직 차원 원인: 영향 파악 부족, 전이 의존성, 호환성 부담
이 글에서는 패치가 나온 뒤에도 우리 조직의 리포지터리에 반영되기까지 시간이 오래 걸리는 현상을 '반영 지연'이라고 부릅니다. 반영 지연이 생기는 이유는 크게 세 가지입니다.
- 영향 파악 부족: 권고문이 공개돼도 그 취약점이 우리 리포지터리에 영향을 주는지 알아내지 못하면 패치를 반영하기 어렵습니다. 앞서 다룬 구조적 원인이 패치와 권고문이 나오기까지 시간이 걸려서 생기는 문제라면, 영향 파악 부족은 패치와 권고문이 나왔는데도 우리 조직이 그 영향을 몰라서 생기는 문제입니다. 영향을 알려면 권고문을 확인해야 하고, 우리 리포지터리에 어떤 오픈 소스가 들어 있는지도 알아야 합니다.
- 오픈 소스 구성 요소의 16%는 의존성 목록에 보이지 않음: Black Duck의 2026년 OSSRA 보고서에 따르면, Black Duck이 감사한 코드베이스에서 찾은 오픈 소스 구성 요소의 16%는 복사해서 붙여 넣은 코드, 벤더 코드 직접 포함, AI 생성처럼 표준 패키지 매니저를 거치지 않고 코드베이스에 들어왔습니다. 이런 구성 요소는 의존성 목록만 읽는 스캐너에 보이지 않습니다. 따라서 권고문이 나와도 관련 취약점이 우리 조직의 리포지터리에 영향을 주는지 판단하기 어렵습니다.
- 전이 의존성: 직접 선택하지 않은 패키지에 취약점이 있으면, 개발자는 그 영향을 판단하기 어렵습니다. 이런 패키지는
package.json같은 의존성 선언 파일에 나오지 않아 무엇이 들어 있는지부터 따로 확인해야 하기 때문입니다. 패치를 포함한 버전으로 패키지를 업그레이드하기도 어렵습니다. 그 패키지의 버전이 다른 패키지의 버전 조건과 얽혀 있어 한 패키지만 따로 업그레이드할 수 없을 때가 있기 때문입니다.- jws는 jwa와 함께 올려야 함: 예를 들어, jsonwebtoken은 jws를 의존성으로 쓰는데 패치가 담긴 jws 3.2.3은 jwa 1.4.2 이상을 요구합니다. 리포지터리에 jwa 1.4.1이 고정돼 있다면 jws만 업그레이드해서는 이 버전 조건을 맞출 수 없어 jwa까지 함께 업그레이드해야 합니다(다만 jws 3.2.3이 수정한 취약점의 권고문은 jsonwebtoken 사용자는 jws.verify()를 쓰는 경우에 해당해 영향을 받지 않는다고 밝힙니다).
- 호환성 부담: 패치가 메이저 버전으로만 나오면 개발팀은 반영을 미루기 쉽습니다. 메이저 버전으로 업그레이드하려면 변경된 API에 맞춰 코드를 수정하고, 기존 기능을 다시 테스트하고, 동작 변화가 사용자에게 주는 영향까지 검토해야 하기 때문입니다. 그러나 패치 반영을 미룰수록 부담은 더 커지죠. Datadog은 애플리케이션이 여러 버전이나 뒤처지면 업그레이드에 드는 시간과 비용이 크게 늘어 작업이 미뤄지기 쉽고, 결국 지원이 끝난 버전에 머물게 된다고 분석합니다. 취약점을 수정하지 못한 채 보안 상태는 악화되고요.
- jsonwebtoken의 패치는 메이저 버전 9.0.0으로만 나옴: jsonwebtoken을 관리하는 Auth0는 취약점을 수정한 새 메이저 버전 9.0.0을 내면서 이 버전으로 업그레이드하면 설정과 애플리케이션에 따라 사용자에게 영향이 있을 수 있다고 안내했습니다. 8.x에는 패치가 나오지 않아 취약점을 수정하려면 9.0.0 이상으로 업그레이드해야 하는데요. 그 전에 서비스에 문제가 없는지 검토해야 합니다. 그만큼 패치 반영도 미뤄지기 쉽습니다.
반영 지연은 어떻게 줄일 수 있나요?
패치 반영이 늦어질수록 위험에 노출되는 시간도 길어집니다. 패치가 나오기까지 걸리는 시간은 개별 조직이 앞당기기 어렵습니다. 그러나 패치가 나온 뒤의 반영 지연 문제는 조직이 직접 개선할 수 있죠.
패치 반영이 늦어지는 조직 차원의 원인 세 가지는 CI 기반 패치 사이클로 대처할 수 있습니다. 이는 CI가 정해진 주기로 의존성을 진단하고, 사람이 fix 명령을 실행해 자동 수정한 lockfile을 GitLab merge request(MR)로 올려 CI 검증을 통과하면 merge해 반영하고, 반영한 상태를 게이트로 유지하는 과정을 반복하는 건데요. 자동 수정한 내용이 MR로 올라가기에 조직이 이미 운영하는 코드 리뷰와 승인 절차를 그대로 거치게 할 수 있습니다.
이 글은 CI 기반 패치 사이클로 조직 차원의 세 가지 원인에 각각 대응하는 방법을 다음과 같이 제안합니다.
- 영향 파악 부족 → 정기 스캔과 알림
- CI가 정해진 주기로 lockfile을 취약점 데이터베이스와 대조합니다. 우리 의존성에 해당하는 새 권고문이 나오면 OSV-Scanner는 취약점을 찾았다는 뜻으로 종료 코드 1을 반환하고, CI는 이 스캔 Job을 실패로 처리합니다.
- 스캔 Job이 실패하면 파이프라인도 실패하고, GitLab은 기본 브랜치의 파이프라인 실패를 Slack 같은 메신저로 알립니다. 이로써 사람이 권고문을 일일이 찾아보지 않아도 우리 리포지터리에 취약한 버전이 들어 있다는 사실을 다음 정기 스캔에서 알 수 있습니다(실습 1단계 진단, 4단계 MR 게이트, 5단계 정기 스캔).
- 실제로 영향을 받는지는 권고문의 조건과 우리 코드가 그 기능을 쓰는지 대조해 판단해야 합니다. 다만 앞서 본 것처럼 패키지 매니저를 거치지 않고 들어온 구성 요소는 lockfile에 없어 이 방식으로는 찾기 어렵습니다.
- 전이 의존성 → lockfile 기반 스캔과 자동 수정
- lockfile에는 전이 의존성까지 실제로 설치되는 모든 패키지의 버전이 고정돼 있습니다. 따라서 lockfile을 스캔하면 의존성 선언 파일에 보이지 않던 패키지의 취약점까지 함께 찾을 수 있습니다(실습 1단계 진단).
- 전이 의존성의 취약점을 수정할 때는 OSV-Scanner의 fix 명령이 버전 조건을 모두 만족하면서 패치가 담긴 버전 조합을 계산해 lockfile을 다시 씁니다. jws의 취약점을 수정하려면 jwa도 함께 업그레이드해야 하는 상황처럼 여러 패키지를 함께 바꿔야 할 때는 lockfile을 다시 계산하는 relax 전략으로 한 번에 처리할 수 있습니다(실습 2단계 자동 수정과 검증).
- 다만 relax는 lockfile 전체를 다시 계산하기에 lockfile 안의 버전만 바꾸는 in-place 전략보다 바뀌는 패키지가 많을 수 있습니다.
- 호환성 부담 → 테스트를 거친 패치 반영과 기한 있는 업그레이드 예외
- 패치가 담긴 버전으로 바꾼 lockfile은 MR로 올리고, MR 파이프라인에서 기존 테스트를 통과해야 merge됩니다(실습에서는 npm test로 실행). 테스트가 다루는 범위에서 기존 기능이 그대로 동작하는지 확인하기에 업그레이드 부담을 줄일 수 있습니다(실습 2단계 자동 수정과 검증, 4단계 MR 게이트).
- 당장 진행할 수 없는 메이저 업그레이드는 사유와 만료일을 붙여 예외로 기록하고, 그동안 해당 취약점은 스캔 결과에서 걸러 냅니다.
- 만료일이 되면 그 취약점이 다시 스캔에 탐지돼 Job이 실패하고, 1번과 같은 메신저 알림이 옵니다. 이로써 미뤄 둔 업그레이드를 잊고 넘어가는 일을 막을 수 있습니다(실습 3단계 예외 기록, 5단계 정기 스캔).
아울러 패치를 반영한 상태를 유지하는 장치를 하나 더 추가합니다. 취약점을 포함한 버전이 다시 유입되는 MR은 merge되지 않도록 게이트로 차단하는 것입니다(실습 4단계 MR 게이트). 다만 게이트는 lockfile 전체를 검사하기에 이미 쓰고 있는 의존성에 새 취약점이 공개되면 수정하거나 예외로 기록하기 전까지 관련 없는 MR도 차단됩니다. main에서 수정하거나 예외를 기록한 뒤에도, 차단된 MR은 main의 변경을 받아(리베이스 등) 파이프라인을 다시 돌려야 통과합니다.
이로써 탐지와 검증, 차단은 CI가 맡고, 사람은 fix 명령을 실행해 그 결과를 리뷰하고 메이저 업그레이드나 예외처럼 판단이 필요한 일에 집중하는 시스템을 구축할 수 있습니다. 지금부터 이 패치 사이클을 오픈 소스 스캐너인 OSV-Scanner와 GitLab CI로 직접 만들고, 작동 결과를 확인해 보겠습니다.
CI 기반 패치 사이클에서 CI와 사람이 맡는 일
OSV-Scanner와 GitLab CI로 패치 사이클을 어떻게 만드나요?
패치 사이클은 진단, 자동 수정과 검증, 예외 기록, MR 게이트, 정기 스캔의 다섯 단계로 만듭니다. 단계별로 하는 일과 대응하는 문제는 다음과 같습니다.
| 단계 | 하는 일 | 대응하는 문제 |
|---|---|---|
| 1단계 진단 | lockfile을 스캔해 취약점 확인 | 영향 파악 부족, 전이 의존성 |
| 2단계 자동 수정과 검증 | fix 명령으로 lockfile을 수정하고, 테스트를 통과한 MR만 merge | 전이 의존성, 호환성 부담 |
| 3단계 예외 기록 | 당장 수정할 수 없는 취약점에 사유와 만료일을 붙여 예외로 기록 | 호환성 부담 |
| 4단계 MR 게이트 | 취약한 버전을 다시 들여오는 MR의 merge 차단 | 취약한 버전의 재유입 |
| 5단계 정기 스캔 | 매일 main을 스캔하고, 실패하면 Slack으로 알림 | 영향 파악 부족, 호환성 부담(예외 만료) |
실습 환경
- GitLab: GitLab.com의 비공개 프로젝트
- 샘플: lodash와 jsonwebtoken을 의존성으로 쓰는 작은 npm 프로젝트입니다. 오래 손대지 않은 리포지터리를 재현하려고
npm install --before=2021-01-01로 lockfile을 만들어 2021년 1월 1일 시점의 버전으로 고정했습니다. - 스캐너: OSV-Scanner v2.6.0을 썼습니다. 스캔과 테스트는 GitLab CI에서, 자동 수정(fix 명령)은 로컬에서 실행했습니다.
- 알림: GitLab for Slack app으로 Slack 채널에 알림을 보냈습니다.
- 결과 기준일: 2026년 10월 2일입니다. 취약점 데이터베이스에 권고문이 추가되거나 바뀌면 같은 lockfile이라도 결과가 달라질 수 있습니다.
실습은 npm으로 진행했지만, OSV-Scanner의 스캔은 Maven(pom.xml)과 Gradle(gradle.lockfile) 같은 Java 프로젝트도 지원합니다. 다만 2단계의 자동 수정은 npm과 Maven만 지원합니다.
1단계 진단: 패치가 나온 취약점부터 확인합니다
OSV-Scanner는 프로젝트의 의존성 목록을 오픈 소스 취약점 데이터베이스인 OSV.dev와 대조해 이미 알려진 취약점을 찾습니다. 새 취약점을 찾아내는 도구가 아니라, 공개된 권고문을 우리 의존성과 맞춰 보는 도구입니다. 이 실습에서는 package-lock.json을 스캔합니다.
설정
스캔은 GitLab CI Job으로 실행합니다. 처음에는 allow_failure: true를 붙인 리포트 모드로 시작합니다. 리포트 모드에서는 스캔 Job이 취약점을 찾아 실패해도 파이프라인을 막지 않고 경고만 남깁니다. lockfile에 이미 취약점이 쌓여 있는 상태에서 처음부터 게이트를 켜면 모든 MR의 파이프라인이 실패해 merge가 막히기 때문입니다.
workflow:
rules:
- if: $CI_PIPELINE_SOURCE == "merge_request_event"
- if: $CI_PIPELINE_SOURCE == "schedule"
- if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
stages:
- test
- scan
test:
stage: test
image: node:22
script:
- npm ci --ignore-scripts
- npm test
osv-scan:
stage: scan
image:
name: ghcr.io/google/osv-scanner:v2.6.0
entrypoint: [""]
needs: []
script:
- /osv-scanner scan source -L package-lock.json
allow_failure: true # 리포트 모드. 4단계에서 이 줄을 지웁니다.
workflow 규칙에 따라 MR 파이프라인, 스케줄 파이프라인, 기본 브랜치의 파이프라인만 실행됩니다.
결과
- 첫 파이프라인에서 스캔 Job은 lockfile의 패키지 16개 가운데 4개에서 취약점 8건(High 4건, Medium 4건)을 찾았습니다.
- 결과 표 위의
8 vulnerabilities can be fixed는 8건 모두 패치가 담긴 버전이 이미 나와 있다는 뜻입니다. - 샘플의 lodash 취약점을 처음 수정한 4.17.21은 2021년 2월에 나왔습니다. 이 샘플은 패치가 나온 지 5년이 넘도록 반영하지 않은 리포지터리와 같은 상태입니다.

2단계 자동 수정과 검증: 테스트를 통과한 MR로만 반영합니다
OSV-Scanner의 fix 명령은 찾은 취약점을 수정할 수 있는 버전 조합을 계산해 lockfile을 수정하고, 전략에 따라 package.json의 버전 조건도 바꿉니다. 공식 문서는 이 기능을 실험 기능으로 분류합니다. fix 명령을 실행하면 경고가 먼저 나옵니다. 신뢰할 수 없는 프로젝트에서 실행하면 패키지 매니저가 스크립트를 실행하거나 프로젝트에 지정된 외부 레지스트리를 따라갈 수 있다는 내용입니다.
실습에서는 바뀌는 범위가 작은 in-place 전략을 먼저 적용하고, in-place로 수정하지 못한 취약점에는 relax 전략을 이어서 적용했습니다.
in-place 전략으로 lockfile 안의 버전만 바꿉니다
in-place 전략은 각 패키지에 걸린 기존 버전 조건을 지키면서 lockfile 안의 취약한 버전만 바꿉니다. 이 전략은 비교적 덜 위험하지만, relax 전략보다 수정하는 취약점이 적을 수 있습니다.
실행
osv-scanner fix --strategy=in-place -L package-lock.json
수정한 lockfile은 MR로 올리고, MR 파이프라인에서 테스트가 통과하면 merge합니다.
결과
Found 10 vulnerabilities matching the filter
Can fix 6/10 matching vulnerabilities by changing 2 dependencies
UPGRADED-PACKAGE: lodash,4.17.20,4.18.1
UPGRADED-PACKAGE: semver,5.7.1,5.7.2
FIXED-VULN-IDS: GHSA-29mw-wpgm-hmr9,GHSA-35jh-r3h4-6jhm,GHSA-c2qf-rxjj-qqgw,GHSA-f23m-r3pf-42rh,GHSA-r5fr-rjxr-66jc,GHSA-xxjr-mmjv-4gpg
REMAINING-VULNS: 4
UNFIXABLE-VULNS: 4
- fix 명령은 권고문 ID 기준으로 10개 가운데 6개를 수정했다고 보고했습니다.
- lodash는 4.17.20에서 4.18.1로, semver는 5.7.1에서 5.7.2로 업그레이드됐습니다. lodash 하나를 올리자 lodash 관련 ID 5개가 한꺼번에 수정됐습니다.
- MR 파이프라인에서 테스트가 통과해 merge했고, 스캔 결과는 8건에서 4건으로 줄었습니다.

relax 전략으로 lockfile을 다시 계산합니다
남은 4건 가운데 jws 취약점 1건은 같은 메이저 버전인 3.2.3에 패치가 있는데도, in-place 전략으로는 수정할 수 없는 취약점(UNFIXABLE-VULNS)으로 분류됐습니다. jws 3.2.3은 jwa 1.4.2 이상을 요구하는데 lockfile의 jwa는 1.4.1이어서 jws만 바꾸면 버전 조건을 맞출 수 없기 때문입니다.
이런 취약점은 relax 전략으로 수정합니다. relax 전략은 package.json을 기준으로 의존성 그래프 전체를 가능한 최신 버전으로 다시 계산하고, 필요하면 직접 의존성의 버전 조건도 바꿉니다. npm 프로젝트에서는 기존 package-lock.json과 node_modules를 지운 뒤 npm install --package-lock-only로 lockfile만 다시 만듭니다.
실행
osv-scanner fix --strategy=relax --upgrade-config=minor -M package.json -L package-lock.json
--upgrade-config=minor를 붙이면 마이너 버전까지만 올립니다. 메이저 업그레이드가 섞이지 않게 하는 설정입니다. 이 lockfile도 MR로 올리고, 테스트가 통과하면 merge합니다.
결과
Found 4 vulnerabilities matching the filter
Can fix 1/4 matching vulnerabilities by changing 0 dependencies
FIXED-VULN-IDS: GHSA-869p-cjfg-cm3x
REMAINING-VULNS: 3
UNFIXABLE-VULNS: 3
changing 0 dependencies는package.json의 직접 의존성 조건을 하나도 바꾸지 않았다는 뜻입니다. 실제로package.json은 바뀌지 않았습니다.- lockfile에서는 jws가 3.2.2에서 3.2.3으로, jwa가 1.4.1에서 1.4.2로 함께 업그레이드됐습니다. 버전이 바뀐 패키지는 이 두 개였습니다.
- lockfile만 다시 만들고 패키지는 설치하지 않아 실행한 뒤에도 로컬에
node_modules는 없었습니다. - 이 MR도 테스트를 통과해 merge했습니다.

결과 요약
| 시점 | 영향받은 패키지 | 취약점 |
|---|---|---|
| 자동 수정 전 | 4개 | 8건 |
| in-place 적용 후 | 2개 | 4건 |
| relax 적용 후 | 1개 | 3건 |
- 두 번의 자동 수정으로 취약점은 8건에서 3건으로 줄었습니다.
- 남은 3건은 모두 jsonwebtoken 8.5.1의 취약점이고, 9.0.0 이상으로 메이저 업그레이드해야만 수정할 수 있습니다.
- 스크린샷을 찍고 결과를 확인한 시간을 포함해 첫 스캔부터 두 번째 자동 수정 MR을 merge하기까지 약 40분이 걸렸습니다.

3단계 예외 기록: 남은 취약점에는 사유와 만료일을 기록합니다
메이저 업그레이드는 코드에 미치는 영향을 검토한 뒤에 진행해야 합니다. 그때까지 jsonwebtoken 취약점 3건은 예외로 기록합니다.
설정
lockfile과 같은 디렉터리에 osv-scanner.toml을 만들고, 예외마다 권고문 ID(id), 만료일(ignoreUntil), 사유(reason)를 적습니다. 사유에는 지금 수정하지 못하는 이유, 결정한 사람, 다시 검토할 날짜를 남깁니다.
[[IgnoredVulns]]
id = "GHSA-8cf7-32gw-wr33"
ignoreUntil = 2026-12-31
reason = "이유: jsonwebtoken 9.0.0 메이저 업그레이드가 필요해 relax(minor) 처방으로 해결 불가, 9.x 마이그레이션 전까지 임시 예외 / 결정자: @username / 재검토일: 2026-12-01"
# GHSA-hjrf-2m68-5959, GHSA-qwph-4952-7xr6도 같은 형식으로 기록합니다.
OSV-Scanner는 스캔하는 파일과 같은 디렉터리에 있는 osv-scanner.toml을 자동으로 읽습니다. 따라서 스캔 명령은 바꾸지 않아도 됩니다.
결과
- 예외를 추가한 MR의 파이프라인에서 스캔 Job이 처음으로 통과했습니다.
- 걸러 낸 취약점마다 사유가 Job 로그에 남았습니다. 나중에 이 취약점을 왜 넘겼는지 로그로 추적할 수 있습니다.
- 로그의
and 1 alias는 예외로 지정한 GHSA ID와 별칭으로 연결된 ID도 함께 걸러졌다는 뜻입니다. OSV-Scanner는 취약점 하나를 예외로 두면 그 별칭도 함께 예외로 처리합니다.
Loaded filter from: /builds/…/osv-scanner.toml
GHSA-8cf7-32gw-wr33 and 1 alias have been filtered out because: 이유: jsonwebtoken 9.0.0 메이저 업그레이드가 필요해 … / 재검토일: 2026-12-01
Filtered 3 vulnerabilities from output
No issues found
4단계 MR 게이트: 취약한 의존성이 다시 유입되지 못하게 막습니다
예외를 기록한 뒤 스캔 결과는 0건이 됐습니다. 이제 스캔 Job을 게이트로 바꿉니다.
설정
osv-scanJob에서allow_failure: true한 줄을 지웁니다. 이제 스캔 Job이 실패하면 파이프라인 전체가 실패합니다.- Settings > Merge requests의 Merge checks에서 Pipelines must succeed를 활성화합니다. 최신 파이프라인이 성공하지 않았거나 아직 실행 중인 MR은 merge할 수 없습니다.
- 같은 화면의 Skipped pipelines are considered successful은 비활성화 상태로 둡니다. 이 옵션을 활성화하면
[skip ci]로 파이프라인을 건너뛴 MR도 성공으로 취급돼 게이트를 거치지 않고 merge할 수 있습니다.
결과
- 스캔 Job을 게이트로 바꾼 MR의 파이프라인은 그대로 통과했습니다. 스캔 결과를 먼저 0건으로 정리했기 때문입니다.
작동 확인
게이트가 실제로 막는지 확인하려고 lockfile을 첫 커밋의 상태로 되돌린 MR을 만들었습니다. 오래된 브랜치를 합치다 lockfile 충돌을 잘못 해결해 옛 버전이 되살아난 상황을 재현한 것입니다.
- 테스트 Job은 통과했지만, 스캔 Job이 되살아난 취약점 5건(권고문 ID 7개)을 찾아 파이프라인이 실패했습니다.
- 파이프라인이 실패한 MR은 merge할 수 없는 상태가 됐습니다.
- 예외로 둔 jsonwebtoken 취약점 3건은 이번에도 걸러졌습니다.
기능 테스트로는 막을 수 없는 변경을 게이트가 막은 것입니다.

5단계 정기 스캔: 새로 공개된 취약점과 만료된 예외를 Slack으로 알립니다
MR 게이트는 MR 파이프라인이 실행될 때만 검사합니다. 리포지터리에 커밋이 없으면 파이프라인도 실행되지 않아서 main에 있는 의존성에 새 취약점이 공개되거나 예외가 만료돼도 알 수 없습니다. 따라서 코드 변경과 관계없이 매일 main을 스캔합니다.
설정
- 파이프라인 스케줄을 만들어 main을 매일 09:00에 스캔합니다. 실습에서는 주기를 cron 표현식
0 9 * * *로, 시간대를 Asia/Seoul로 설정했습니다. 스케줄로 실행되는 파이프라인은 1단계 CI 설정의$CI_PIPELINE_SOURCE == "schedule"규칙에 해당합니다. - 프로젝트의 GitLab for Slack app 설정에서 트리거는 A pipeline status changes 하나만 활성화하고, 알림을 받을 채널을 지정합니다.
- Notify only broken pipelines를 체크하고, 알림을 보낼 브랜치는 Default branch로 정합니다. 이렇게 설정하면 성공한 파이프라인은 알리지 않고, 기본 브랜치의 파이프라인이 실패할 때만 알림을 보냅니다.
작동 확인
실습 중에 새 취약점이 공개되기를 기다릴 수는 없어서 예외 하나를 만료시켜 스캔 결과가 바뀌는 상황을 만들었습니다.
- GHSA-8cf7-32gw-wr33 예외의 만료일을 실습일(2026년 10월 2일)의 하루 전인 2026-10-01로 바꿔 main에 커밋했습니다. 커밋 파이프라인이 아니라 정기 스캔이 이 변화를 탐지하는지 확인하려고 커밋 메시지에
[skip ci]를 붙였습니다. 이 커밋의 파이프라인은 Job 없이 skipped로 기록됐습니다. - 10초 뒤 스케줄을 수동으로 실행했습니다. Job 로그에는 만료된 예외가
has unused ignores경고로 표시됐고, 스캔 Job은 해당 취약점을 다시 찾아 실패했습니다. - 이 파이프라인의 source는 schedule이었고, Slack 채널로 실패 알림이 왔습니다.
- 만료일을 원래대로 되돌린 커밋은
[skip ci]없이 올렸습니다. 이 커밋의 파이프라인에서는 예외 3건이 다시 적용돼 스캔이 통과했고, 성공한 파이프라인이어서 Slack 알림은 오지 않았습니다.


패치 사이클을 운영할 때 무엇을 유의해야 하나요?
위 실습은 GitLab.com의 리포지터리 한 곳에서 취약점 8건을 다뤘습니다. 실제로 운영할 때는 처리할 취약점이 더 많거나 폐쇄망처럼 환경이 다를 수 있고, 미뤄 둔 버전 업그레이드와 스캐너에 탐지되지 않는 취약점도 관리해야 합니다. 패치 사이클을 운영할 때 유의해야 할 네 가지 사항을 소개합니다.
처리할 취약점이 많으면 KEV, EPSS, CVSS 순으로 우선순위를 정합니다
처리할 취약점이 많을 때는 실제로 악용되고 있는지(KEV), 악용될 가능성이 높은지(EPSS), 얼마나 심각한지(CVSS) 순으로 우선순위를 정합니다. 미국 CISA는 실제 공격에 악용된 것으로 확인된 취약점을 KEV 카탈로그로 공개하고, 모든 조직에 이 목록의 취약점부터 수정하라고 권고합니다. FIRST의 EPSS는 공개된 CVE가 30일 안에 악용될 확률을 매일 계산해 무료로 공개합니다. OSV-Scanner 결과 표에는 CVSS 점수만 나오기에 KEV와 EPSS는 공개 데이터와 따로 대조해야 합니다. 같은 메이저 버전 안에서 수정할 수 있는 취약점은 순위와 관계없이 바로 자동 수정하고, KEV에 오른 취약점은 메이저 업그레이드가 필요하더라도 대처를 미루지 않기를 권장합니다.
취약점 처리 순서를 정하는 흐름
취약점 예외는 만료일과 재검토일을 함께 관리합니다
메이저 업그레이드를 미뤄 예외로 기록한 취약점은 예외의 만료일과 함께 예외의 재검토일을 정해 관리합니다. 만료일이 되면 그 취약점이 다시 탐지돼 스캔이 실패하고, 게이트는 lockfile 전체를 검사해 관련 없는 MR까지 merge가 막힙니다. 반면 사유에 미리 적어 둔 재검토일은 도구가 이를 탐지해서 사용자에게 알려 주지 않습니다. 따라서 재검토일을 만료일보다 한 달쯤 앞에 두고 이슈의 마감일이나 팀 일정에 따로 등록하면, 버전을 업그레이드할지 예외를 연장할지 미리 결정할 시간을 확보할 수 있습니다. 만료일 전에 업그레이드하거나 예외를 연장하면, 스캔이 실패해 관련 없는 MR의 merge까지 막히는 상황을 줄일 수 있습니다.
취약점 예외의 재검토일과 만료일
폐쇄망에서는 취약점 데이터베이스를 정기적으로 들여옵니다
외부와 통신할 수 없는 폐쇄망에서는 스캐너가 쓰는 취약점 데이터베이스를 외부에서 정기적으로 들여와야 합니다. 스캐너는 마지막으로 들여온 데이터베이스에 있는 취약점까지만 찾을 수 있어 데이터베이스를 들여오는 주기만큼 탐지가 늦어지고 반영 지연도 길어질 수 있습니다. OSV-Scanner는 미리 내려받은 데이터베이스로 스캔하는 오프라인 모드를 지원합니다. 조직의 반입 절차에 걸리는 시간까지 고려해 반입 주기를 정하고, 반입 직후에 스캔이 실행되도록 순서를 맞춥니다. 자동 수정에 필요한 패키지 정보도 사내 레지스트리에서 가져오도록 구성해야 합니다.
권고문이 나오기 전의 취약점은 다른 통제로 대비합니다
패치 사이클은 공개된 권고문이 있어야 작동하기에 권고문이 나오기 전의 취약점은 패치에 기대지 않는 통제로 대비해야 합니다. 특히 비공개 조율 기간의 취약점이나 권고문 없이 패치만 배포된 취약점은 스캐너에 탐지되지 않을 수 있습니다. Anthropic은 AI로 취약점을 찾고 악용하는 데 드는 시간이 줄어든 만큼 특정 패치에 기대지 않는 통제인 네트워크 기본 설정 강화, 다중 인증(MFA) 적용, 탐지와 대응을 위한 충분한 로그가 더 중요해졌다고 강조합니다. 패치 사이클과 함께 네트워크 기본 설정을 점검하고, 소스 코드와 CI/CD에 접근하는 계정에 MFA를 적용하며, 이상 징후를 탐지하도록 로그를 충분히 남깁니다.
맺음말
지금까지 AI로 취약점을 빨리 찾아도 패치 반영이 늦어지는 이유와 OSV-Scanner와 GitLab CI로 반영 지연을 줄이는 패치 사이클을 실습으로 살펴봤습니다. 글 전체 내용을 정리하면 다음과 같습니다.
- AI로 취약점을 찾는 속도가 빨라져도, 패치가 나오고 취약점 정보가 공개되기까지는 검증과 패치 개발, 비공개 조율에 시간이 걸립니다. 패치가 나온 뒤에도 영향 파악 부족, 전이 의존성, 호환성 부담 때문에 우리 리포지터리에 반영되기까지 시간이 걸리는데, 이 반영 지연은 개별 조직이 직접 줄일 수 있습니다.
- CI 기반 패치 사이클은 CI가 정해진 주기로 lockfile을 스캔하고, 사람이 fix 명령으로 자동 수정한 lockfile을 MR로 올려 테스트를 통과하면 merge하는 과정을 반복합니다. 당장 진행할 수 없는 메이저 업그레이드는 사유와 만료일을 붙여 예외로 기록하고, 취약한 버전이 다시 유입되는 MR은 게이트로 막습니다.
- 실습에서는 fix 명령으로 취약점 8건을 약 40분 만에 3건으로 줄였고, 메이저 업그레이드가 필요한 jsonwebtoken 3건은 예외로 기록했습니다. 게이트는 옛 lockfile이 되살아난 MR의 merge를 막았습니다. 정기 스캔은 예외가 만료된 취약점을 다시 탐지했고, 스캔 Job 실패는 Slack 알림으로 전달됐습니다.
- 운영할 때는 처리할 취약점의 우선순위를 KEV, EPSS, CVSS 순으로 정하고, 예외는 만료일과 재검토일을 함께 관리합니다. 폐쇄망에서는 취약점 데이터베이스를 정기적으로 들여오고, 권고문이 나오기 전의 취약점은 네트워크 기본 설정 강화, MFA, 충분한 로그처럼 패치에 기대지 않는 통제로 대비합니다.
이미 패치가 나온 취약점, 우리 리포지터리에는 몇 건 남아 있을까요?
AI로 취약점 발견은 빨라졌지만, 패치를 리포지터리에 반영하는 일은 각 조직의 몫입니다. 인포그랩이 의존성 진단과 자동 수정, 예외 관리, MR 게이트, 정기 스캔으로 이어지는 패치 사이클을 GitLab 환경에 맞게 설계하고 구축합니다.
참고 자료
- "Anthropic's coordinated vulnerability disclosure dashboard", Anthropic, https://red.anthropic.com/2026/cvd/
- "Project Glasswing: An initial update", Anthropic, https://www.anthropic.com/research/glasswing-initial-update
- "Vulnerability Disclosure Policy", Google Project Zero, https://projectzero.google/vulnerability-disclosure-policy.html
- "Coordinated vulnerability disclosure for Claude-discovered vulnerabilities", Anthropic, https://www.anthropic.com/coordinated-vulnerability-disclosure
- "GitHub Advisory database", GitHub Docs, https://docs.github.com/en/code-security/concepts/vulnerability-reporting-and-management/github-advisory-database
- "Repository security advisories", GitHub Docs, https://docs.github.com/en/code-security/concepts/vulnerability-reporting-and-management/repository-security-advisories
- "Researcher Reservation Guidelines", CVE Program, https://www.cve.org/Resources/Media/Archives/OldWebsite/cve/researcher_reservation_guidelines
- "Snyk users don't have to worry about NVD delays", Snyk, https://snyk.io/blog/snyk-users-dont-have-to-worry-about-nvd-delays/
- "Vulnerability fixes in plain sight: How your scanners are missing hundreds of vulnerabilities", Chainguard, https://chainguard.dev/unchained/vulnerability-fixes-in-plain-sight-how-your-scanners-are-missing-hundreds-of-vulnerabilities
- "2026 Open Source Security and Risk Analysis Report", Black Duck, https://www.blackduck.com/resources/analyst-reports/open-source-security-risk-analysis.html
- "auth0/node-jws Improperly Verifies HMAC Signature", GitHub Advisory Database, https://github.com/advisories/GHSA-869p-cjfg-cm3x
- "State of DevSecOps", Datadog, https://datadoghq.com/state-of-devsecops
- "Unrestricted key type could lead to legacy keys usage", Auth0 (GitHub), https://github.com/auth0/node-jsonwebtoken/security/advisories/GHSA-8cf7-32gw-wr33
- "OSV-Scanner", GitHub, https://github.com/google/osv-scanner
- "Output", OSV-Scanner Docs, https://google.github.io/osv-scanner/output/
- "GitLab for Slack app", GitLab Docs, https://docs.gitlab.com/user/project/integrations/gitlab_slack_application/
- "Guided Remediation", OSV-Scanner Docs, https://google.github.io/osv-scanner/experimental/guided-remediation/
- "Auto-merge", GitLab Docs, https://docs.gitlab.com/user/project/merge_requests/auto_merge/
- "Configuration", OSV-Scanner Docs, https://google.github.io/osv-scanner/configuration/
- "Merge request pipelines", GitLab Docs, https://docs.gitlab.com/ci/pipelines/merge_request_pipelines/
- "config", npm Docs, https://docs.npmjs.com/cli/v10/using-npm/config#before
- "Supported Artifacts and Manifests", OSV-Scanner Docs, https://google.github.io/osv-scanner/supported-languages-and-lockfiles/
- "CI/CD YAML syntax reference", GitLab Docs, https://docs.gitlab.com/ci/yaml/
- "Predefined CI/CD variables reference", GitLab Docs, https://docs.gitlab.com/ci/variables/predefined_variables/
- "Scheduled pipelines", GitLab Docs, https://docs.gitlab.com/ci/pipelines/schedules/
- "Reducing the Significant Risk of Known Exploited Vulnerabilities", CISA, https://www.cisa.gov/known-exploited-vulnerabilities
- "Exploit Prediction Scoring System (EPSS)", FIRST, https://www.first.org/epss/
- "Offline Mode", OSV-Scanner Docs, https://google.github.io/osv-scanner/usage/offline-mode/
이 글이 도움이 되셨나요?
인포그랩 전문가가 맞춤 상담을 도와드립니다.
관련 글

GitLab CI에 AI 에이전트를 연결할 때 점검할 5가지
코딩 에이전트를 GitLab CI에 연결했다면 CLAUDE.md와 AGENTS.md는 더 이상 문서가 아닙니다. MR 하나로 지시가 바뀌는 이 파일을 CODEOWNERS, 보호 브랜치, CI Job, Protected 변수로 통제하는 다섯 가지 점검 방법을 실습으로 정리했습니다.

2026년 CI/CD 공급망 공격 3가지 유형과 GitLab 방어 전략
2026년 CI/CD 파이프라인 공급망 공격 세 가지 유형과 발생 원인을 정리했습니다. GitLab의 보호된 변수와 커밋 SHA 고정, Pipeline execution policies, Job 토큰 allowlist로 대응하는 방법을 티어별로 살펴봅니다.

GitLab 액세스 토큰 점검 4단계: 경찰청 GitHub 토큰 유출 권고문 대응
경찰청이 GitHub 개인 액세스 토큰 유출 권고문을 배포했습니다. GitLab Self-Managed에서 토큰을 세고, 보고, 회전하고, 강제하는 4단계 실행 방법과 Free·Premium·Ultimate 티어별로 가능한 조치를 정리했습니다.