
Композиционный анализ (Software Composition Analysis, SCA) – это практика автоматического выявления и отслеживания внешних зависимостей проекта с целью мониторинга их известных уязвимостей.
SCA – это неотъемлемая часть процесса безопасной разработки программного обеспечения (РБПО, или SSDLC – Secure Software Development Lifecycle).
Регулярный анализ уязвимостей для нас является крайне актуальной темой. Мало того, что наша компания непосредственно связана с информационной безопасностью, а следовательно нам бы хотелось предлагать безопасные продукты, так и бонусом в теории мы ускоряем процесс сертификации нашего ПО во ФСТЭК России.
Тем не менее, мы не нашли ни одной статьи про внедрение компонентного анализа в проекты на C++. А ведь когда дело доходит до «плюсов», любая тема получает x3 к сложности.
Дело в том, что в экосистеме C++ нет центральных репозиториев пакетов, в отличие от Java (Maven Central) или Python (PyPI), в которых у пакетов правильно и полноценно прописаны все метаданные: название, версия, транзитивности.
В этой статье мы расскажем, какие инструменты используем и как организуем пайплайн для своевременного и регулярного анализа зависимостей.
P.S. В написании текста статьи не участвовала нейросеть! 100% ручной труд разработчика Георгия ?
Что такое Software Bills Of Materials
Software Bill of Materials (SBOM) – это структурированный список сторонних компонентов, библиотек и зависимостей, входящих в программный продукт. На сегодняшний день в индустрии сложилось два основных отраслевых формата: SPDX и CycloneDX.
SPDX (Software Package Data Exchange) – стандарт под эгидой Linux Foundation, исторически ориентированный на юридическую чистоту и лицензионную совместимость. SPDX предоставляет богатые возможности для описания сложных лицензионных выражений, информации об авторских правах и атрибуции на уровне файлов. Его основная сила в compliance-задачах, аудитах и подготовке отчетности для юридических отделов. Однако для автоматического отслеживания уязвимостей SPDX подходит хуже: он менее удобен для интеграции с SCA-инструментами и имеет более слабую модель описания зависимостей.
CycloneDX – стандарт, разработанный OWASP Foundation с фокусом на безопасность и управление уязвимостями. Есть три главных плюса в отличие от SPDX:
Детальная идентификация компонентов через Package URL (PURL) и Common Platform Enumeration (CPE). Пока опишу кратко, что PURL – это стандартизированный способ однозначно идентифицировать и находить пакет, а CPE используется для матчинга с базами уязвимостей (вроде NVD). Наличие обоих идентификаторов в SBOM значительно повышает точность обнаружения CVE.
Поддержка деревьев зависимостей и отношений между компонентами, что критично для C++ со сложными транзитивными связями.
В основном инструменты, связанные с мониторингом уязвимостей, принимают на вход именно CycloneDX.
Пример CycloneDX файла, созданного с помощью syft в контейнере с Astra Linux 1.7
{ "$schema": "http://cyclonedx.org/schema/bom-1.6.schema.json", "bomFormat": "CycloneDX", "specVersion": "1.6", "serialNumber": "urn:uuid:5df52509-341d-495d-89b3-d3e9753cef41", "version": 1, "metadata": { "timestamp": "2025-08-28T17:15:03+03:00", "tools": { "components": [ { "type": "application", "author": "anchore", "name": "syft", "version": "1.32.0" } ] }, "component": { "bom-ref": "9d35b16d74c72ad0", "type": "container", "name": "test_syft", "version": "sha256:098d3fdf99920a076f53ee809c39a95e83473b42259e36b86edf8924a7be776b" }, "properties": [ { "name": "syft:image:labels:ru.rutoken.ci.gitlab_project_id", "value": "1034" } ] }, "components": [ { "bom-ref": "pkg:deb/astra/grdcontrol@4.4.0?arch=amd64&distro=astra-1.7_x86-64&package-id=5b8d0051eb5a2636", "type": "library", "publisher": "Aktiv Co. <hotline@guardant.ru>", "name": "grdcontrol", "version": "4.4.0", "cpe": "cpe:2.3:a:grdcontrol:grdcontrol:4.4.0:*:*:*:*:*:*:*", "purl": "pkg:deb/astra/grdcontrol@4.4.0?arch=amd64&distro=astra-1.7_x86-64" }, { "bom-ref": "pkg:deb/astra/libaudit1@1%3A2.8.5astra3%2Bci4?arch=amd64&distro=astra-1.7_x86-64&package-id=ec8648962195b247&upstream=audit", "type": "library", "publisher": "Ubuntu Developers <ubuntu-devel-discuss@lists.ubuntu.com>", "name": "libaudit1", "version": "1:2.8.5astra3+ci4", "licenses": [ { "license": { "id": "GPL-1.0-only" } }, { "license": { "id": "GPL-2.0-only" } }, { "license": { "id": "LGPL-2.1-only" } } ], "cpe": "cpe:2.3:a:libaudit1:libaudit1:1\\:2.8.5astra3\\+ci4:*:*:*:*:*:*:*", "purl": "pkg:deb/astra/libaudit1@1%3A2.8.5astra3%2Bci4?arch=amd64&distro=astra-1.7_x86-64&upstream=audit" }, { "bom-ref": "pkg:deb/astra/libc6@2.28-10%2Bdeb10u4%2Bci202407161455%2Bastra12?arch=amd64&distro=astra-1.7_x86-64&package-id=5c6f0772bc436927&upstream=glibc", "type": "library", "publisher": "GNU Libc Maintainers <debian-glibc@lists.debian.org>", "name": "libc6", "version": "2.28-10+deb10u4+ci202407161455+astra12", "licenses": [ { "license": { "id": "GPL-2.0-only" } }, { "license": { "id": "LGPL-2.1-only" } } ], "cpe": "cpe:2.3:a:libc6:libc6:2.28-10\\+deb10u4\\+ci202407161455\\+astra12:*:*:*:*:*:*:*", "purl": "pkg:deb/astra/libc6@2.28-10%2Bdeb10u4%2Bci202407161455%2Bastra12?arch=amd64&distro=astra-1.7_x86-64&upstream=glibc" }, { "bom-ref": "pkg:deb/astra/libccid@1.5.2-1?arch=amd64&distro=astra-1.7_x86-64&package-id=686f8e47cda607fb&upstream=ccid", "type": "library", "publisher": "Ludovic Rousseau <rousseau@debian.org>", "name": "libccid", "version": "1.5.2-1", "licenses": [ { "license": { "id": "LGPL-2.1-only" } } ], "cpe": "cpe:2.3:a:libccid:libccid:1.5.2-1:*:*:*:*:*:*:*", "purl": "pkg:deb/astra/libccid@1.5.2-1?arch=amd64&distro=astra-1.7_x86-64&upstream=ccid" }, { "bom-ref": "pkg:deb/astra/libpam0g@1.3.1-5.astra.se16?arch=amd64&distro=astra-1.7_x86-64&package-id=4dd08cc62f7ca378&upstream=pam", "type": "library", "publisher": "Steve Langasek <vorlon@debian.org>", "name": "libpam0g", "version": "1.3.1-5.astra.se16", "licenses": [ { "license": { "name": "GPL" } } ], "cpe": "cpe:2.3:a:libpam0g:libpam0g:1.3.1-5.astra.se16:*:*:*:*:*:*:*", "purl": "pkg:deb/astra/libpam0g@1.3.1-5.astra.se16?arch=amd64&distro=astra-1.7_x86-64&upstream=pam" }, { "bom-ref": "pkg:deb/astra/libpcsclite1@1.8.24-1%2Bb2?arch=amd64&distro=astra-1.7_x86-64&package-id=3df3ba37584b8028&upstream=pcsc-lite%401.8.24-1", "type": "library", "publisher": "Ludovic Rousseau <rousseau@debian.org>", "name": "libpcsclite1", "version": "1.8.24-1+b2", "licenses": [ { "license": { "id": "BSD-3-Clause" } }, { "license": { "id": "GPL-3.0-only" } }, { "license": { "id": "GPL-3.0-or-later" } }, { "license": { "id": "ISC" } }, { "license": { "name": "Expat" } } ], "cpe": "cpe:2.3:a:libpcsclite1:libpcsclite1:1.8.24-1\\+b2:*:*:*:*:*:*:*", "purl": "pkg:deb/astra/libpcsclite1@1.8.24-1%2Bb2?arch=amd64&distro=astra-1.7_x86-64&upstream=pcsc-lite%401.8.24-1" }, { "bom-ref": "pkg:deb/astra/libudev1@241-7~deb10u8astra.se34%2Bci1?arch=amd64&distro=astra-1.7_x86-64&package-id=996aced19957f5fa&upstream=systemd", "type": "library", "publisher": "Debian systemd Maintainers <pkg-systemd-maintainers@lists.alioth.debian.org>", "name": "libudev1", "version": "241-7~deb10u8astra.se34+ci1", "licenses": [ { "license": { "id": "CC0-1.0" } }, { "license": { "id": "GPL-2.0-only" } }, { "license": { "id": "GPL-2.0-or-later" } }, { "license": { "id": "LGPL-2.1-only" } }, { "license": { "id": "LGPL-2.1-or-later" } }, { "license": { "name": "Expat" } }, { "license": { "name": "public-domain" } } ], "cpe": "cpe:2.3:a:libudev1:libudev1:241-7\\~deb10u8astra.se34\\+ci1:*:*:*:*:*:*:*", "purl": "pkg:deb/astra/libudev1@241-7~deb10u8astra.se34%2Bci1?arch=amd64&distro=astra-1.7_x86-64&upstream=systemd" }, { "bom-ref": "pkg:deb/astra/libusb-1.0-0@2%3A1.0.26-1%2Bb2?arch=amd64&distro=astra-1.7_x86-64&package-id=1645309387d57ba7&upstream=libusb-1.0%402%3A1.0.26-1", "type": "library", "publisher": "Aurelien Jarno <aurel32@debian.org>", "name": "libusb-1.0-0", "version": "2:1.0.26-1+b2", "licenses": [ { "license": { "id": "GPL-2.0-only" } }, { "license": { "id": "LGPL-2.1-only" } } ], "cpe": "cpe:2.3:a:libusb-1.0-0:libusb-1.0-0:2\\:1.0.26-1\\+b2:*:*:*:*:*:*:*", "purl": "pkg:deb/astra/libusb-1.0-0@2%3A1.0.26-1%2Bb2?arch=amd64&distro=astra-1.7_x86-64&upstream=libusb-1.0%402%3A1.0.26-1" }, { "bom-ref": "pkg:deb/astra/pcscd@1.8.24-1%2Bb2?arch=amd64&distro=astra-1.7_x86-64&package-id=74a083bbba38daa9&upstream=pcsc-lite%401.8.24-1", "type": "library", "publisher": "Ludovic Rousseau <rousseau@debian.org>", "name": "pcscd", "version": "1.8.24-1+b2", "licenses": [ { "license": { "id": "BSD-3-Clause" } }, { "license": { "id": "GPL-3.0-only" } }, { "license": { "id": "GPL-3.0-or-later" } }, { "license": { "id": "ISC" } }, { "license": { "name": "Expat" } } ], "cpe": "cpe:2.3:a:pcscd:pcscd:1.8.24-1\\+b2:*:*:*:*:*:*:*", "purl": "pkg:deb/astra/pcscd@1.8.24-1%2Bb2?arch=amd64&distro=astra-1.7_x86-64&upstream=pcsc-lite%401.8.24-1" }, { "bom-ref": "pkg:deb/astra/rutokenlogon@1.0.0?arch=amd64&distro=astra-1.7_x86-64&package-id=7cb848b3a81f9c9f", "type": "library", "publisher": "Aktiv Co. <hotline@rutoken.ru>", "name": "rutokenlogon", "version": "1.0.0", "cpe": "cpe:2.3:a:rutokenlogon:rutokenlogon:1.0.0:*:*:*:*:*:*:*", "purl": "pkg:deb/astra/rutokenlogon@1.0.0?arch=amd64&distro=astra-1.7_x86-64" } ], "dependencies": [ { "ref": "pkg:deb/astra/libaudit1@1%3A2.8.5astra3%2Bci4?arch=amd64&distro=astra-1.7_x86-64&package-id=ec8648962195b247&upstream=audit", "dependsOn": [ "pkg:deb/astra/libaudit-common@1%3A2.8.5astra3%2Bci4?arch=all&distro=astra-1.7_x86-64&package-id=2708ca830f5c5df7&upstream=audit", "pkg:deb/astra/libc6@2.28-10%2Bdeb10u4%2Bci202407161455%2Bastra12?arch=amd64&distro=astra-1.7_x86-64&package-id=5c6f0772bc436927&upstream=glibc", "pkg:deb/astra/libcap-ng0@0.7.9-2%2Bb2?arch=amd64&distro=astra-1.7_x86-64&package-id=b3ab0c8350ed83af&upstream=libcap-ng%400.7.9-2" ] }, { "ref": "pkg:deb/astra/libc6@2.28-10%2Bdeb10u4%2Bci202407161455%2Bastra12?arch=amd64&distro=astra-1.7_x86-64&package-id=5c6f0772bc436927&upstream=glibc", "dependsOn": [ "pkg:deb/astra/libgcc1@1%3A8.3.0-6%2Bb1?arch=amd64&distro=astra-1.7_x86-64&package-id=748a8e3331794acd&upstream=gcc-8%408.3.0-6" ] }, { "ref": "pkg:deb/astra/libccid@1.5.2-1?arch=amd64&distro=astra-1.7_x86-64&package-id=686f8e47cda607fb&upstream=ccid", "dependsOn": [ "pkg:deb/astra/libc6@2.28-10%2Bdeb10u4%2Bci202407161455%2Bastra12?arch=amd64&distro=astra-1.7_x86-64&package-id=5c6f0772bc436927&upstream=glibc", "pkg:deb/astra/libusb-1.0-0@2%3A1.0.26-1%2Bb2?arch=amd64&distro=astra-1.7_x86-64&package-id=1645309387d57ba7&upstream=libusb-1.0%402%3A1.0.26-1" ] }, { "ref": "pkg:deb/astra/libpam0g@1.3.1-5.astra.se16?arch=amd64&distro=astra-1.7_x86-64&package-id=4dd08cc62f7ca378&upstream=pam", "dependsOn": [ "pkg:deb/astra/debconf@1.5.71%2Bdeb10u1%2Bci202303291646%2Bastra1%2Bb1?arch=all&distro=astra-1.7_x86-64&package-id=fd877f1e40ca69cd&upstream=debconf%401.5.71%2Bdeb10u1%2Bci202303291646%2Bastra1", "pkg:deb/astra/libaudit1@1%3A2.8.5astra3%2Bci4?arch=amd64&distro=astra-1.7_x86-64&package-id=ec8648962195b247&upstream=audit", "pkg:deb/astra/libc6@2.28-10%2Bdeb10u4%2Bci202407161455%2Bastra12?arch=amd64&distro=astra-1.7_x86-64&package-id=5c6f0772bc436927&upstream=glibc" ] }, { "ref": "pkg:deb/astra/libpcsclite1@1.8.24-1%2Bb2?arch=amd64&distro=astra-1.7_x86-64&package-id=3df3ba37584b8028&upstream=pcsc-lite%401.8.24-1", "dependsOn": [ "pkg:deb/astra/libc6@2.28-10%2Bdeb10u4%2Bci202407161455%2Bastra12?arch=amd64&distro=astra-1.7_x86-64&package-id=5c6f0772bc436927&upstream=glibc" ] }, { "ref": "pkg:deb/astra/libudev1@241-7~deb10u8astra.se34%2Bci1?arch=amd64&distro=astra-1.7_x86-64&package-id=996aced19957f5fa&upstream=systemd", "dependsOn": [ "pkg:deb/astra/libc6@2.28-10%2Bdeb10u4%2Bci202407161455%2Bastra12?arch=amd64&distro=astra-1.7_x86-64&package-id=5c6f0772bc436927&upstream=glibc" ] }, { "ref": "pkg:deb/astra/libusb-1.0-0@2%3A1.0.26-1%2Bb2?arch=amd64&distro=astra-1.7_x86-64&package-id=1645309387d57ba7&upstream=libusb-1.0%402%3A1.0.26-1", "dependsOn": [ "pkg:deb/astra/libatomic1@8.3.0-6%2Bb1?arch=amd64&distro=astra-1.7_x86-64&package-id=8b755eb11941e775&upstream=gcc-8%408.3.0-6", "pkg:deb/astra/libc6@2.28-10%2Bdeb10u4%2Bci202407161455%2Bastra12?arch=amd64&distro=astra-1.7_x86-64&package-id=5c6f0772bc436927&upstream=glibc", "pkg:deb/astra/libudev1@241-7~deb10u8astra.se34%2Bci1?arch=amd64&distro=astra-1.7_x86-64&package-id=996aced19957f5fa&upstream=systemd" ] }, { "ref": "pkg:deb/astra/pcscd@1.8.24-1%2Bb2?arch=amd64&distro=astra-1.7_x86-64&package-id=74a083bbba38daa9&upstream=pcsc-lite%401.8.24-1", "dependsOn": [ "pkg:deb/astra/libc6@2.28-10%2Bdeb10u4%2Bci202407161455%2Bastra12?arch=amd64&distro=astra-1.7_x86-64&package-id=5c6f0772bc436927&upstream=glibc", "pkg:deb/astra/libccid@1.5.2-1?arch=amd64&distro=astra-1.7_x86-64&package-id=686f8e47cda607fb&upstream=ccid", "pkg:deb/astra/libpcsclite1@1.8.24-1%2Bb2?arch=amd64&distro=astra-1.7_x86-64&package-id=3df3ba37584b8028&upstream=pcsc-lite%401.8.24-1", "pkg:deb/astra/libsystemd0@241-7~deb10u8astra.se34%2Bci1?arch=amd64&distro=astra-1.7_x86-64&package-id=b9c496f1d240bf3e&upstream=systemd", "pkg:deb/astra/libudev1@241-7~deb10u8astra.se34%2Bci1?arch=amd64&distro=astra-1.7_x86-64&package-id=996aced19957f5fa&upstream=systemd", "pkg:deb/astra/lsb-base@10.2019051400%2Bci202012021351%2Bastra2%2Bb1?arch=all&distro=astra-1.7_x86-64&package-id=8abe7cabdb191e7b&upstream=lsb%4010.2019051400%2Bci202012021351%2Bastra2" ] }, { "ref": "pkg:deb/astra/rutokenlogon@1.0.0?arch=amd64&distro=astra-1.7_x86-64&package-id=7cb848b3a81f9c9f", "dependsOn": [ "pkg:deb/astra/grdcontrol@4.4.0?arch=amd64&distro=astra-1.7_x86-64&package-id=5b8d0051eb5a2636", "pkg:deb/astra/libc6@2.28-10%2Bdeb10u4%2Bci202407161455%2Bastra12?arch=amd64&distro=astra-1.7_x86-64&package-id=5c6f0772bc436927&upstream=glibc", "pkg:deb/astra/libccid@1.5.2-1?arch=amd64&distro=astra-1.7_x86-64&package-id=686f8e47cda607fb&upstream=ccid", "pkg:deb/astra/libpam0g@1.3.1-5.astra.se16?arch=amd64&distro=astra-1.7_x86-64&package-id=4dd08cc62f7ca378&upstream=pam", "pkg:deb/astra/libpcsclite1@1.8.24-1%2Bb2?arch=amd64&distro=astra-1.7_x86-64&package-id=3df3ba37584b8028&upstream=pcsc-lite%401.8.24-1", "pkg:deb/astra/pcscd@1.8.24-1%2Bb2?arch=amd64&distro=astra-1.7_x86-64&package-id=74a083bbba38daa9&upstream=pcsc-lite%401.8.24-1" ] } ] }
Кстати, визуально этот файл можно посмотреть здесь.


