목차
- Trivy False Positive, 왜 이렇게 피곤할까요
- Trivy False Positive를 줄이기 위한 VEX CSAF 이해
- 옵션부터 정리해보겠습니다: 무엇을 언제 쓰면 되나
- 실전 1단계: 기준 스캔 결과를 먼저 저장합니다
- 실전 2단계: VEX CSAF 문서로 오탐 근거를 남깁니다
- 실전 3단계: .trivyignore.yaml과 상태 필터를 같이 최적화합니다
- Trivy False Positive 트러블슈팅
- 1. VEX를 넣었는데 결과가 안 줄어드는 경우
- 2. 너무 넓게 suppress 되는 경우
- 3. ignore 파일과 VEX가 섞여서 이유를 모르겠는 경우
- 4. unfixed를 너무 믿는 경우
- 검증은 이렇게 보시면 됩니다
- 운영 추천안: 이런 경우엔 이렇게 가시면 됩니다
- FAQ처럼 자주 받는 질문 몇 가지
- VEX만 쓰면 ignore 파일은 필요 없나요
- CSAF와 OpenVEX 중 무엇을 먼저 볼까요
- Trivy False Positive를 완전히 없앨 수 있나요
본 글은 보안 학습과 본인이 관리하는 시스템의 방어를 위한 교육 목적입니다. 타인의 시스템에 대한 무단 접근·공격은 불법이며 법적 처벌 대상입니다. 오늘 내용도 어디까지나 제가 관리하는 이미지와 파이프라인에서 Trivy False Positive와 취약점 오탐을 줄이기 위해 정리한 방식입니다.
Trivy False Positive, 왜 이렇게 피곤할까요
CI에 Trivy를 붙여두면 초반에는 꽤 속이 시원합니다. 그런데 조금 지나면 다른 문제가 생기더라고요. 실제 서비스 경로에서는 쓰이지 않는 라이브러리인데도 계속 빨간 줄이 뜨고, 배포판 패치가 아직 없는 이슈까지 매번 반복해서 올라옵니다. 바로 이런 지점에서 Trivy False Positive 대응 기준이 필요합니다.
저도 처음에는 .trivyignore에 CVE를 바로 넣어버렸습니다. 그런데 나중에 보니 이 방식은 너무 거칠더라고요. 왜 무시했는지 근거가 남지 않고, 이미지 태그가 바뀌어도 예외가 그대로 살아남는 경우가 있었습니다. 실제 운영에서는 VEX(Vulnerability Exploitability eXchange)와 .trivyignore.yaml을 역할별로 나눠 쓰는 쪽이 훨씬 관리하기 편했습니다.

