본문으로 건너뛰기

v0.21.0 — 2026 SBOM 최소 요소, 아웃바운드 라이선스 충돌, 제대로 된 대상을 보는 스캔

기능 릴리스이면서 교정이 유난히 많은 릴리스입니다. 새 기능 두 가지(2026 SBOM 최소 요소, 아웃바운드 라이선스 충돌 판정)와 함께, 스캔이 성공으로 끝나면서 실제로는 요청한 것과 다른 대상을 설명하던 결함들을 고쳤습니다. 나머지를 건너뛰더라도 이 부분은 읽을 값이 있습니다. 다른 브랜치를 측정한 통과는 근거가 되지 않고, 아무것도 대조하지 못한 빈 취약점 목록도 근거가 되지 않습니다.

기계가 읽는 전체 변경 목록은 CHANGELOG.md에 있습니다.

업그레이드 전에 읽어 주세요

이미 가지고 있는 데이터의 표시가 달라지는 변경이 둘 있습니다.

컨테이너 인벤토리는 다음 스캔부터 생태계가 바뀝니다. 컨테이너 스캔은 이미지가 무엇이든 모든 패키지를 Alpine 패키지로 기록했습니다. Rocky 이미지의 rpm도 Debian 이미지의 deb도 pkg:apk/*로 남았습니다. 실패한 것은 없고 놓친 취약점도 없습니다. 개수는 맞고 식별자만 틀렸기 때문에 눈에 띄지 않았습니다. 기존에 저장된 행은 그대로 두며, rpm·deb 이미지를 다시 스캔하면 교정됩니다. 다만 그 재스캔은 해당 프로젝트의 취약점 판정을 초기화합니다. 분석가의 판정과 최초 발견 시각은 컴포넌트를 기준으로 이어지는데 교정된 패키지는 다른 컴포넌트이기 때문입니다. Alpine 이미지만 스캔해 온 배포는 영향이 없습니다. 업그레이드를 참고하세요.

이미 올린 SBOM의 취약점 수가 늘어날 수 있습니다. 공급자 SBOM이 운영체제를 적지 않은 채 OS 패키지만 담고 있었다면 지금까지는 아무것도 대조되지 않았습니다. 다시 업로드하면 취약점이 나옵니다. 실측한 사례에서는 패키지 다섯 개짜리 CentOS 문서가 0건에서 306건이 되었습니다.

주요 변경

모든 SBOM을 2026 최소 요소로 측정합니다

2026 SBOM 최소 요소(v2.1, 2026-07-29, CISA·NSA·FBI와 15개 국제 파트너 발행)가 2021년 NTIA 최소 요소를 대체합니다. 일부가 아니라 모든 소프트웨어에 적용되므로 이 기준선에는 조건을 두지 않았고, 모든 CycloneDX 문서를 측정합니다. 데이터 필드 17개와 관행 6개, 합해서 23개 항목입니다.

모든 항목은 권고이고 통과·실패 판정을 움직이지 않습니다. 의도한 설계이며 지침 자체를 따른 것입니다. 지침은 값을 적을 수 없을 때 "모른다"고 명시하는 것을 값 대신 받아들이는데, 항목을 필수로 올리면 정당하게 없을 수 있는 값 때문에 SBOM이 실패하게 됩니다. 여섯 관행 중 넷은 문서에 무엇이 담겼는지가 아니라 조직이 어떻게 일하는지를 다루므로, 점수 대신 무엇을 갖춰야 하는지 안내를 답니다.

규제 대응표의 미국 프레임워크도 이 요소들로 옮겼습니다. 롤업은 실패 수를 나머지로 추론하게 두지 않고 failed로 명시하며, 네 값의 합이 전체와 같습니다.

적합성 패널의 순서도 그에 맞춰 바꿨습니다. SBOM을 막는 것(필수 실패, 권고 부족, 사람이 봐야 하는 항목)을 첫머리에 두고 기준선마다 절을 나눴습니다. 권고 23줄이 필수 항목을 화면 아래로 밀어내지 않습니다. 부족한 항목에는 그것을 채울 CycloneDX 조각을 접이식 블록으로 보여 줍니다. 두 기준선 모두 해당합니다.

SBOM 적합성을 참고하세요.

배포 라이선스를 선언하면 의존성을 그에 비추어 판정합니다

라이선스 충돌은 아웃바운드 라이선스가 있어야 성립하므로 정책 축으로는 표현할 수 없었습니다. 정책은 AGPL-3.0을 금지한다고 말할 수는 있어도, 그것이 이 프로젝트가 배포하는 MIT 라이선스와 충돌한다고는 말하지 못합니다.

이제 프로젝트 설정에서 아웃바운드 라이선스를 선언합니다. 호환성 매트릭스가 스캔에 담긴 모든 라이선스를 그에 비추어 판정하고, Compliance 표에 판정 열과 필터, 요약이 생기며 근거는 드로어에서 볼 수 있습니다.

선언하지 않으면 판정하지 않습니다. 비어 있는 열은 문제없음이 아니며, 요약은 "평가하지 않음"과 "평가했고 없음"을 구분합니다.

마이그레이션 0050이 컬럼을 추가합니다.

수출한 SBOM이 누가 언제 무엇으로 만들었는지 밝힙니다

2026 최소 요소는 SBOM에 어느 생명주기 단계에서 만들었는지, 누가 만들었는지, 어떤 도구의 어느 버전으로 만들었는지, 빈 필드가 모르는 것인지 밝히지 않는 것인지를 적으라고 합니다. 넷 다 기록하지 않았고 도구 버전은 0.0.1 이라는 고정 문자열이었습니다.

이제 단계는 스캔 종류에서 따라옵니다. 요소 문서가 그은 구분을 그대로 씁니다. 소스 매니페스트는 빌드 이전, 빌드된 이미지는 빌드 이후입니다. 업로드받은 공급자 문서를 다시 수출할 때는 단계를 적지 않습니다. 남의 문서를 변환했다고 우리가 그 문서의 작성자가 되지는 않기 때문입니다. 작성자는 SBOM_AUTHOR로 선언하며, 설정하지 않으면 필드를 비웁니다. 요소는 충족하면서 아무것도 답하지 않는 자리표시자를 넣지 않습니다.

이 진술들은 TRUSCA가 만드는 두 문서 모두에 들어갑니다. 요청 시 다시 만드는 수출 문서와, 서명해서 내려받게 하는 스캔 문서입니다. 처음에는 수출 경로에만 들어가서 정작 소비자가 받는 SBOM이 아무 말도 하지 않는 상태였습니다.

알아 둘 만한 수정

풀 리퀘스트 스캔이 기준 브랜치를 설명했습니다

모든 소스 스캔이 git clone --depth 1로 원격의 기본 브랜치를 가져왔습니다. ref는 보관 키로만 함께 다녔습니다. 그래서 취약한 의존성을 추가한 PR이 통과하고, main의 Critical CVE가 아무 상관 없는 PR을 막았습니다. 브랜치 단위 판정이 엉뚱한 코드를 가리켰습니다.

이제 워커가 ref를 가져와 체크아웃합니다. 그 사이 사라진 ref는 기본 브랜치로 물러서고 metadata.ref_fallback을 남깁니다. 대체된 대상에서 나온 판정은 요청한 자리에서 나온 판정과 구분할 수 있어야 하기 때문입니다.

OS 패키지로 가득한 공급자 SBOM이 아무것도 대조하지 못했습니다

Trivy는 어느 배포판 어드바이저리 데이터베이스를 쓸지 문서 안의 operating-system 컴포넌트를 보고 정합니다. 패키지 PURL이 아닙니다. 그래서 이미지에 설치된 rpm을 하나도 빠짐없이 담고 PURL도 모두 올바른 SBOM이 취약점 0건을 보고했습니다.

이제 패키지들에서 배포판을 추론해 그 컴포넌트를 넣은 사본으로 스캔합니다. 업로드한 문서는 고치지 않습니다. 적합성 판정과 서명 번들의 근거이기 때문입니다. 인식하지 못하는 배포판은 판단 근거로 세지 않으므로, 판정할 수 없는 문서는 추측하지 않고 받은 그대로 스캔합니다.

그 뒤에 결함이 하나 더 있었습니다. Trivy는 OS 패키지 결과에 배포판 이름을 붙이는데 어떤 PURL 재구성도 그것을 매핑하지 못해서, 이미 대조에 성공한 취약점이 데이터베이스에 닿기 전에 버려지고 있었습니다. 이제 Trivy가 각 항목에 붙여 주는 PURL로 이어집니다.

CI에서 컨테이너 스캔을 아예 실행할 수 없었습니다

워커가 요구하는 image_ref를 어느 CI 클라이언트도 보내지 못해서, CI가 띄운 컨테이너 스캔은 대기열에 들어가 워커를 기다리다 실패했습니다. 이제 액션과 GitLab 템플릿이 이미지를 받고, 빠졌을 때는 요청 시점에 분명한 메시지로 거절합니다.

같은 ref에 두 번째 CI 실행이 빌드를 실패시켰습니다

포털은 (프로젝트, ref)당 활성 스캔 하나만 허용하고 두 번째 요청에 409를 돌려주는데, 세 CI 클라이언트가 이것을 치명적 오류로 다뤘습니다. 그래서 GitHub이 권장하는 cancel-in-progress 워크플로가 스스로를 망가뜨렸습니다. 러너는 죽지만 그 스캔은 서버에서 계속 돌고, 뒤이은 실행은 아무도 보고 있지 않은 스캔과 충돌합니다. 이제 409가 ref를 쥔 스캔을 알려 주고 클라이언트가 거기에 붙습니다. 폴링은 일시적 오류를 견디므로 거의 끝난 스캔을 버리지 않고, 429는 Retry-After를 따릅니다.

스캔되지 않은 푸시를 처리된 것처럼 기록했습니다

ref에 이미 실행 중인 스캔이 있어 건너뛴 전달을 duplicate로 보고했습니다. 이 API가 재전송된 전달을 가리킬 때 쓰는 말입니다. 그래서 운영자가 SCM의 전달 로그를 보면 아무것도 스캔하지 않은 커밋이 처리된 것으로 보였습니다. 이제 skipped_active_scan이라고 적습니다. 그 경로는 500을 돌려주기도 해서 Git 호스트가 재시도했습니다.

풀 리퀘스트 이벤트에는 최상위 ref가 없어 웹훅이 띄운 PR 스캔은 ref 없이 저장되어 아무 것과도 묶이지 않았습니다. 이제 CI 클라이언트의 스캔과 같이 pr-N · mr-N으로 키를 붙입니다. 수신부는 전달을 기록하기 전에 용량 제한을 확인하므로 팀 동시 실행 제한과 디스크 제한을 더는 우회하지 않습니다.

중첩 컴포넌트가 보이지 않았습니다

CycloneDX는 컴포넌트가 자기 components 배열을 갖게 허용합니다. 함께 담긴 의존성이나 이미지 레이어의 패키지가 그렇습니다. 최상위만 읽고 있었고, 세 곳이 각각 따로 보면 맞아 보였습니다. 적재는 중첩된 항목을 버렸고, 적합성은 그것들이 빠진 분모로 채점했으며, 중첩 컴포넌트에 걸린 Trivy 취약점은 PURL로 조회했을 때 저장된 적 없는 행을 찾다가 아무 기록 없이 사라졌습니다.

그 밖의 수정

  • 파일 항목을 패키지 필드의 분모에 넣고 있었습니다. 파일에는 패키지 버전도 PURL 타입도 없으므로, 담을 수 없는 값이 없다고 문서를 깎아내렸습니다. 이제 파일에는 파일이 담을 수 있는 식별자를 묻습니다.
  • 모든 SBOM이 존재하지 않는 릴리스를 주장했습니다. 빌더 버전 기본값이 2.3.0-dev 였고 TRUSTEDOSS_VERSION을 설정하는 배포가 없었습니다. 릴리스 이미지는 빌드 시점에 태그를 넣고 기본값은 unknown이 되었습니다.
  • 규제 대응표의 면책 문구가 다른 제품 이름을 적고 있었습니다. 데이터를 가져온 자매 프로젝트의 이름이었고, 그것을 보여 주는 제품의 이름이 아니었습니다. 이제 두 언어 모두 TRUSCA로 나옵니다.
  • CI 가이드가 없는 것을 약속하고 있었습니다. 존재하지 않는 함수를 불러오는 웹훅 시크릿 예제, 실패한 스캔을 예전 스캔과 견주던 Jenkins 빠른 시작, 존재하지 않는 포털 화면과 API 키 범위를 가리키던 가이드 세 곳입니다.

업그레이드

표준 업그레이드 절차를 따르세요. 마이그레이션 0050은 이 프로젝트의 모든 마이그레이션이 그렇듯 forward-only입니다.

업그레이드 후 계획해 둘 일이 둘 있습니다.

  1. 인벤토리를 신뢰하고 쓰는 rpm·deb 기반 컨테이너 이미지를 다시 스캔하세요. 그 프로젝트의 취약점 판정이 초기화된다는 점을 감안해야 합니다.
  2. OS 패키지를 담은 공급자 SBOM을 다시 업로드하세요. 지금까지 아무것도 대조되지 않았을 수 있습니다.