После advisory команда часто открывает package.json, меняет видимую версию и считает работу законченной. Цена такой правки проявляется позже: lockfile разрешил другое транзитивное дерево, SBOM относится к старому build, а reviewer не может восстановить, что именно было поставлено. Ошибка не в существовании трёх файлов. Ошибка в ожидании, что manifest, lockfile и инвентарь отвечают на один вопрос.
Полезнее разделить их роли до изменения. Manifest выражает намерение автора: direct dependency и допустимый range. Lockfile фиксирует resolved tree, которое выбрал конкретный installer при известных ему правилах. SBOM описывает компоненты и supply-chain relationships выбранного software artifact. Runtime отвечает ещё на четвёртый вопрос: что реально было загружено в конкретном процессе. Эти слои связаны, но не взаимозаменяемы; у каждого есть свой владелец и свой способ проверки.
Один package — четыре разные границы
Документация npm 7 описывает package-lock.json как автоматически созданное представление dependency tree, которое помогает последующим installs получить то же дерево. Это сильнее, чем range в manifest, но не превращает файл в журнал уже запущенного сервиса. Installer flags, platform, optional dependencies, lifecycle scripts и выбор workspace могут менять условия, в которых дерево превращается в конкретный build. Поэтому проверить lockfile — обязательный шаг, но назвать его production inventory можно только при явной связи с артефактом.
NTIA называет SBOM формальной записью о деталях и отношениях компонентов. В отличие от lockfile, SBOM может жить рядом с поставкой и быть полезен вне конкретного package manager. Но полезность требует provenance: кто и когда сгенерировал документ, из какого commit или image, в каком format, где хранится неизменяемая ссылка. SPDX 2.3 описывает спецификацию формата и versioned fields; он не создаёт честную связь с binary автоматически. Файл без указанного artifact identity остаётся списком, происхождение которого невозможно проверить.
| Артефакт | Главный вопрос | Владелец состояния | Проверяемый сигнал | Чего не обещает |
|---|---|---|---|---|
| package.json | какую direct dependency просит проект? | автор change | manifest diff и review range | точно resolved tree |
| package-lock.json | какое дерево выбрал installer? | resolver и committed lockfile | lockfile diff, clean install с теми же правилами | что модуль загрузился в process |
| SBOM | какие компоненты заявлены для named artifact? | build pipeline и владелец inventory | artifact identity, format, component records | что путь вызова достижим |
| Runtime evidence | что произошло в named scenario? | запуск и наблюдение среды | test, trace или controlled smoke | все возможные configuration paths |
| Advisory | какой внешний risk signal надо разобрать? | источник advisory и triage owner | ID, URL, package/version scope | факт воздействия на этот сервис |
Минимальный graph, который можно прочитать
Учебный fixture строит три lockfile rows: demo-service зависит от demo-shell, а demo-shell зависит от demo-parser. SBOM намеренно перечисляет только два component records, потому что пример показывает не полноту инвентаря, а необходимость сравнения. buildSyntheticInventory выдаёт строку для каждого package и отдельно помечает, есть ли synthetic запись в SBOM. Статус synthetic-not-listed означает пропуск в специально заданном массиве, не проблему стороннего генератора и не evidence о настоящей поставке.
import {
buildSyntheticInventory,
syntheticDependencyInput,
} from './web/scripts/upgrade-2023-04.mjs';
const inventory = buildSyntheticInventory(syntheticDependencyInput);
console.log(inventory.rows);
// demo-parser: lockfile = synthetic-listed
// demo-parser: sbom = synthetic-listed
// runtime = not-observed-by-fixture
console.log(inventory.deploymentInventory);
// not-proven-by-fixture
Этот пример воспроизводим, потому что все строки определены рядом с функцией. Он честно неполон: не читает package-lock.json, не формирует SPDX или CycloneDX, не строит container image и не знает, какая команда release взяла артефакт. Важно не расширять его обещание. Строка sbom = synthetic-listed подтверждает лишь пересечение двух arrays в памяти. Она не даёт право написать в ticket, что production SBOM обновлён или что transitive dependency больше не существует.
Почему совпадение списков не заканчивает работу
Совпадение name и version между lockfile и SBOM — хороший consistency check, но оно оставляет важные вопросы. Какой graph branch выбрал resolver? Не попал ли optional component в другой platform? Совпадает ли current commit с build, для которого получен SBOM? Был ли SBOM regenerated после overrides? Есть ли bundle, native addon или generated source, чей состав не виден в простом списке npm packages? Ответы зависят от build pipeline, а не от красивого JSON-формата.
Особенно опасно обходить это distinction после patch update. Direct dependency может выглядеть как единственная изменённая строка, но новый resolution меняет transitive children. Поэтому в review полезно смотреть не только manifest, но и lockfile diff: added, removed, version-shifted, integrity или source changes. Затем SBOM generation запускают для candidate artifact и сопоставляют его с тем же commit. Если процесс не умеет такого связывания, результат не «SBOM не нужен», а явный пробел в цепочке evidence, который надо закрыть до уверенного выпуска.
Маршрут: симптом → причина → проверка → действие
- Симптом. В pull request изменился package.json, но lockfile не изменился, изменился неожиданно широко или рядом лежит SBOM без указания build.
- Причина. Намерение автора, resolved tree и inventory artifact смешаны в один этап. Поэтому нельзя понять, какой файл устарел и кто должен его обновить.
- Проверка. Назовите commit, installer command и relevant flags для lockfile; затем назовите artifact identity, generator и format для SBOM. Сверяйте exact name/version, но сохраняйте различие уровней.
- Действие. Сделайте таблицу соответствий для candidate release: manifest request → lockfile resolution → SBOM component → build artifact. Отсутствующее звено пишите как unknown, не как совпадение.
- Проверка контракта. Запустите fixture. Он должен оставить deploymentInventory равным not-proven-by-fixture и не превратить synthetic arrays в настоящий inventory.
- Фиксация. В release record храните ссылки на lockfile diff, generated SBOM и artifact ID. Это даёт следующему reviewer точку сравнения для следующего advisory.
Как выбирать минимально достаточный SBOM
Не надо начинать с обещания «соберём идеальный список всего». Для одного pipeline достаточно установить contract: какой artifact считается release unit, кто запускает generator, какой format принимается, где лежит document, как он связывается с immutable build identity и кто проверяет failure. Минимальные элементы NTIA помогают не забыть о data fields, automation support и practices/processes. Но они не диктуют команду package manager и не решают, какие части monorepo являются отдельными поставками.
Внутри команды полезно хранить не только generated file, но и criterion freshness. Например, SBOM считается актуальным, если он был generated из того же source revision и относится к тому же artifact digest, что release candidate. Это criterion, а не факт в этой статье: его надо реализовать и проверить своим pipeline. Если такой связи нет, честнее назвать документ inventory candidate и не закрывать им вопрос о deployed package. Слова «lockfile» и «SBOM» здесь не являются печатями качества.
Ограничение модели и следующий шаг
Функция buildSyntheticInventory не выполняет install, не парсит реальный lockfile, не создаёт SPDX, не проверяет signature, digest, license или provenance. Она не видит environment variables, bundles, generated assets и runtime imports. Её единственное назначение — показать, что lockfile listing, SBOM listing и runtime observation имеют разные статусы даже у одного demo-parser. Из-за этого PASS fixture не может быть приложен как доказательство состава production image.
Следующий шаг — взять один существующий build pipeline и нарисовать его по таблице: где появляется manifest diff, где resolver создаёт lockfile, где generator получает artifact, где сохраняется SBOM и какой review видит оба указателя. Если ваш инструментарий не поддерживает часть цепочки, это нормальный результат диагностики. Сначала зафиксируйте precise gap и владельца, затем выберите инструмент. Так update перестаёт быть заменой строки и становится проверяемым изменением supply chain.
Проверяемые источники
- npm Docs v7.24.2 (Legacy): package-lock.json, 22 сентября 2021 — официальная legacy-документация npm 7, доступная до апреля 2023: lockfile описывает точное дерево, созданное установщиком, и предназначен для коммита. Это не снимок уже запущенного процесса.
- NTIA: The Minimum Elements for a Software Bill of Materials, 12 июля 2021 — официальный первичный документ: SBOM назван формальной записью о компонентах и связях цепочки поставки. Формат записи не доказывает, что компонент загружен в runtime.
- SPDX Specification 2.3.0, Conformance, 2022 — официальная спецификация SPDX 2.3, опубликованная в 2022 году. Она помогает именовать формат и версию инвентаря, но не генерирует его из данного fixture.
- NIST SP 800-218 SSDF Version 1.1, Final, 3 февраля 2022 — официальный финальный документ, доступный до апреля 2023. Он задаёт практики безопасной разработки и работы с компонентами, но не аттестует отдельный выпуск.