Trivy 스캔 결과가 바로 차단으로 이어지지 않고, VEX CSAF와 ignore 정책을 거쳐 검증된 이슈만 남는 흐름을 보여주는 이미지입니다.
Trivy False Positive를 줄이기 위한 VEX CSAF 이해
쉽게 말해 VEX는 "이 취약점이 존재하는 건 맞지만, 우리 제품이나 패키지 조합에서는 실제 영향이 없다"를 표준 형식으로 설명하는 문서입니다. 그냥 메모가 아니라 스캐너가 읽을 수 있는 구조화된 예외죠.
Trivy는 로컬 VEX 파일로 CycloneDX, OpenVEX, CSAF를 지원합니다. 이 중 CSAF(Common Security Advisory Framework)는 SBOM 형식에 덜 종속적인 편이라 컨테이너 이미지, 파일시스템, 저장소, Kubernetes, SBOM 대상에 두루 적용하기 좋습니다. 여러 팀이 함께 보는 환경에서는 CSAF가 설명력도 좋고, 감사 대응 때도 근거를 정리하기 수월했습니다.
여기서 중요한 포인트가 하나 있습니다. VEX는 단순 숨김이 아니라 왜 영향이 없는지를 기록하는 레이어라는 점입니다. 반대로 --ignore-unfixed나 --ignore-status는 결과를 빠르게 줄이는 운영 옵션에 가깝습니다. 둘을 같은 개념으로 다루면 나중에 기준이 꼬이기 쉽습니다.
옵션부터 정리해보겠습니다: 무엇을 언제 쓰면 되나
| 방법 | 적합한 상황 | 장점 | 주의점 |
|---|---|---|---|
--ignore-unfixed |
배포판 패치가 아직 없는 이슈를 우선 제외할 때 | 가장 빠르게 노이즈를 줄임 | 영향도 근거까지 대신해주지는 않습니다 |
--ignore-status |
under_investigation, will_not_fix, fix_deferred 같은 상태 기반 필터가 필요할 때 |
공급자 메타데이터를 운영 정책에 반영하기 쉬움 | 패키지별 상세 설명은 약합니다 |
.trivyignore.yaml |
특정 CVE를 경로나 PURL 단위로 제한해서 무시할 때 | 만료일, statement 기록이 가능 | 실험적 기능이라 --ignorefile를 명시하는 편이 안전합니다 |
--vex + CSAF |
영향 없음의 근거를 문서화해야 할 때 | 감사 대응과 재사용에 유리함 | 문서 구조를 이해해야 하고 실험적 기능입니다 |
제가 권장하는 순서는 이렇습니다. 1) unfixed 정리, 2) 상태 기반 필터, 3) 그래도 남는 케이스를 VEX CSAF로 설명, 4) 정말 로컬 예외만 필요한 건 .trivyignore.yaml. 이 순서를 지켜야 정책이 덜 엉킵니다.
실전 1단계: 기준 스캔 결과를 먼저 저장합니다
처음부터 예외부터 만들면 안 됩니다. 기준 보고서가 있어야 나중에 무엇이 줄었는지, 무엇이 실수로 사라졌는지 비교할 수 있거든요. 저는 보통 JSON과 테이블 출력을 둘 다 남깁니다.
- 대상 이미지를 취약점 스캐너만으로 먼저 스캔합니다.
- JSON 결과를 저장합니다.
- 사람이 확인하기 좋은 테이블 결과도 따로 봅니다.
trivy image --scanners vuln --format json --output result.json debian:11.8
trivy image --scanners vuln --severity HIGH,CRITICAL debian:11.8
여기서 판단 기준은 단순 개수보다 같은 CVE가 어떤 패키지와 어떤 상태로 반복되는지입니다. 패치 버전이 비어 있고 배포판 공급자 이슈만 남아 있는 경우는 --ignore-unfixed 후보일 수 있고, 특정 패키지가 실제 실행 경로에 없으면 VEX 후보가 됩니다.
제가 홈랩에서 자주 헷갈렸던 건 "발견됨"과 "영향 있음"을 같은 뜻으로 보는 부분이었습니다. 스캐너는 존재 여부를 잘 찾지만, 실행 경로나 제품 조합까지 자동으로 다 판단해주지는 않더라고요. 이 차이를 구분해야 보안 스캔 최적화가 제대로 됩니다.
실전 2단계: VEX CSAF 문서로 오탐 근거를 남깁니다
Trivy는 --vex /path/to/file 형식으로 로컬 VEX 파일을 읽습니다. CSAF에서는 product_tree에 패키지 식별자(PURL)를 두고, vulnerabilities 아래에서 어떤 제품이 known_not_affected인지 지정하면 됩니다.
아래 예시는 Trivy 공식 문서 흐름을 바탕으로, Debian 패키지 하나를 영향 없음(not affected)으로 설명하는 구조입니다.
{
"document": {
"category": "csaf_vex",
"csaf_version": "2.0",
"publisher": {
"category": "vendor",
"name": "Example Company ProductCERT",
"namespace": "https://psirt.example.com"
},
"title": "Example VEX document",
"tracking": {
"id": "2024-EVD-UC-01-A-001",
"status": "final",
"version": "1",
"initial_release_date": "2024-01-01T11:00:00.000Z",
"current_release_date": "2024-01-01T11:00:00.000Z",
"revision_history": [
{
"number": "1",
"date": "2024-01-01T11:00:00.000Z",
"summary": "Initial version."
}
]
}
},
"product_tree": {
"branches": [
{
"category": "vendor",
"name": "Debian",
"branches": [
{
"category": "product_name",
"name": "Database Libraries",
"branches": [
{
"category": "product_version",
"name": "5.3",
"product": {
"name": "Database Libraries 5.3",
"product_id": "LIBDB-5328",
"product_identification_helper": {
"purl": "pkg:deb/debian/[email protected]%2Bdfsg1-0.8?arch=amd64&distro=debian-11.8"
}
}
}
]
}
]
}
]
},
"vulnerabilities": [
{
"cve": "CVE-2019-8457",
"product_status": {
"known_not_affected": [
"LIBDB-5328"
]
},
"threats": [
{
"category": "impact",
"details": "Vulnerable code not in execute path.",
"product_ids": [
"LIBDB-5328"
]
}
]
}
]
}
실제로 써보면 핵심은 두 가지입니다. PURL 매칭이 정확해야 하고, 영향 없음의 이유를 threats/details에 남겨야 나중에 팀원들이 봐도 납득이 됩니다. 그냥 CVE를 가리는 문서가 아니어야 하거든요.

