Уведомление об advisory легко превратить в аварийную задачу одним словом «уязвимость». Пакет совпал по имени и версии, значит его надо немедленно заменить. Проблема начинается, когда после быстрого обновления меняется всё дерево, ломается сборка или остаётся непонятно, был ли компонент вообще в поставленном артефакте. Цена ошибки двойная: можно остановить полезный релиз из-за неподтверждённого сигнала или оставить без владельца компонент, который действительно требует разбора.
Для первой реакции не нужен ни придуманный CVE, ни число из сканера. Нужна короткая запись о том, откуда пришёл advisory, какой exact package и version он называет, где этот package виден в lockfile и где он записан в инвентаре компонентов. Затем отдельно формулируется вопрос о достижимости: может ли путь от конкретного entry point дойти до этого кода в нужной конфигурации. Это четыре разных факта. Их смешивание и делает alert похожим на готовый вердикт.
Advisory задаёт вопрос, а не результат проверки
База advisory хранит сведения о пакетах и затронутых версиях. GitHub в сообщении о своей Advisory Database в 2019 году описывал связь curated advisory с пакетами dependency graph. Из этого не следует, что конкретный сервис загрузил пакет, вызвал уязвимую ветку или выпустил её в production. Advisory полезен как вход в triage: он даёт идентификатор источника и условие для сопоставления. Вердикт о приложении появляется только после проверки его собственных артефактов и поведения.
В обычном Node-проекте manifest отвечает на другой вопрос: какую зависимость автор хотел получить. Lockfile фиксирует дерево, которое разрешил package manager для конкретного состояния. SBOM, Software Bill of Materials, фиксирует инвентарь компонентов и их связи для выбранного build-артефакта. NTIA определяет SBOM как формальную запись о деталях и supply-chain relationships компонентов. Эта запись полезна, когда нужно показать provenance, но сама по себе не наблюдает загруженный модуль в работающем процессе.
| Наблюдение | Что оно подтверждает | Чего оно не подтверждает | Следующее действие |
|---|---|---|---|
| Advisory называет package и version | есть внешний вход для разбора | наличие CVE в вашем коде или выпуске | сохранить URL, дату и scope advisory |
| Lockfile содержит exact package@version | resolver когда-то выбрал эту запись | наличие компонента в deployed artifact | найти путь, по которому запись попала в дерево |
| SBOM содержит component record | в инвентаре есть названный компонент | факт загрузки в runtime | сверить build identity и источник SBOM |
| Есть путь от entry point до пакета | есть гипотеза достижимости для названной конфигурации | вызов уязвимой функции или exploitability | проверить импорт, feature path и входные условия |
| Обновление подготовлено | есть candidate change | совместимость и безопасность выпуска | отдельно пройти install, tests и runtime gate |
Учебный fixture: только контракт synthetic input
Ниже используется детерминированный input с именами demo-service и demo-parser. Идентификатор SYNTHETIC-ADVISORY-001 не является CVE, package names и versions не получены из registry, а suppliedPath не взят из trace. Функция не открывает файл, не вызывает scanner, не запускает npm и не импортирует приложение. Поэтому её удобно выполнить на чистой машине, но её PASS означает только то, что учебные поля не перепутаны.
node web/scripts/upgrade-2023-04.mjs --verify-fixture
# Все значения входа synthetic: это не вывод scanner и не CVE.
# PASS проверяет только контракт in-memory: запись в lockfile/SBOM,
# явно ненаблюдаемую достижимость, named gates и предел rollback.
# PASS не означает, что пакет уязвим, достижим, совместим или безопасно обновлён.
Функция triageSyntheticAdvisory сопоставляет synthetic package и version с двумя заранее заданными списками: lockfile packages и SBOM components. Если запись есть в обоих, она возвращает synthetic-entry-matched и synthetic-component-matched. Она намеренно оставляет vulnerabilityStatus равным not-determined-by-fixture. Даже suppliedPath заканчивается на demo-parser только как договор для review; поле reachability.conclusion остаётся not-assessed-by-fixture. Так пример не делает подмену: совпадение строки не становится доказательством эксплуатации.
Достижимость не равна наличию в дереве
Транзитивная зависимость часто остаётся в lockfile после того, как прямой импорт исчез из исходника. Обратная ситуация тоже возможна: компонент нужен только в development, optional branch, build-time tool или в отключённой функции. Поэтому вопрос «есть ли package?» полезен, но узок. Для достижения кода важны точка входа, mode, loaded configuration, dynamic import, plugin registration и условие, при котором управление доходит до нужной функции. Нельзя честно получить этот набор условий из одной строки lockfile.
Здесь полезно записать путь в форме hypothesis, а не finding: demo-http-handler → demo-shell → demo-parser. Затем рядом назвать, чем он должен быть проверен в настоящем проекте: статическим import graph, unit или integration test, trace разрешённого окружения, ручным запуском с безопасными test data. Результат каждого способа имеет свою границу. Статический граф покажет возможные связи, тест покажет выбранный сценарий, trace покажет наблюдённый запуск. Ни один из них автоматически не перечисляет все конфигурации и входы.
Маршрут: симптом → причина → проверка → действие
- Симптом. В alert есть package и version, но в задаче нет ссылки на lockfile, SBOM, build или entry point. Не пишите «уязвимость в production» до этих данных.
- Причина. Advisory, resolved dependency, inventory и runtime path были записаны как один факт. Из-за этого reviewer не видит, какое утверждение ещё не доказано.
- Проверка. Найдите exact запись в commit lockfile и component record в SBOM, если он привязан к нужному build. Затем опишите один возможный путь от entry point и способ проверить именно его.
- Действие. Создайте небольшой triage record: source advisory, package@version, lockfile location, SBOM provenance, reachability hypothesis, owner и deadline следующей проверки.
- Проверка модели. Запустите
node web/scripts/upgrade-2023-04.mjs --verify-fixture. PASS подтверждает исключительно assertions synthetic контракта и должен оставить vulnerabilityStatus неопределённым. - Решение. Если факты не связываются, оставьте статус «требует project review», а не «ложное срабатывание». Отсутствие доказательства достижимости не становится доказательством отсутствия риска.
Как сделать запись пригодной для следующего человека
Минимальная запись удобнее огромного отчёта, если в ней можно отделить наблюдаемое от предполагаемого. В поле source кладут immutable advisory URL или идентификатор базы. В поле resolved component — package, version и путь в lockfile именно того commit, который проверяется. В provenance SBOM — build ID или другой неизменяемый указатель на артефакт. В reachability — не слово «используется», а точка входа, конфигурация и выбранный метод проверки. В outcome — один из честных статусов: подтверждено в границах метода, не найдено в артефакте или ещё не проверено.
NIST SP 800-218 v1.1 говорит о безопасных практиках разработки и о необходимости работать с рисками в жизненном цикле. Для этой заметки важна не попытка выдать NIST за scanner, а порядок ответственности: входной сигнал надо связать с конкретным компонентом, решением и evidence. Если пакет обновляется, новая запись получает свой lockfile diff, новый inventory и отдельный test plan. Старый alert нельзя закрывать лишь ссылкой на pull request с изменённым диапазоном versions.
Ограничение модели и следующий шаг
Этот fixture не знает реальную advisory database, schema конкретного SBOM, package manager, private registry, исходный код, feature flags, зависимости ОС или runtime. Он не вычисляет vulnerable range, не строит call graph, не запускает приложение и не выносит вердикт «безопасно». Его полезная граница уже видна в output: всё названо synthetic, достижимость не assessed, а решение всегда требует review в настоящем проекте.
Следующий рабочий шаг — выбрать один новый advisory и заполнить описанную запись без чувствительных данных. Если SBOM отсутствует, не рисуйте его задним числом: сначала назовите артефакт, который надо инвентаризировать, и владельца генерации. Если lockfile и SBOM расходятся, не угадывайте, какой прав. Сначала найдите commit и build, из которых каждый получен. После этого уже можно обсуждать обновление как контролируемое изменение, а не как реакцию на страшное слово.
Проверяемые источники
- GitHub Changelog: GitHub Advisory Database, 14 ноября 2019 — первичное сообщение GitHub о базе advisory, сопоставленных с пакетами dependency graph. Оно описывает источник данных, но не подтверждает применимость advisory к конкретному приложению.
- NTIA: The Minimum Elements for a Software Bill of Materials, 12 июля 2021 — официальный первичный документ: SBOM назван формальной записью о компонентах и связях цепочки поставки. Формат записи не доказывает, что компонент загружен в runtime.
- NIST SP 800-218 SSDF Version 1.1, Final, 3 февраля 2022 — официальный финальный документ, доступный до апреля 2023. Он задаёт практики безопасной разработки и работы с компонентами, но не аттестует отдельный выпуск.