Пиков / КАПО
Как читать авторский курс и нормативные материалы

Это учебные материалы, а не заключение о соответствии конкретного продукта. Организационные модели курса отделяйте от терминов стандарта: ППК в опубликованном проекте — машиночитаемый перечень, а живой реестр его версий является практикой сопровождения. Термин OSA в разных материалах имеет разный охват. Статус и обязательность нормативного документа проверяются отдельно; проект КАПО опубликован как проект первой редакции.

П38. Честный SBOM контейнерного образа

Цель

Сгенерировать SBOM одного и того же образа двумя независимыми генераторами (Syft и Trivy), сравнить число компонентов и доказать, что «честность» SBOM измеряется не количеством строк, а источником факта для каждого компонента. Проверяемый результат — таблица расхождений + объяснение, откуда взялся каждый «лишний» или «пропущенный» пакет.

Что значит «нечестный» SBOM образа SBOM по образу опирается на следы: базы пакетных менеджеров (/var/lib/dpkg/status, /lib/apk/db/installed, /var/lib/rpm), lockfile'ы языков и метаданные бинарей. Если ПО положено в образ COPY-ом мимо пакетного менеджера, метаданные стёрты (mint --obfuscate-metadata) или бинарь упакован UPX — генератор не видит компонент, и SBOM покажет «0 уязвимостей» при дырявом образе. Честный SBOM явно перечисляет, какие источники фактов использованы и где их нет.

Команда или действие

Возьмите реальный публичный образ среднего размера и снимите два SBOM в одном формате (CycloneDX JSON), чтобы их можно было сравнивать:

IMG=ghcr.io/benc-uk/nodejs-demoapp:latest

# 1. SBOM генератором Syft
syft "$IMG" -o cyclonedx-json=syft.cdx.json

# 2. SBOM генератором Trivy
trivy image --format cyclonedx --output trivy.cdx.json "$IMG"

# 3. сколько компонентов нашёл каждый
echo "syft : $(jq '.components | length' syft.cdx.json)"
echo "trivy: $(jq '.components | length' trivy.cdx.json)"

# 4. множества PURL и их разница (что нашёл один и не нашёл другой)
jq -r '.components[].purl' syft.cdx.json  | sort -u > syft.purls
jq -r '.components[].purl' trivy.cdx.json | sort -u > trivy.purls
echo "--- только в syft ---";  comm -23 syft.purls trivy.purls
echo "--- только в trivy ---"; comm -13 syft.purls trivy.purls

# 5. проверка «честности»: сколько компонентов БЕЗ purl (висячая идентичность)
jq '[.components[] | select(.purl == null)] | length' syft.cdx.json

Для CI-гейта по качеству SBOM (а не только по severity) добавьте порог на долю компонентов без PURL:

TOTAL=$(jq '.components | length' syft.cdx.json)
NOPURL=$(jq '[.components[] | select(.purl == null)] | length' syft.cdx.json)
awk -v t="$TOTAL" -v n="$NOPURL" 'BEGIN{ r=n/t; printf "no-purl ratio: %.2f\n", r; exit (r>0.10)}'
# код возврата 1, если больше 10% компонентов без PURL — пайплайн падает

Что вы должны увидеть

Числа у двух генераторов различаются — это норма, а не ошибка. Для node-демо ориентир — десятки компонентов (apk-пакеты базового Alpine + npm-зависимости приложения), и наборы PURL пересекаются не полностью:

syft : 213
trivy: 198
--- только в syft ---
pkg:npm/path-to-regexp@0.1.10
pkg:apk/alpine/libcrypto3@3.3.2-r0?...
--- только в trivy ---
pkg:apk/alpine/.python-rundeps@...

Скелет компонента в CycloneDX, по которому видно источник факта (поле purl + properties):

{
  "type": "library",
  "name": "axios",
  "version": "1.7.7",
  "purl": "pkg:npm/axios@1.7.7",
  "properties": [
    { "name": "syft:package:foundBy", "value": "javascript-package-cataloger" },
    { "name": "syft:location:0:path", "value": "/app/node_modules/axios/package.json" }
  ]
}

CI-проверка печатает долю компонентов без PURL и завершается кодом 0, если она ≤ 10 %:

no-purl ratio: 0.03

Если не получилось

Критерий зачёта

Сдаёте: syft.cdx.json, trivy.cdx.json, файл с разницей PURL и письменный разбор 3–5 расхождений (для каждого — почему один генератор нашёл компонент, а другой нет: разный каталогизатор, версия БД, обработка бинарей). Плюс рабочий CI-фрагмент с порогом по доле компонентов без PURL и демонстрация того, что он валит сборку на искусственно «ослеплённом» SBOM.

Два генератора — это фича, а не баг На приёмке полезно держать кросс-проверку: расхождение наборов PURL подсвечивает компоненты со слабой идентичностью и зоны, где одному инструменту не хватает каталогизатора. Это и есть переход от «у нас есть SBOM» к «у нас есть честный SBOM».

Нормативная связка