"이미지 스캔 게이트 걸어주세요" 한 줄에서 시작된 3일
보안팀 요청은 늘 한 줄입니다. "CI에 이미지 취약점 스캔 게이트 걸어주세요." 그런데 검색하면 Trivy도, Grype도, Docker Scout도 전부 "빠르고 정확하다"고 합니다. 더 곤란한 건 실제로 같은 이미지를 세 도구로 돌려보면 탐지 건수가 서로 다르게 나온다는 점입니다. 이 지점에서 대부분 "누가 맞느냐"를 검증하려다 3일을 씁니다.
결론부터 말하면 그 질문은 답이 없습니다. 세 도구는 애초에 같은 물건이 아니고, 숫자 차이의 정체는 대부분 정확도가 아니라 판정 기준의 차이입니다. 그래서 이 글은 "무엇이 더 좋은가"가 아니라 **"우리 상황에서는 무엇이 탈락하는가"**로 질문을 바꿉니다. 판정 축은 세 개입니다.
- 스캔 대상 범위 — OS 패키지만인가, 언어 의존성·IaC·시크릿까지인가
- 취약점 DB 출처 — 배포판 보안 권고 우선인가, NVD 버전 매칭 중심인가
- 예외 처리·운영 비용 — ignore를 어떻게 관리하고, 오프라인에서 굴러가는가
정체성 비교: 세 도구는 같은 카테고리가 아니다
| 항목 | Trivy | Grype (+Syft) | Docker Scout |
|---|---|---|---|
| OS 패키지 | 지원 | 지원 | 지원 |
| 언어 의존성 | 지원(다수 생태계) | 지원(Syft 카탈로거 기반) | 지원 |
| IaC / K8s manifest | 지원(설정 스캔) | 미지원(스코프 밖) | 미지원(스코프 밖) |
| 시크릿 탐지 | 지원 | 미지원 | 미지원 |
| SBOM | 생성·소비(SPDX/CycloneDX) | Syft가 생성, Grype가 소비 | 이미지 기반 SBOM 조회 |
| DB 출처 | 배포판 보안 권고 + 다수 소스 취합 | 배포판 권고 + NVD 등 취합 | Docker가 큐레이션한 어드바이저리 |
| 오프라인 운용 | DB 사전 다운로드·미러 가능 | DB 사전 다운로드·미러 가능 | Docker 계정·서비스 종속성 확인 필요 |
| 과금 경계 | OSS CLI 무료 | OSS CLI 무료 | CLI 무료 범위 + 조직 기능은 플랜 종속 |
포지셔닝을 한 문장씩 정리하면 이렇습니다. Grype는 Syft와 짝입니다(SBOM 생성=Syft, 스캔=Grype). SBOM을 산출물로 남겨야 하는 팀에게는 이 분리가 장점입니다. Trivy는 올인원 지향이라 스캐너 하나로 이미지·IaC·시크릿까지 덮으려는 팀에 맞습니다. Docker Scout는 Docker 생태계(Desktop/Hub/CLI)에 붙는 관리형 성격이라, 이미 Hub 중심으로 굴러가는 팀에서 도입 비용이 사실상 0에 가깝습니다.
왜 같은 이미지인데 숫자가 다른가
핵심은 여기입니다. Debian·Alpine·Red Hat 같은 배포판은 자체 Security Tracker를 운영하며, 상위 버전으로 올리지 않고 패치만 백포트하는 경우가 흔합니다. 이때 패키지 버전 문자열은 그대로인데 실제로는 수정된 상태죠. 배포판은 이런 CVE를 not affected, will_not_fix, wont-fix 같은 상태로 명시합니다.
- 배포판 권고를 우선 참조하는 경로: 백포트 반영 → 해당 CVE를 제외하거나 fix 없음으로 분류
- NVD 버전 매칭 경로: "이 패키지 버전 ≤ 취약 범위" → 여전히 취약으로 판정
즉 숫자 차이는 대체로 이 판정 기준 차이 + 스캔 대상 범위 차이(언어 의존성 카탈로깅 방식 포함)의 합입니다. 따라서 남의 벤치마크 표를 믿지 말고, 자기 이미지로 직접 돌려서 차이를 해석하는 게 맞습니다. 차이가 났다면 순서대로 확인하세요. ① 패키지 타입이 다른가(OS vs 언어) → 스캔 범위 차이 ② 같은 패키지인데 한쪽만 잡았나 → DB 출처·fix-state 판정 차이 ③ severity가 다른가 → 심각도 산정 소스 차이.
고르지 말고 탈락시켜라: 상황별 의사결정표
| 상황 | 1순위 권장 | 탈락 도구와 이유 | 주의점 |
|---|---|---|---|
| 단일 이미지 CI 게이팅만 필요 | 셋 다 가능 → 팀 익숙도로 결정 | 탈락 없음 | 여기서 비교에 3일 쓰지 말 것. 요구가 하나면 도구도 하나면 됨 |
| SBOM을 산출물로 보관·제출(고객사/규제) | Syft+Grype 또는 Trivy | Scout — SBOM을 독립 아티팩트로 관리·서명하는 파이프라인에는 결합도가 높음 | 제출 포맷(SPDX vs CycloneDX)을 계약서에서 먼저 확인 |
| Docker Desktop·Hub 중심, 개발자 로컬 경험 중시 | Docker Scout | Trivy/Grype — 기능은 되지만 로컬 UX·설치·교육 비용이 추가 발생 | 조직 단위 정책·이력은 플랜 종속. 과금 경계 사전 확인 |
| 폐쇄망·오프라인(DB 미러링 필수) | Trivy 또는 Grype | Scout — 외부 서비스·계정 종속성 검증이 선행되어야 함 | DB 미러 갱신 주기를 운영 항목으로 등록(방치 시 스캔이 무의미) |
| IaC·시크릿까지 한 도구로 커버 | Trivy | Grype/Scout — 해당 스코프가 설계상 대상 밖 | 도구 하나로 묶으면 실패 원인 분리가 어려워짐. job 분리 권장 |
| 이미지 취약점이 너무 많아 게이트가 안 걸림 | (도구 무관) 베이스 이미지 교체 우선 | — | 스캐너 튜닝보다 표면 축소가 빠른 구간이 실제로 존재 |
두 개를 쓰는 게 맞는 경우도 있습니다. Syft로 SBOM을 산출물로 남기고, Trivy로 게이팅하는 조합은 공급망 요구와 게이트를 동시에 만족시킵니다. 다만 CI 시간이 두 배로 붙는 트레이드오프는 명시적으로 받아들여야 합니다.
복붙 실전 설정 + 실패 분기
로컬에서 3도구 1줄 스캔
# 세 도구 모두 옵션이 버전마다 다르므로 --version / --help로 먼저 확인
trivy image myapp:1.0
syft myapp:1.0 -o cyclonedx-json > sbom.json && grype sbom:sbom.json
docker scout cves myapp:1.0정상 결과는 취약점 요약 테이블(패키지/설치 버전/CVE/심각도/수정 버전) 출력입니다. 여기서 건수와 소요 시간을 기록해 두세요. 판단 근거는 이 기록이지 남의 표가 아닙니다. 출력이 비어 있다면 이미지 태그 오타이거나 DB 다운로드 실패인 경우가 많으니 종료 코드와 stderr를 먼저 보세요.
GitHub Actions: HIGH/CRITICAL만 실패
- name: Trivy gate
run: |
trivy image --severity HIGH,CRITICAL \
--ignore-unfixed --exit-code 1 myapp:${{ github.sha }}- name: Grype gate
run: |
syft myapp:${{ github.sha }} -o cyclonedx-json > sbom.json
grype sbom:sbom.json --fail-on high- name: Scout gate
run: |
docker scout cves --exit-code \
--only-severity critical,high myapp:${{ github.sha }}세 경우 모두 종료 코드 1이 게이트 실패입니다. 처음부터 CRITICAL만 막고 HIGH는 경고로 두는 단계적 도입이 현실적입니다.
예외 처리: ignore 파일은 방치하면 부채가 된다
# .trivyignore
# CVE-2024-XXXXX
# 사유: 사용하지 않는 코드 경로 / 담당: dhson / 만료: 2026-09-30
CVE-2024-XXXXX# .grype.yaml
ignore:
- vulnerability: CVE-2024-XXXXX
package:
name: libfoo
fix-state: not-fixed # 조건부 예외: 패키지·fix 상태까지 지정 가능Docker Scout는 파일보다 정책/대시보드 기반 예외에 가깝습니다. 조직 단위 통제에는 유리하지만, 예외 근거가 코드 리뷰 히스토리에 남지 않는다는 점은 감수해야 합니다.
표현 방식은 달라도 규칙은 하나입니다. 예외에 사유·담당자·만료일이 없으면 6개월 뒤 아무도 못 지웁니다. 만료일 지난 예외를 주기적으로 뽑아 리뷰하는 잡을 함께 만들어 두세요.
반드시 마주치는 실패 분기 3종
① DB 다운로드 rate limit / 오프라인 실패 DB 레지스트리 pull 제한이나 폐쇄망에서 스캔이 통째로 실패합니다. 처방은 캐시 + 사전 fetch입니다.
# 예: DB만 먼저 받아 캐시에 적재 → 이후 --skip-db-update로 스캔
trivy image --download-db-only
trivy image --skip-db-update myapp:1.0CI에서는 DB 디렉터리를 actions/cache 등으로 캐싱하고, 폐쇄망이면 내부 레지스트리에 DB를 미러링합니다. 미러 갱신이 멈추면 스캔은 통과하는데 의미는 없는 상태가 되므로 갱신 주기를 모니터링 항목으로 올려야 합니다.
② fixed version 없음 때문에 파이프라인 영구 실패
배포판이 will_not_fix로 둔 CVE는 아무리 업데이트해도 사라지지 않습니다. 이때 --ignore-unfixed(Trivy)나 fix-state 필터(Grype)를 쓰는데, 무조건 켜면 실제 위험을 숨깁니다. 판단 기준은 이렇게 잡으세요. 게이트(머지 차단)에는 fix 가능한 것만 → 고치라는 지시가 실행 가능해야 하므로. 리포트(주간 이슈)에는 전체 → 위험 자체는 계속 보여야 하므로.
③ 베이스 이미지 교체가 스캐너 튜닝보다 빠른 경우 ignore 항목을 하나씩 늘리며 버티는 중이라면 방향이 틀렸을 수 있습니다. 기준은 단순합니다. ignore 항목이 5개를 넘어가면 베이스 이미지를 의심하세요. distroless·slim·alpine 전환, 빌드 스테이지 분리(컴파일 도구는 최종 이미지에서 제거)로 취약점 표면 자체를 줄이는 편이 튜닝보다 빠른 구간이 분명히 있습니다.
도입 비용 계산과 이번 주에 할 3가지
CI 시간은 실제 비용입니다. 다음 공식으로 먼저 계산하세요.
증가 CI분/월 = (스캔 소요 초 ÷ 60) × 일 빌드 수 × 근무일예를 들어 스캔 40초, 일 빌드 20회, 근무일 20일이면 월 약 267분입니다 (예시 값 — 환경별 상이). 여기서 가장 큰 변수는 취약점 DB 캐시 유무입니다. 캐시가 없으면 매 빌드가 수십~수백 MB DB를 새로 받고, 그 시간이 스캔 시간보다 클 수도 있습니다. 캐시 적용 전후를 각각 측정해 위 공식에 대입해 보면 대부분 캐시 도입이 최우선 과제라는 결론이 나옵니다.
유료 전환 임계도 미리 그어 두면 좋습니다. 레포 몇 개, 팀 하나, 게이트만 필요하면 OSS CLI로 충분합니다. 반대로 조직 단위 정책 일괄 적용, 취약점 이력·추이 대시보드, 감사 증적 제출이 요구되기 시작하면 그때부터는 직접 만드는 비용과 유료 플랜을 비교하는 국면입니다. 정확한 무료 범위와 가격은 각 벤더 공식 문서에서 확인이 필요합니다.
이번 주에 할 일은 세 가지입니다. 첫째, 오늘 자기 이미지로 위 1줄 명령 3개를 돌려 건수와 소요 시간을 기록하세요. 둘째, 의사결정표에서 자기 상황에 해당하는 한 행을 찾아 탈락시키고 남은 것으로 확정하세요. 셋째, HIGH/CRITICAL 게이팅 PR을 하나 올리세요.
스캐너 선택은 취향 싸움이 아니라 제약 조건 확인 작업입니다. 오프라인인지, SBOM을 제출해야 하는지, IaC까지 덮어야 하는지 — 이 세 질문에 답하면 후보는 대개 하나로 줄어듭니다. 숫자를 비교하느라 3일을 쓰는 대신, 제약을 적어 탈락시키고 오늘 PR을 올리세요. 완벽한 도구보다 오늘 켜진 게이트가 언제나 더 안전합니다.
자주 묻는 질문 (FAQ)
Q. Trivy와 Grype 중 어느 쪽이 더 정확한가요? A. 같은 이미지에서 결과가 갈리는 이유는 대개 정확도가 아니라 판정 기준입니다. 배포판 보안 권고(백포트·will_not_fix)를 우선 반영하는 경로와 NVD 버전 매칭 경로는 같은 패키지를 다르게 판정합니다. 자기 이미지로 직접 돌린 뒤 차이가 난 항목이 패키지 타입 차이인지, fix-state 판정 차이인지 분류해서 해석하는 편이 정확합니다.
Q. --ignore-unfixed를 항상 켜도 되나요?
A. 게이트(머지 차단)에는 켜는 편이 현실적입니다. 수정 버전이 없는 CVE로 파이프라인이 영구 실패하면 개발자가 할 수 있는 조치가 없기 때문입니다. 다만 주간 리포트나 보안 검토에는 끄고 전체를 봐야 합니다. 무조건 켜두면 실제 위험이 시야에서 사라집니다.
Q. 폐쇄망에서도 이미지 스캔이 가능한가요? A. 가능합니다. Trivy·Grype 모두 취약점 DB를 사전에 받아 두거나 내부에 미러링하는 방식을 지원합니다. 핵심은 미러 갱신 주기를 운영 항목으로 관리하는 것입니다. DB가 오래되면 스캔은 통과하지만 의미가 없어집니다. Docker Scout는 외부 서비스·계정 종속성을 먼저 검증해야 합니다.
이 글은 AI 에이전트가 자료 조사와 1차 초안 작성을 담당하고, 사람 편집자가 사실관계·출처·톤과 맥락을 검토한 뒤 발행했습니다. 환경(OS·버전)에 따라 결과가 다를 수 있으니 적용 전 공식 문서를 함께 확인하세요. 오류를 발견하시면 이메일로 제보해 주세요 — 확인 후 신속히 정정합니다.
댓글
첫 번째 댓글을 남겨보세요.