CSAF VEX에서 product_tree와 vulnerabilities가 어떻게 연결되는지, product_id와 PURL이 어떤 역할을 하는지 설명하는 이미지입니다.
문서를 만들었다면 스캔에 바로 붙여봅니다.
trivy image debian:11.8 --vex ./debian11.vex.csaf
trivy image debian:11.8 --vex ./debian11.vex.csaf --show-suppressed
--show-suppressed를 같이 쓰면 사라진 항목과 적용된 source를 확인하기 편합니다. 이 단계에서 CVE가 없어졌다고 바로 안심하지 마시고, 왜 suppress 되었는지를 꼭 보셔야 합니다. 저도 예전에 PURL 범위를 너무 넓게 잡아서 다른 버전까지 같이 가려버린 적이 있었는데, 그때 이 옵션이 꽤 도움이 됐습니다.
실전 3단계: .trivyignore.yaml과 상태 필터를 같이 최적화합니다
VEX만으로 모든 노이즈를 처리하려고 하면 오히려 관리가 무거워집니다. 반대로 모든 걸 ignore 파일에 몰아넣으면 근거가 약해지고요. 그래서 저는 용도를 분리합니다.
- VEX CSAF: 영향 없음의 근거가 있는 취약점
- .trivyignore.yaml: 특정 경로나 패키지에만 한정한 임시 예외
- –ignore-unfixed: 배포판 패치가 아직 없는 이슈를 줄이는 운영 옵션
- –ignore-status: 상태가 명확한 공급자 메타데이터 기반 정리
예를 들어 임시 예외가 필요하면 이렇게 갑니다.
vulnerabilities:
- id: CVE-2022-40897
paths:
- "usr/local/lib/python3.9/site-packages/setuptools-58.1.0.dist-info/METADATA"
statement: "Accept the risk during image transition"
- id: CVE-2023-3817
purls:
- "pkg:deb/debian/libssl1.1"
- id: CVE-2023-29491
expired_at: 2026-09-30
statement: "Temporary exception until base image refresh"
그리고 스캔은 이렇게 묶습니다.
trivy image \
--ignore-unfixed \
--ignore-status under_investigation,will_not_fix,fix_deferred \
--ignorefile ./.trivyignore.yaml \
--vex ./debian11.vex.csaf \
--show-suppressed \
debian:11.8
여기서 중요한 포인트가 있습니다. .trivyignore.yaml은 실험적 기능이라 명시적으로 --ignorefile를 붙이는 습관이 좋습니다. 그리고 expired_at을 꼭 넣어두세요. 안 그러면 예외가 영구화되기 쉽습니다. 이런 게 나중에 제일 무섭더라고요.
Trivy False Positive 트러블슈팅
1. VEX를 넣었는데 결과가 안 줄어드는 경우
대부분은 PURL 불일치입니다. 패키지 버전, distro qualifier, arch qualifier가 맞는지 다시 보셔야 합니다. 특히 Debian/Ubuntu 계열은 qualifier가 빠지면 다른 패키지 변형과 매칭이 달라질 수 있습니다.
2. 너무 넓게 suppress 되는 경우
PURL에서 버전을 뺀 예외는 여러 버전에 넓게 적용될 수 있습니다. 편할 때도 있지만 운영 환경에서는 위험할 때가 더 많습니다. 저는 배포 이미지에는 가능하면 버전까지 넣는 편입니다.
3. ignore 파일과 VEX가 섞여서 이유를 모르겠는 경우
--show-suppressed를 켜면 어느 소스에서 suppress 되었는지 확인하기 쉬워집니다. 테이블 출력의 source를 먼저 보고, JSON 보고서를 쓰는 파이프라인이면 수정된 findings도 같이 보관하는 편이 좋습니다.
4. unfixed를 너무 믿는 경우
--ignore-unfixed는 노이즈를 줄이는 데는 좋지만, 리스크가 사라진다는 뜻은 아닙니다. 배포판 패치가 아직 없을 뿐이고, 실제 영향 분석은 여전히 필요합니다. 특히 외부 공개 서비스용 이미지는 여기서 한 번 더 눈으로 확인하는 게 안전합니다.

