벤더 C/H · NVD C/H · 실효 C/H 세 열이 나란히 놓여 어느 것이 판정값인지 헷갈렸다.
실제로 "벤더 5 / NVD 11 인데 실효가 15" 를 어떻게 읽어야 하는지 질문이 나왔다 —
세 열이 같은 CVE 집합을 세지 않기 때문이다. 실효는 CVE 마다 max(벤더, NVD) 를
적용한 결과라 합집합이 된다(5 + 11 − 교집합 1 = 15). 가로로 더하거나 빼서
검산되지 않는 숫자들을 나란히 두는 것이 혼란의 원인이었다.
판정 기준은 실효 C/H 하나뿐이므로 그것만 남기고, 벤더·NVD 의 불일치는
`하향` 열(벤더가 NVD 보다 낮게 매긴 CVE 수)로 대신 드러낸다. 개별 CVE 목록은
이미 `⚠️ 벤더 하향 등급` 절에 있어 집계 열은 중복이었다.
이전: | 벤더 C/H | NVD C/H | 실효 C/H | 차단 | 예외 |
이후: | 실효 C/H | 차단 | 예외 | 하향 |
- 범례(SUMMARY_LEGEND) 를 상수로 빼서 render_md 와 render_brief_md 양쪽에 붙였다.
Job Summary(render_brief_md)에는 범례가 아예 없어서, 새 `하향` 열이 설명 없이
노출될 뻔했다.
하향 신호를 열로 남긴 이유 — 실측:
argocd:v2.14.5 (ubuntu 24.04) 하향 43건
argocd:v3.5.1 (ubuntu 26.04) 하향 1건
dex:v2.45.1 (alpine) 하향 4건 ← 전부 SeveritySource=ghsa (Go 모듈)
redis 2개 (alpine) 하향 0건 ← Alpine 은 자체 등급을 제공하지 않는다
os-pkgs 의 등급 출처를 세어보면 ubuntu 332건은 SeveritySource=ubuntu 인데
alpine 150건은 nvd 이거나 출처 없음이다. 즉 Alpine 이미지에서 "벤더" 열은
NVD 를 그대로 되비추는 값이라 비교 자체가 성립하지 않았다.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
카탈로그가 한 차트의 여러 버전을 들고 있어서, 게이트 요약표에 버전 정렬이 없으면
최신 버전과 구버전이 위험도 순으로 섞여 "지금 쓸 버전이 어느 정도인가" 를 한눈에
볼 수 없었다. 실측 사례 — argo-cd 10.4.0 이 구버전 7.8.11 사이에 끼어 표시됐다.
변경 전 (chart asc → 실효 C/H desc)
argo-cd@7.8.11 argocd:v2.14.5 14/101
argo-cd@7.8.11 redis:7.4.2 5/60
argo-cd@10.4.0 dex:v2.45.1 5/52 ← 최신이 구버전 사이에
argo-cd@7.8.11 dex:v2.42.0 4/55
argo-cd@10.4.0 argocd:v3.5.1 1/40
변경 후 (chart asc → 버전 desc → 실효 C/H desc)
argo-cd@10.4.0 dex:v2.45.1 5/52
argo-cd@10.4.0 argocd:v3.5.1 1/40
argo-cd@10.4.0 redis:8.6.4 0/0
argo-cd@7.8.11 argocd:v2.14.5 14/101
argo-cd@7.8.11 redis:7.4.2 5/60
argo-cd@7.8.11 dex:v2.42.0 4/55
- scripts/pipeline/cve-gate.py
- version_sort_key() 추가. 문자열 비교로는 "10.4.0" < "7.8.11" 이 되어 최신이
아래로 내려간다. 숫자 구간을 정수로 파싱한다.
프리릴리스는 코어와 분리해 다룬다 — 단순히 구간을 이어붙이면 리스트가 긴 쪽이
커져 "1.2.0-rc1" 이 "1.2.0" 보다 최신으로 정렬된다(단위 테스트로 실측).
semver 규칙대로 프리릴리스 없는 정식 릴리스를 더 크게 본다.
- 정렬은 안정 정렬을 3번 겹쳐 만든다. 버전만 내림차순이라 reverse=True 가
필요해 한 번에 튜플 하나로 묶을 수 없다.
- manifests/helm/argo-cd/7.7.0 제거
게이트 차단 574건 중 237건이 이 버전의 이미지 3개였다
(argocd:v2.13.0 117 / dex:v2.41.1 61 / redis:7.2.4-alpine 59 — 뒤 둘은 EOSL).
제거 후 차단 337건.
doc/catalog-stack-classification.md 의 버전 목록도 갱신했다(10.4.0 이 빠져 있어
이미 stale 이었다). doc/chart-restructure-plan.md 는 과거 이관 기록이라 두었다.
로컬 파이프라인으로 검증했다(extract → generate → scan → gate, argo-cd 범위).
이미지 6개 / SBOM 6개 성공 / 커버리지 전부 ok.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
render_summary_table() 을 이미지 알파벳순(리포트 파일명 순) 대신 차트 이름
오름차순으로 묶고, 같은 차트 안에서는 실효 CRITICAL/HIGH 내림차순으로 정렬한다.
scan-sbom.sh 의 trivy-summary.md 는 이미 차트별로 묶여 있는데 cve-gate.md/brief 는
아니어서 두 리포트의 정렬 기준이 서로 달랐다.
이 과정에서 report_stem_to_image() 와 같은 종류의 문제를 발견해 같이 고쳤다 —
이미지 하나가 여러 차트에 쓰이면 리포트 파일명→차트 매핑이 마지막 차트만 남겨,
차트별로 묶었을 때 다른 차트의 노출이 조용히 빠졌을 것이다(scan-sbom.sh 에서
실측으로 확인된 것과 동일한 패턴). 신규 charts_of_image() 로 sbom-index.tsv 를
전부 읽어 이미지→차트 목록을 보존하고, render_summary_table 표시에서만 이걸로
펼친다 — 게이트 판정(evaluate 등)에 쓰이는 대표 차트/버전은 안 건드린다.
로컬에서 (a) flowise/cnpg-cluster/etcd 3개 차트 실제 데이터로 정렬 확인
(b) 이미지 하나가 차트 두 개에 쓰이는 합성 케이스로 양쪽에 다 나오는지 확인.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
전체 카탈로그(45+ 차트) 스캔을 실제로 CI에서 돌려보니 GITHUB_STEP_SUMMARY 가
1MB 제한에 걸려 cve-gate.md(2.2MB) 업로드가 중단됐다 — CVE 개별 상세(차단
항목·벤더 하향 등급 등)가 이미지당 CVE 수에 비례해 불어나는 게 원인이다.
render_summary_table()로 차트·이미지별 건수 표를 뽑아 render_md(전체)와
render_brief_md(신규, Job Summary용) 양쪽에서 재사용한다. brief 는 건수 표 +
누락/커버리지 이상 건수만 담고, CVE 개별 상세는 아티팩트(cve-gate.md/json)를
보라고 안내한다 — 이미지 수에는 선형으로 늘지만 이미지당 CVE 수에는 무관해
카탈로그가 커져도 1MB 제한에 걸리지 않는다.
sbom.yml 의 Job Summary 스텝을 --brief-md 출력으로 교체했다.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>