8 lines
19 KiB
JSON
8 lines
19 KiB
JSON
{
|
||
"index": 170,
|
||
"slug": "editorial-2023-04-mechanism-dependency-security",
|
||
"title": "Advisory, lockfile и SBOM: как доказать состав выпуска",
|
||
"excerpt": "Имя пакета в advisory ещё не доказывает риск для конкретного выпуска. Разбираем границы manifest, lockfile, SBOM и runtime, показываем проверку npm-командами и критерий, при котором неизвестность не маскируют под «безопасно».",
|
||
"contentHtml": "<p>Сканер сообщает об advisory для транзитивного пакета. Команда меняет строку в <code>package.json</code>, получает зелёный pull request и закрывает задачу. Позже выясняется, что lockfile не изменился, SBOM относится к предыдущему образу, а пакет присутствует только в optional-ветке другой платформы. Команда потратила время на ложную аварию или оставила настоящий риск без владельца. В обоих случаях не восстановлен ответ на главный вопрос: какой компонент попал в конкретный выпуск и при каких условиях он достижим?</p>\n<p>Безопасность зависимости проверяют не по одному имени и не по одному файлу. Внешний сигнал связывают с точной записью в resolved tree, эту запись — с SBOM того же кандидата, а затем отдельно проверяют достижимость и поведение нужного runtime-сценария. <code>package.json</code>, lockfile, SBOM и runtime evidence описывают разные границы. Совпадение двух списков полезно, но не является вердиктом о безопасности.</p>\n<h2>Сигнал advisory не равен факту о выпуске</h2>\n<p>Advisory — сообщение о проблеме в определённом пакете и диапазоне версий. Оно отвечает на вопрос «какой сигнал надо разобрать», но не на вопрос «затронут ли мой образ». Для этого нужны как минимум имя пакета, точная версия, источник пакета и путь, по которому компонент попал в кандидат.</p>\n<p>Различайте три утверждения. «Версия попала в дерево» — факт о разрешении зависимостей. «Компонент попал в артефакт» — факт о конкретном build и его SBOM. «Опасный код достижим с контролируемым входом» — вывод анализа кода, конфигурации и runtime. Эти утверждения могут быть истинны независимо друг от друга.</p>\n<h2>Что фиксирует каждый слой</h2>\n<p>Manifest фиксирует намерение проекта. Диапазон <code>\"demo-shell\": \"^1.0.0\"</code> задаёт допустимые версии, но не говорит, что установлено сегодня. Lockfile фиксирует resolved tree. В npm он содержит представление дерева, версии, источники и integrity-поля, чтобы повторная установка могла получить ту же структуру.</p>\n<p>SBOM — inventory конкретного программного продукта и его компонентов. Он полезен для сопоставления с базами уязвимостей и лицензий, но файл без source revision, build ID или digest нельзя надёжно связать с deployed image. Runtime evidence отвечает на ещё один вопрос: что загрузилось и произошло в выбранном сценарии. Ни один слой не заменяет остальные.</p>\n<div class=\"table-scroll\"><table><caption>Граница ответственности артефактов</caption><thead><tr><th scope=\"col\">Слой</th><th scope=\"col\">Вопрос</th><th scope=\"col\">Проверка</th><th scope=\"col\">Чего не доказывает</th></tr></thead><tbody><tr><td><code>package.json</code></td><td>Что проект просит установить?</td><td>Diff manifest и review диапазона</td><td>Точную resolved version</td></tr><tr><td><code>package-lock.json</code></td><td>Какое дерево выбрал installer?</td><td>Diff lockfile и clean install</td><td>Загрузку модуля в процессе</td></tr><tr><td>SBOM</td><td>Какие компоненты заявлены для artifact?</td><td>Records, relations и identity артефакта</td><td>Достижимость по коду</td></tr><tr><td>Runtime evidence</td><td>Что произошло в named scenario?</td><td>Тест, trace или controlled smoke</td><td>Все возможные пути</td></tr><tr><td>Advisory</td><td>Какой внешний сигнал исследовать?</td><td>ID, package и affected range</td><td>Воздействие на сервис</td></tr></tbody></table></div>\n<h2>Граф зависимости: почему одного совпадения мало</h2>\n<p>Возьмём небольшой учебный граф: <code>demo-service</code> зависит от <code>demo-shell</code>, а <code>demo-shell</code> — от <code>demo-parser</code>. Advisory указывает на <code>demo-parser@1.0.0</code>. Совпадение имени и версии в lockfile и SBOM подтверждает пересечение двух документов. Оно не подтверждает, что пакет попал в production-образ, вызывается из реального entry point или уязвим при фактической конфигурации.</p>\n<pre><code>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};</code></pre>\n<p>Этот фрагмент намеренно не читает настоящий lockfile и не запускает scanner. Он показывает границу доказательства: найденная строка — факт о входных данных, а не решение по уязвимости. Если exact version отсутствует в lockfile, проверяют provenance advisory или другой workspace. Если запись есть в lockfile, но отсутствует в SBOM кандидата, проверяют вход генератора и identity артефакта. Если оба списка совпали, остаётся анализ пути использования.</p>\n<figure><img src=\"/assets/editorial/2023/dependency-security-2023-lockfile-sbom.svg\" alt=\"Схема границ manifest, lockfile, SBOM и runtime evidence при проверке зависимости\" loading=\"lazy\" /><figcaption>Схема разделяет намерение проекта, resolved tree, инвентарь связанного артефакта и наблюдаемое выполнение. Это учебная модель, а не SBOM или запись production-запуска.</figcaption></figure>\n<h2>Воспроизводимая проверка в npm</h2>\n<p>Ниже — последовательность для проекта с <code>package.json</code> и <code>package-lock.json</code>. Запускайте её в том же commit и с той же версией Node/npm, которые использует сборка. <code>npm ci</code> удаляет существующий <code>node_modules</code> и завершается с ошибкой, если manifest и lockfile не согласованы. Это контроль входа, а не замена тестам приложения.</p>\n<pre><code>set -eu\n\n# 1. Чистое дерево кандидата.\nnpm ci\n\n# 2. Почему пакет установлен и где находятся его копии.\nnpm explain demo-parser\nnpm ls demo-parser --all --json > dependency-tree.json\n\n# 3. Известные advisory. Ненулевой код возможен при найденных проблемах.\nset +e\nnpm audit --json > audit.json\naudit_status=$?\nset -e\nprintf 'npm audit exit code: %s\\n' \"$audit_status\"\n\n# 4. SBOM именно после этой установки.\nnpm sbom --sbom-format=spdx > sbom.spdx.json\n\n# 5. Идентичность результатов фиксируется рядом с CI-артефактами.\ngit rev-parse HEAD\nsha256sum package-lock.json sbom.spdx.json</code></pre>\n<p><code>npm explain</code> выводит цепочку зависимостей, из-за которой пакет установлен. <code>npm ls</code> даёт машинно читаемое дерево текущего <code>node_modules</code>, а не доказательство того, что именно это дерево ушло в выпуск. <code>npm audit</code> отправляет описание зависимостей в registry и может завершиться ненулевым кодом; такой статус показывает результат аудита, но не доказывает эксплуатацию. <code>npm sbom</code> создаёт SPDX или CycloneDX. Для production SBOM сохраняйте digest образа или другого выпускаемого артефакта рядом с документом.</p>\n<h2>Как читать расхождения</h2>\n<p>Сверку начинайте с identity, а не с имени пакета. Один и тот же <code>name@version</code> в двух документах не означает один и тот же источник, если различаются registry, URL tarball, integrity, source revision или artifact digest. В lockfile эти поля помогают отличить запись от простого совпадения текста.</p>\n<div class=\"table-scroll\"><table><caption>Решение по результату сверки</caption><thead><tr><th scope=\"col\">Наблюдение</th><th scope=\"col\">Что доказано</th><th scope=\"col\">Что неизвестно</th><th scope=\"col\">Следующий шаг</th></tr></thead><tbody><tr><td>Advisory не совпадает с exact version</td><td>Сигнал не подтверждает affected version в этом lockfile</td><td>Корректность advisory, другой workspace или артефакт</td><td>Проверить источник и границы affected range</td></tr><tr><td>Exact version есть в lockfile, нет в SBOM</td><td>Кандидатное дерево содержит компонент</td><td>Другой вход генератора или неполный SBOM</td><td>Сверить commit, digest, omit и генератор</td></tr><tr><td>Компонент есть в SBOM, но omitted при install</td><td>Он разрешён и описан документом</td><td>Попал ли на диск и в runtime-образ</td><td>Проверить build-команду и финальный слой</td></tr><tr><td>Компонент достижим, но опасная ветка выключена</td><td>Путь существует при определённом условии</td><td>Флаг и входы в deployed окружении</td><td>Проверить условие smoke-тестом</td></tr><tr><td>Есть только локальный <code>npm audit</code></td><td>Registry вернул результат для локального дерева</td><td>Состояние конкретного deployed artifact</td><td>Повторить для candidate и привязать к digest</td></tr></tbody></table></div>\n<h2>Почему SBOM не закрывает достижимость</h2>\n<p>SBOM — инвентарная модель компонентов и отношений между ними. Она помогает сопоставлять компонент с базой уязвимостей, лицензий или provenance. Но инвентарь не знает, вызывается ли экспорт, включён ли feature flag, доступен ли endpoint извне и может ли атакующий передать опасный вход. Эти вопросы относятся к анализу кода, конфигурации и runtime.</p>\n<p>Работает и обратная граница: успешный smoke-тест одного endpoint не доказывает отсутствие риска во всех путях. Native addon, bundled dependency, generated bundle, private registry и зависимости операционной системы могут выпасть из npm-сценария. Если инструмент не покрывает такой слой, результат помечают как unknown и назначают отдельную проверку.</p>\n<h2>Критерий готовности и ограничения</h2>\n<p>Решение об обновлении можно принимать, когда для одного кандидата восстановлена цепочка <code>advisory → exact package@version → lockfile commit → SBOM → artifact digest → runtime-сценарий</code>. Для каждого перехода есть файл, ссылка или команда, которую другой инженер может повторить. Lockfile diff просмотрен, clean install завершился ожидаемо, проектные тесты прошли, а неизвестные пути перечислены отдельно.</p>\n<p>Критерий не вычисляет вероятность эксплуатации и не отменяет ручную оценку. Он также не обещает полноту SBOM: она зависит от генератора, режима установки и того, что команда называет артефактом. Для монорепозитория отдельно проверяйте workspace, production-режим и образ, который публикуется. Для менеджеров, отличных от npm, переносите принцип слоёв, но не копируйте команды без сверки с документацией своего инструмента.</p>\n<h2>Порядок действий для команды</h2>\n<ol><li>Сохранить ID advisory, источник, affected range, время получения и имя пакета.</li><li>Проверить exact package и version в lockfile кандидата, затем выполнить <code>npm explain</code> и зафиксировать путь от direct dependency.</li><li>Убедиться, что кандидат собирается теми же Node/npm и установочными флагами, что и pipeline.</li><li>Сгенерировать SBOM после clean install, связать его с commit и digest выпуска, сохранить формат и версию генератора.</li><li>Сравнить lockfile, SBOM и финальный образ: версии, integrity, registry, optional/dev-границы и bundled-файлы.</li><li>Отдельно проверить достижимость: entry point, import, conditional export, feature flag, конфигурацию и контролируемый вход.</li><li>Запустить тесты и runtime smoke для затронутого сценария, затем записать решение, оставшиеся unknown и rollback-артефакт.</li></ol>\n<p>Если отсутствует хотя бы одно звено, корректный статус — «требует дополнительной проверки». Такая формулировка сохраняет владельца и следующий шаг. Она точнее, чем «ложное срабатывание» при несовпавшей версии и чем «безопасно» при одном зелёном отчёте.</p>\n<h2>Проверяемые источники</h2><ul><li><a href=\"https://docs.npmjs.com/cli/v12/configuring-npm/package-lock-json\" target=\"_blank\" rel=\"noopener noreferrer\">npm Docs: package-lock.json</a> — точное дерево, поля <code>resolved</code> и <code>integrity</code>, а также границы lockfile.</li><li><a href=\"https://docs.npmjs.com/cli/v12/commands/npm-ci\" target=\"_blank\" rel=\"noopener noreferrer\">npm Docs: npm ci</a> — clean install и проверка согласованности manifest с lockfile.</li><li><a href=\"https://docs.npmjs.com/cli/v12/commands/npm-explain\" target=\"_blank\" rel=\"noopener noreferrer\">npm Docs: npm explain</a> — вывод цепочки зависимостей, из-за которой пакет установлен.</li><li><a href=\"https://docs.npmjs.com/cli/v12/commands/npm-audit\" target=\"_blank\" rel=\"noopener noreferrer\">npm Docs: npm audit</a> — отчёт registry об известных уязвимостях и его ненулевой exit code при найденной проблеме.</li><li><a href=\"https://docs.npmjs.com/cli/v12/commands/npm-sbom\" target=\"_blank\" rel=\"noopener noreferrer\">npm Docs: npm sbom</a> — генерация SBOM в SPDX или CycloneDX и режим <code>package-lock-only</code>.</li><li><a href=\"https://www.ntia.gov/report/2021/minimum-elements-software-bill-materials-sbom\" target=\"_blank\" rel=\"noopener noreferrer\">NTIA: The Minimum Elements for an SBOM</a> — официальное определение SBOM как записи компонентов и связей поставки.</li><li><a href=\"https://spdx.dev/use/specifications/\" target=\"_blank\" rel=\"noopener noreferrer\">SPDX: Specifications</a> — официальный открытый стандарт и актуальные версии формата.</li></ul>"
|
||
}
|