Files

8 lines
19 KiB
JSON
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
{
"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) =&gt; item.name === advisory.packageName\n &amp;&amp; item.version === advisory.affectedVersion,\n);\n\nconst recorded = components.some(\n (item) =&gt; item.name === advisory.packageName\n &amp;&amp; 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 &gt; dependency-tree.json\n\n# 3. Известные advisory. Ненулевой код возможен при найденных проблемах.\nset +e\nnpm audit --json &gt; audit.json\naudit_status=$?\nset -e\nprintf 'npm audit exit code: %s\\n' \"$audit_status\"\n\n# 4. SBOM именно после этой установки.\nnpm sbom --sbom-format=spdx &gt; 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>"
}