Задача SCA
Важно отметить, что наша задача заключается в непрерывном компонентном анализе наших продуктов. Это означает, что анализ необходимо сделать автоматическим, и, например, запускать по расписанию раз в неделю.
Естественно, аудит найденных уязвимостей невозможно выполнить автоматически, но актуализацию информации о компонентах – можно, чем мы и займемся в рамках этой статьи.
В рамках одного проекта нас интересуют несколько конфигураций сборки, которые отличаются следующими характеристиками:
профилем (тулчейнами, целевой платформой): glibc-x86_64, windows-msvc143, arm и другие.
продуктовой конфигурацией: default/for-customer-1/for-customer-2.
названием ветки: master, release candidate, и т.п.
Это повлияет на реализацию pipeline и способ именования артефактов анализа в дальнейшем.
Есть еще один нюанс — это хранение зависимостей. Исторически у нас сложилось три источника:
Директория 3rdparty для компилируемых зависимостей (например, nlohmann/json).
binary_deps (старый вариант, самописный скрипт).
conan 2 (новый вариант).
Естественно, вести автоматический учет первых двух источников будет проблематично. Так и оказалось. Но обо всем по порядку…
Генерация SBOM
Мы рассмотрели следующие инструменты:
cdxgen
syft
conan:sbom (официальное расширение conan 2).
cdxgen
cdxgen – это официальный генератор SBOM проекта OWASP CycloneDX, созданный той же командой, которая разрабатывает и поддерживает сам стандарт. Имеет режимы c, cpp и другие; различные уровни глубины анализа. К сожалению, минусов оказалось куда больше, чем плюсов.
Во-первых, cdxgen совсем не выявляет зависимостей. Насколько я понял, на это никак нельзя повлиять через cli или env параметры. Ссылки на документацию: CLI и ENV.
Параметр --min-confidence по умолчанию приводит к тому, что в отдельные компоненты выделяются даже заголовочные файла. Значение 0.1 приводит к тому, что ни один класс не включается в SBOM, в том числе Boost и т.д. Из чего я делаю вывод, что cdxgen с C++ дружит плохо. Недавний комментарий насчет плохой поддержки C++.
Параметры --deep, --profile не привносят значительных изменений.
Забавный факт, в дебажных логах дается косвенная рекомендация не использовать cdxgen для проектов C и C++:
Looking for C/C++ projects. Should I warn the user that the generated SBOM might have low accuracy and contain errors?
Во-вторых, в cdxgen реализована минимальная поддержка conan. Воспринимаются только conanfile.txt и conan.lock, но не conanfile.py. Из-за этого в некоторых случаях (например, в нашем ?), когда в conanfile используются кастомные команды (в нашем случае rtrequires вместо requires), cdxgen может упустить важные связи или зависимости, игнорировать видимость компонентов и т.д.
Для эксперимента я создал conan.lock вручную, с ним cdxgen выделил чуть больше компонентов, но не зависимости, т.к. он не запускает конан, а видимо просто парсит пути и анализирует их код в придачу.
В третьих, в официальной архивной репе CycloneDX cyclonedx-conan написано следующее: "CycloneDX support for Conan2 is available as an official Conan2 extension." Думаю, можно считать это официальной рекомендацией.
Вывод: в какой-то степени cdxgen дает представление об используемых зависимостях, подходит для первичного ручного анализа. Однако для использования в автоматической системе мониторинга сгенерированный SBOM совершенно не подходит.
syft
syft – это open source решение для генерации sbom в контейнерах. Однако наше внимание привлекла опция filesystems в списке поддерживаемых supported scan targets.
К сожалению, режим сканирования каталогов малоинформативен. Хоть и заявлена поддержка conan, по факту воспринимает только conanfile.txt и conan.lock, в которых не указываются зависимости. Пруф только такой, но от наличия conanfile.py действительно ничего не меняется. Итого, получаем всего 1 уровень в глубину.
Помимо conan в этом режиме сканируются:
Packages
File digests
File metadata
Executables
Исходный код не учитывается. Секции Depends и Recommends из сканируемых пакетов тоже:
Пруф, почему не учитывается source code.
Пруф, что реализована поддержка только установленных пакетов (пункты dpkg и rpm).
Пруф, что executables scan бесполезный.
Рассмотрим режим сканирования образа. Для теста я собрал docker контейнер с Astra 1.7 и установленным rutokenlogon. syft создал большой SBOM со всеми управляемыми dpkg пакетами. Для rutokenlogon выявились корректные зависимости с корректными версиями. Фактически все, что указаны в deb пакете в разделе Depends. То есть раздел Recommends не учитывается.
Вывод: syft подходит как дополнительный материал для анализа системных зависимостей. Но многие наши зависимости линкуются статически, поэтому он не покрывает наши хотелки полностью.
conan:sbom
Официальный модуль для генерации SBOM conan:sbom содержит функции для создания файлов в формате CycloneDX версий 1.4 и 1.6 из построенного конаном графа зависимостей. Таким образом, conan создает глубокое дерево со всеми версиями и зависимостями, а также корректно работает с кастомными командами, что в нашем случае немаловажно.