스캔 결과에서 suppressed vulnerabilities와 source 정보가 보여서 어떤 규칙이 적용됐는지 검증하는 장면의 이미지입니다.
검증은 이렇게 보시면 됩니다
완성된 결과를 볼 때는 총 개수만 보면 안 됩니다. 저는 아래 순서로 읽습니다.
- 기준 보고서 대비 어떤 CVE가 빠졌는지 확인합니다.
- 빠진 이유가 VEX인지 ignorefile인지 구분합니다.
- 남아 있는 HIGH/CRITICAL 중 실제 조치 대상만 추립니다.
- 예외 문서의 만료일과 변경 이력을 같이 봅니다.
판단 기준도 명확해야 합니다. 같은 CVE가 결과에서 사라졌다고 끝내면 안 되고, suppressed source가 기대한 정책인지를 확인해야 합니다. VEX로 처리하려던 항목이 ignorefile에서 먼저 가려졌다면 정책 계층이 뒤집힌 상태거든요.
제가 직접 해보니 가장 안정적인 검증 방식은 기준 JSON 보고서 + suppress 확인용 테이블 출력을 함께 남기는 방식이었습니다. 사람은 테이블로 읽고, 파이프라인 비교는 JSON으로 하는 거죠. 이거 진짜 편하더라고요.
운영 추천안: 이런 경우엔 이렇게 가시면 됩니다

오탐 성격에 따라 어떤 옵션을 먼저 선택해야 하는지 한눈에 보여주는 요약 이미지입니다.
- 배포판 패치가 아직 없는 경우:
--ignore-unfixed를 먼저 적용하고, 주기적으로 재검토하세요. - 공급자 상태 정보로 충분한 경우:
--ignore-status로 운영 정책을 단순화하세요. - 영향 없음의 근거를 남겨야 하는 경우: VEX CSAF로 문서화하세요. 감사 대응이나 팀 간 공유에 특히 강합니다.
- 특정 파일 경로나 패키지에만 임시 예외가 필요한 경우:
.trivyignore.yaml에expired_at과statement를 꼭 넣으세요.
제 추천은 분명합니다. 조직 차원의 표준 예외는 VEX CSAF, 팀 로컬의 짧은 유예는 .trivyignore.yaml입니다. 여기에 --ignore-unfixed와 --ignore-status를 운영 보조 수단으로 얹는 방식이 가장 덜 망가집니다.
다음 글에서는 Trivy 결과를 CI에서 fail-open / fail-close로 나누는 기준도 다뤄보겠습니다. 이전 글에서 다뤘던 컨테이너 보안 글과 함께 보면 더 잘 연결됩니다.
FAQ처럼 자주 받는 질문 몇 가지
VEX만 쓰면 ignore 파일은 필요 없나요
아닙니다. VEX는 설명 가능한 예외에 강하고, ignore 파일은 로컬 임시 예외에 강합니다. 둘의 목적이 다릅니다.
CSAF와 OpenVEX 중 무엇을 먼저 볼까요
팀이 작고 단순하면 OpenVEX도 좋습니다. 다만 여러 대상과 제품 구조를 설명해야 하면 CSAF가 더 잘 맞는 경우가 많습니다.
Trivy False Positive를 완전히 없앨 수 있나요
완전히 없애는 건 어렵습니다. 대신 오탐을 문서화 가능한 예외와 정말 처리해야 할 취약점으로 나누는 건 충분히 가능합니다. 그 차이가 운영 품질을 바꿉니다.
본 글은 보안 학습과 본인이 관리하는 시스템의 방어를 위한 교육 목적입니다. 실제 운영 환경에서는 변경 이력과 승인 절차를 함께 관리하시길 권합니다.