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

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

containerized build

Почему это важно для SCA/OSA

Сборка C/C++ в контейнере с multi-stage Dockerfile создаёт характерную ловушку для SCA: то, что формировало бинарь, и то, что попадает в финальный образ, — это разные множества. В стадии сборки ставятся -dev-пакеты (libssl-dev, zlib1g-dev, заголовки, статические .a), компилятор, кодогенераторы. В рантайм-образ через COPY --from=build переезжает только готовый бинарь и, может быть, рантайм-.so. Если генерировать SBOM по финальному образу, всё, что было в build-стадии, исчезает — а именно оно определило версии слинкованного кода.

Особенно остро это со статической линковкой: libfoo-dev дал .a, код вкомпилировался в бинарь, а сам пакет в финальном образе отсутствует. Скан рантайм-образа покажет «чистый» бинарь без зависимости, которая физически в нём есть.

Как это проявляется

Классический multi-stage Dockerfile прячет состав на стыке стадий:

FROM debian:12 AS build
RUN apt-get update && apt-get install -y libssl-dev zlib1g-dev g++ cmake   # сформировали бинарь
COPY . /src
RUN cmake -S /src -B /b && cmake --build /b

FROM debian:12-slim                      # чистый рантайм
COPY --from=build /b/app /usr/bin/app    # переехал только бинарь
# libssl-dev/zlib1g-dev здесь НЕТ — но их код в app

Снимите состав в build-стадии, а не в финале

# внутри build-стадии (target):
docker build --target build -t app:build .
docker run --rm app:build dpkg -l > build-stage-packages.txt   # полный список -dev пакетов
# сравните с финалом:
docker run --rm app:final dpkg -l > runtime-packages.txt

build-stage-packages.txt содержит libssl-dev 3.0.13, которого нет в runtime-packages.txt, — это и есть пропавшая зависимость.

Слепая зона или приём

Слепая зона — SBOM рантайм-образа выдают за SBOM продукта. Build-стадия с её -dev-пакетами и статическими библиотеками не отражается в финальном образе, и поздний скан её не восстановит. Дополнительный риск — образ может содержать удалённые на вид артефакты (секреты, исходники) в нижних слоях, но для состава главное именно потеря build-стадии.

SBOM продукта собирается из build-стадии Снимайте инвентарь пакетов и команды компилятора в той стадии, где формировался бинарь (--target build), и объединяйте с инвентарём рантайм-образа. SBOM только финального образа систематически недосчитывает того, что слинковано статически.

Что делать на проекте

  1. Стройте build-стадию отдельно (docker build --target build) и снимайте в ней dpkg -l/rpm -qa — это состав сборочного окружения.
  2. Снимите реальные команды компиляции/линковки внутри build-стадии (build-scan, VERBOSE=1), чтобы поймать статические .a.
  3. Сгенерируйте SBOM отдельно для build-стадии и для рантайм-образа; объедините, отметив, что слинковано статически.
  4. Зафиксируйте базовые образы с дайджестом (debian:12@sha256:...), а не по тегу — иначе состав плывёт.
  5. Прогоните ldd по бинарю внутри финального образа: динамические зависимости видны, статические — нет, их добирайте из build-стадии.
  6. Проверьте нижние слои образа на лишние артефакты сборки (см. смежные заметки про слои и обфускацию).

Связь