Кстати, если Вам интересно, как мы используем conan 2 в наших проектах – обязательно напишите об этом в комментариях. Мы проделали серьезную работу по настройке и автоматизации, и нам точно есть, что рассказать!
Итого:
cdxgen практически не юзабелен для проектов C++.
syft дает корректную информацию о фактических зависимостях. (Важно, чтобы они были установлены через пакетный менеджер).
conan дает корректную информацию по внутренним зависимостям.
В соответствии со сказанным выше, единственный надежный и удобный способ для C++ – это conan:sbom.
Итак, первая проблема решена. Но что же делать с 3rdparty зависимостями? К сожалению, мы не придумали ничего лучше, как вручную записывать их версии в 3rdparty/3rdparty_sbom.json.
Версии некоторых open source зависимостей можно определить через сервис osv.dev/post-v1-determineversion. По сути, это база данных, которая сопоставляет хеш исходников и версию. По тому же принципу можно вычислить версии вручную. Как простой пример, можно сделать что-то вроде:
find myproject/ -type f -name '*.[ch]' | sort | xargs sha256sum
Далее мы реализовали генератор SbomGenerator для слияния дополнительных SBOM, так как не всем разработчикам хочется устанавливать дополнительные зависимости для сборки проекта.
Мерджить и валидировать SBOM можно с помощью официальной либы cyclonedx-python-lib. На данный момент не поддерживается спецификация версии 1.6.
Итого, вызов conan на CI выглядит следующим образом:
conan install conanfile.py --update \ -pr "${platform}" \ -s build_type=Release \ -s rtservice/*:build_type=Release \ -g SbomGenerator
Разумеется, ручное формирование части SBOM влечет за собой невозможность автоматического контроля полноты анализа. При любых изменениях в 3rdparty части проекта разработчикам необходимо актуализировать файл SBOM. Чтобы не забывать об этом, мы добавили соответствующую галочку в темплейт Merge Request, но конечно, при первой возможности перенесем все в репозиторий conan.
Dependency Track
Вторым шагом по внедрению SCA является настройка системы мониторинга уязвимостей. Самым популярным open source решением является Dependency Track. В качестве очевидных плюсов отметим удобный API, активную поддержку и сформированное сообщество.
Полную инструкцию по настройке в статью включать мы не будем, но о некоторых нюансах расскажем ?
Например, о том, что основной поиск уязвимостей ведется по CPE – стандартизированной схеме именования от NIST/MITRE для идентификации ОС, приложения и оборудования. Имя продукта в такой схеме формируется следующим образом:
cpe:версия_схемы:тип:вендор:продукт:версия:update:edition:language:...
Например, по запросу cpe:2.3:a:apache:log4j:2.14.1:-:*:*:*:*:*:* в базе NVD можно найти 9 уязвимостей.
Но весь прикол в том, что conan2 не генерирует CPE. Вместо этого он пользуется новыми и молодежными PURL – более современной спецификацией для однозначной идентификации пакетов в конкретных экосистемах пакетных менеджеров. Они формируются следующим образом:
pkg:тип/namespace/имя@версия?qualifiers#subpath
Несколько примеров:
pkg:maven/org.apache.logging.log4j/log4j-core@2.14.1 pkg:npm/%40angular/core@16.0.0 pkg:deb/debian/curl@7.88.1-10?arch=amd64
Так вот, в Dependency Track по факту самый важный Analyzer – это internal. Он бесплатный позволяет искать по уязвимости по PURL (опция fuzzy matching по PURL), что очень важно, т.к. conan2 не формирует CPE.
Также бесплатно можно настроить OSS Index Analyzer, но с ним тоже есть проблемки при поиске conan пакетов, связанные с хешами ревизии, которые включаются в названия пакетов.
В дополнение мы рассмотрели возможность подключения базы ФСТЭК через функциональность Private Vulnerability Repository. Однако для нас это оказалось избыточным, поскольку мы не используем крупные отечественные продукты, на которые в первую очередь ориентирована ФСТЭК.
Далее чуть-чуть про структуру проектов. Мы посчитали самым удобным называть проект через слеш с названием ветки, т.к. для многих проектов есть более одной целевой ветки / конфигурации. Вместо версии проекта для активных веток мы указываем хеш коммита.

# Создание проекта (если его не существует) CREATE_PROJECT_RESPONSE=$(\ curl -X "PUT" "${DEPENDENCY_TRACK_URL}/api/v1/project" \ -H "Content-Type: application/json" \ -H "X-Api-Key: ${DEPENDENCY_TRACK_API_TOKEN}" \ -d "{\"name\": \"${PROJECT_NAME}\"}" \ --fail \ ) PROJECT_UUID=$(jq -r '.uuid' <<< "${CREATE_PROJECT_RESPONSE}") # Загрузка SBOM в существующий проект curl -X "POST" "${DEPENDENCY_TRACK_URL}/api/v1/bom" \ -H "Content-Type: multipart/form-data" \ -H "X-Api-Key: ${DEPENDENCY_TRACK_API_TOKEN}" \ -F "project=${PROJECT_UUID}" \ -F "bom=@${PATH_TO_SBOM}" \ --fail > /dev/null # Обновление версии существующего проекта (версия = хеш коммита) curl -X "PATCH" "${DEPENDENCY_TRACK_URL}/api/v1/project/${PROJECT_UUID}" \ -H "Content-Type: application/json" \ -H "X-Api-Key: ${DEPENDENCY_TRACK_API_TOKEN}" \ -d "{\"version\":\""${COMMIT_SHA}"\"}" \ --fail > /dev/null
Заключение
Ну вот и все на сегодня! Как видите, компонентный анализ в общем-то достаточно простая и понятная вещь. Хотя без нюансов при реализации не обходится.
В дальнейшем мы планируем интегрировать механизм нотификаций Dependency Track и настроить рассылку кастомных отчетов РБПО на почту и в корпоративный мессенджер. А еще пока мы все это разворачивали, вышел Dependency Track v5.0 – скучать не приходится ?
Благодарим за внимание! Обязательно напишите в комментариях, была ли данная статья полезной для Вас, Ваши предложения и замечания. Будем рады ответить на любые вопросы!