{ "index": 170, "slug": "editorial-2023-04-mechanism-dependency-security", "title": "Безопасность зависимостей: как связать advisory, lockfile и SBOM", "excerpt": "Совпадение имени пакета с advisory ещё не доказывает риск для выпуска. Разбираем, что фиксируют manifest, lockfile и SBOM, как проверить путь зависимости и когда обновление можно считать готовым.", "contentHtml": "
Сканер сообщает об advisory для транзитивного пакета. Команда меняет строку в package.json, получает зелёный pull request и закрывает задачу. Через день выясняется, что lockfile не изменился, SBOM относится к предыдущему образу, а пакет присутствует только в optional-ветке для другой платформы. Ошибка стоит времени на ложную аварию или, хуже, оставляет настоящий риск без владельца. В обоих случаях команда не может ответить на простой вопрос: какой компонент попал в конкретный артефакт и где он может быть достигнут?
Тезис статьи прост: безопасность зависимости проверяют не по одному имени и не по одному файлу. Сначала связывают внешний сигнал с точной записью в resolved tree. Затем связывают эту запись с SBOM того же build. После этого отдельно проверяют достижимость и поведение в нужном runtime-сценарии. package.json, lockfile, SBOM и runtime evidence описывают разные границы. Совпадение двух списков полезно, но само по себе не закрывает риск.
Manifest отвечает на вопрос «что проект просит установить». Для прямой зависимости он хранит имя и допустимый диапазон версий. Запись \"demo-shell\": \"^1.0.0\" не говорит, какая версия окажется в текущем дереве.
Lockfile отвечает на другой вопрос: какое дерево выбрал установщик при конкретных правилах разрешения. Для npm это точное представление дерева, созданного установкой. В нём видны транзитивные пакеты, версии, источники и integrity-поля. Но lockfile не является журналом уже запущенного процесса. Его нужно связать с commit, командой установки и артефактом, который действительно собирает pipeline.
\nSBOM отвечает на вопрос «какие компоненты заявлены для named artifact и как они связаны». Он может быть независим от конкретного package manager и пригоден для анализа поставки. Но файл без build ID, digest или другой неизменяемой привязки не доказывает состав текущего образа. Runtime evidence отвечает на четвёртый вопрос: что загрузилось и выполнилось в выбранном сценарии. Ни один из этих слоёв не заменяет остальные.
\n| Артефакт | Главный вопрос | Проверка | Чего не доказывает |
|---|---|---|---|
package.json | Какую direct dependency просит проект? | Diff manifest и review диапазона | Точное resolved tree |
package-lock.json | Какое дерево выбрал installer? | Diff lockfile и clean install | Загрузку модуля в процессе |
| SBOM | Какие компоненты заявлены для артефакта? | Component records и artifact identity | Достижимость по коду и конфигурации |
| Runtime evidence | Что произошло в named scenario? | Тест, trace или controlled smoke | Все возможные пути приложения |
| Advisory | Какой внешний сигнал надо разобрать? | ID, источник, package и version scope | Воздействие на этот сервис |
Рассмотрим ограниченный fixture. demo-service зависит от demo-shell, а demo-shell — от demo-parser. Advisory указывает на demo-parser@1.0.0. В synthetic lockfile пакет есть. В synthetic SBOM есть запись для того же имени и версии. Это подтверждает пересечение двух заданных массивов. Это не подтверждает, что пакет установлен в production, достижим из реального entry point или уязвим именно в таком контексте.
const locked = packages.find(\n (item) => item.name === advisory.packageName\n && item.version === advisory.affectedVersion,\n);\n\nconst recorded = components.some(\n (item) => item.name === advisory.packageName\n && item.version === advisory.affectedVersion,\n);\n\nreturn {\n lockfile: locked ? 'entry-matched' : 'entry-not-found',\n sbom: recorded ? 'component-matched' : 'component-not-found',\n reachability: 'not-assessed',\n vulnerabilityStatus: 'not-determined',\n};\nКод намеренно не читает настоящий lockfile, не запускает scanner и не строит call graph. Он показывает важную границу: найденная строка — это факт о входных данных, а не вердикт о безопасности. Если имя отсутствует в lockfile, вопрос меняется на provenance advisory или на несовпадение версии. Если имя есть в lockfile, но отсутствует в SBOM нужного build, нужно проверять генерацию и identity артефакта. Если оба списка совпали, всё ещё остаётся путь использования.
\n| Симптом | Причина | Проверка | Действие |
|---|---|---|---|
Изменился package.json, но lockfile остался прежним | Проверили намерение, но не разрешённое дерево | Сравнить direct range, lockfile entry и install command | Пересобрать candidate tree и review diff |
| Lockfile и SBOM показывают разные версии | SBOM создан из другого commit или build | Сверить source revision, artifact digest и время генерации | Сгенерировать SBOM для того же candidate artifact |
| Пакет есть в SBOM, но не найден в пути вызова | Состав поставки смешан с reachability | Проверить import, entry point, flags и optional branch | Оставить риск в review до доказанного исключения |
| После patch update изменилось много строк lockfile | Resolver обновил транзитивное дерево | Разделить added, removed, version и integrity changes | Проверить каждую существенную ветку и тесты |
| Fixture завершился PASS | PASS проверяет только synthetic contract | Посмотреть статусы provenance, runtime и deployment | Не прикладывать PASS как доказательство production safety |
Patch update не ограничивается одной строкой manifest. Новый диапазон может выбрать другую транзитивную версию. Installer может иначе обработать optional dependency на другой платформе. Lifecycle script может изменить содержимое build. Поэтому после обновления смотрят lockfile diff, а не только diff package.json. Важны added и removed packages, version shifts, integrity и source fields.
Отдельная ловушка — SBOM, который лежит рядом с репозиторием, но не рядом с выпуском. Такой документ может быть полезен для разработки и одновременно бесполезен для ответа о deployed image. Минимальный критерий свежести задаёт сам pipeline: SBOM создан из того же source revision и относится к тому же artifact digest, что и release candidate. Это инженерный критерий, который нужно реализовать, а не свойство любого файла с названием SBOM.
\nУчебная модель не знает реальную advisory database, формат конкретного SBOM, private registry, зависимости ОС, native addons, bundle, generated source или runtime configuration. Она не вычисляет vulnerable range и не доказывает отсутствие эксплуатации. Даже реальный путь импорта не равен доказанной уязвимости: нужно понимать, достигается ли опасный код с контролируемыми входными данными и при каких настройках.
\nОтрицательный путь обязателен. Advisory может не совпасть с exact resolved version. Компонент может быть в lockfile, но отсутствовать в named artifact. SBOM может быть старше образа. Достижимость может зависеть от выключенного по умолчанию флага. В каждом случае нельзя подменять неизвестность уверенным исключением. Укажите, что именно не проверено, каким методом это проверят и кто владеет следующим действием.
\nОбновление готово к решению, когда для одного candidate release можно восстановить всю цепочку: advisory → exact package@version → lockfile commit → SBOM → artifact digest → проверенный runtime-сценарий. Для каждого перехода есть ссылка или сохранённый результат. Lockfile diff просмотрен. Clean install и проектные тесты завершились ожидаемо. Неизвестные пути явно перечислены. Rollback описывает, какой артефакт и какую версию возвращают.
\nЕсли хотя бы одно звено отсутствует, работа может быть готова к следующему этапу, но не к утверждению безопасности выпуска. Это не бюрократическая формальность. Такая запись позволяет отличить реальный риск от неполного inventory и не потерять его при следующем обновлении.
\n