{ "index": 171, "slug": "editorial-2023-04-practice-dependency-security", "title": "Advisory зависимости: как доказать, что сигнал относится к вашему выпуску", "excerpt": "Совпадение package и версии с advisory ещё не доказывает риск в приложении. Разбираем lockfile, SBOM, достижимость, обновление и критерий готовности без выдуманных production-выводов.", "contentHtml": "

Сканер сообщает: пакет совпал с advisory. Команда видит имя и версию, ставит задачу «срочно обновить» и меняет dependency. Через час lockfile разрастается, сборка падает, а никто не может ответить на главный вопрос: этот пакет попал в поставленный артефакт и был ли достижим уязвимый код? Цена ошибки двойная. Ложная тревога задерживает релиз и создаёт шум. Непроверенный сигнал оставляет настоящий риск без владельца.

\n

Тезис простой: advisory — это вход для расследования, а не вердикт о приложении. Сначала разделите четыре факта: внешний advisory, точную запись в lockfile, компонент в SBOM и путь от entry point до нужного кода. Потом проверяйте обновление отдельными gates. Пока связь между этими фактами не доказана, корректный статус — «требует проверки», а не «безопасно» и не «уязвимо в production».

\n

Что именно фиксирует каждый артефакт

\n

Advisory сообщает, какие имена и диапазоны версий описывает источник. Он не знает конфигурацию вашего сервиса. Manifest показывает намерение автора: прямую зависимость и допустимый range. Lockfile показывает дерево, которое resolver выбрал для конкретного состояния проекта. Это важный снимок, но не журнал уже запущенного процесса.

\n

SBOM описывает компоненты и связи выбранного артефакта. Он отвечает на вопрос «что заявлено в этом build», если документ действительно связан с commit и digest. Runtime evidence отвечает на другой вопрос: что загрузил конкретный процесс. Эти слои можно сопоставить, но нельзя заменить один другим. Наличие строки в lockfile не доказывает наличие строки в образе. Наличие компонента в SBOM не доказывает вызов уязвимой функции.

\n
Разделяйте наблюдение, вывод и действие
СимптомПричинаПроверкаДействие
Advisory совпал с package@versionВнешний сигнал приняли за факт о сервисеСохранить URL, дату, диапазон и источникОткрыть triage, не объявляя production-уязвимость
Пакет есть в lockfileResolved tree смешали с deployed artifactНайти путь в дереве и сверить build identityПроверить SBOM того же артефакта
Пакет есть в SBOMInventory приняли за runtime traceСверить компонент с образом и режимом запускаПроверить импорт или загрузку в нужной конфигурации
Прямого импорта нетЗабыли транзитивный, optional или dynamic pathПроверить graph, plugin registration и feature flagОписать достижимость как гипотезу с методом проверки
После update изменилось много записейResolver пересобрал дерево, а scope не зафиксировалиРазобрать lockfile diff и peer/optional branchesСузить изменение или отдельно подтвердить совместимость
\n

Минимальная цепочка доказательств

\n

Начните с exact package и version. Запишите commit, в котором возник сигнал, и место записи в lockfile. Если пакет транзитивный, сохраните parent path: без него невозможно понять, какая прямая зависимость привела компонент в дерево. Затем найдите SBOM, созданный для candidate build. У него должен быть устойчивый идентификатор: commit, image digest или иной идентификатор артефакта. Файл с названием sbom.json без такой связи — только неподтверждённый документ.

\n

После этого сформулируйте путь достижимости. Не пишите «пакет используется». Пишите: «entry point A при конфигурации B импортирует модуль C, который вызывает ветку D». Для статического графа это возможная связь. Для теста — путь выбранного сценария. Для trace — наблюдение одного запуска. У каждого метода есть границы. Ни один метод сам по себе не перечисляет все платформы, флаги и входы.

\n
advisory URL\n  -> package@version\n  -> lockfile path\n  -> SBOM component + artifact digest\n  -> entry point + configuration\n  -> reachability evidence\n  -> decision with owner
\n

Если звено отсутствует, обозначьте его как unknown. Не заполняйте пробел догадкой. Такой формат помогает review: следующий человек видит не только вывод, но и место, где доказательство заканчивается.

\n

Учебный пример: совпадение строк не равно уязвимости

\n

Ниже приведён ограниченный учебный пример. Имена demo-service, demo-parser и SYNTHETIC-ADVISORY-001 вымышлены. Они не взяты из registry, scanner, lockfile, SBOM или production trace. Код только сравнивает заранее заданные значения в памяти. Он не устанавливает пакет, не строит image и не запускает приложение.

\n
const input = {\n  advisory: { id: 'SYNTHETIC-ADVISORY-001', package: 'demo-parser', version: '1.0.0' },\n  lockfile: [{ package: 'demo-parser', version: '1.0.0' }],\n  sbom: [{ package: 'demo-parser', version: '1.0.0' }],\n  entryPoint: 'demo-http-handler',\n  path: 'demo-http-handler -> demo-shell -> demo-parser'\n};\n\nconst listedInLockfile = input.lockfile.some((x) =>\n  x.package === input.advisory.package && x.version === input.advisory.version\n);\nconst listedInSbom = input.sbom.some((x) =>\n  x.package === input.advisory.package && x.version === input.advisory.version\n);\n\nconsole.log({ listedInLockfile, listedInSbom,\n  reachability: 'not-assessed-by-example',\n  vulnerability: 'not-determined-by-example'\n});
\n

Даже если обе проверки вернут true, пример доказывает только совпадение полей в двух массивах. Он не доказывает, что demo-parser попал в образ, был загружен процессом или достиг уязвимой функции. Отрицательный путь важнее положительного: отсутствие записи в lockfile не доказывает отсутствие компонента в другом build, а наличие записи не доказывает runtime-достижимость. Учебный PASS нельзя прикладывать к production ticket как результат сканирования.

\n
\"Схема
Схема разделяет внешний advisory, инвентарь компонентов и гипотезу достижимости. Иллюстрация не содержит данных production и не заменяет evidence проекта.
\n

Порядок проверки

\n
  1. Зафиксируйте сигнал. Сохраните advisory URL или идентификатор базы, дату, package, точную версию и затронутый диапазон. Не переписывайте формулировку источника в более сильный вывод.
  2. Найдите baseline. Назовите commit и lockfile, которые относятся к проверяемому build. Для транзитивного пакета сохраните полный путь от direct dependency.
  3. Сверьте инвентарь. Найдите SBOM candidate artifact и проверьте его provenance: commit, digest, формат и генератор. Если связь с артефактом не доказана, статус SBOM — «не подтверждён».
  4. Опишите путь. Укажите entry point, режим, feature flag, dynamic import или plugin registration. Для каждого утверждения назовите метод: граф, тест, trace или ручная проверка.
  5. Проверьте отрицательную ветку. Убедитесь, что пакет не только отсутствует в прямом импорте, но и не приходит через другой parent, optional dependency, build-time branch или старый образ.
  6. Сформируйте candidate update. Запишите from/to version, ожидаемый lockfile diff, peer dependencies, optional branches, lifecycle scripts и rollback на baseline.
  7. Пройдите gates. Выполните чистую установку по lockfile, targeted tests и контролируемый runtime smoke в названном окружении. Сохраните ссылки на результаты каждого gate.
  8. Примите решение. Закройте задачу только с доказанными границами: исправлено и проверено, не найдено в конкретном артефакте или требует дополнительного project review.
\n

Почему быстрое обновление часто не является исправлением

\n

Изменение одной строки в manifest может перестроить транзитивное дерево. Меняются peer dependencies, optional packages, integrity, module format и lifecycle scripts. Поэтому смотрите не только на целевую версию, но и на весь lockfile diff. Если diff неожиданно широк, сначала объясните каждое изменение. Большой diff не делает update неправильным, но увеличивает объём доказательств.

\n

Команда чистой установки проверяет install contract для конкретного lockfile. Она не доказывает старт сервиса, работу native addon, browser bundle или вызов нужной функции. Тест проверяет выбранные сценарии. Smoke показывает поведение названного окружения. Только вместе эти результаты дают основание принять выпуск. Если smoke выполнить нельзя, это ограничение процесса, а не доказательство совместимости.

\n

Ограничения

\n

Описанная схема не вычисляет exploitability и не заменяет security research. Она не знает private registry, ОС-зависимости, контейнерные слои, generated code, runtime flags и все возможные входы. SBOM может быть неполным. Статический граф может включать недостижимую ветку. Trace может пропустить редкий сценарий. Поэтому вывод всегда должен содержать scope: какой commit, build, режим и метод проверены.

\n

Не закрывайте alert фразой «пакет не импортируется напрямую». Не закрывайте его и фразой «версия обновлена». В первом случае остаются транзитивные и динамические пути. Во втором остаются новый graph, совместимость и факт выпуска. Если доказательств не хватает, оставьте владельца и следующий проверяемый шаг.

\n

Критерий готовности

\n

Разбор готов, когда другой инженер без устного контекста может восстановить цепочку: advisory → exact package@version → lockfile path → SBOM и immutable artifact → entry point/configuration → evidence достижимости → результаты install, tests и smoke → решение и rollback. Каждый результат имеет ссылку или явно отмечен как unknown. Ни один учебный PASS не выдан за production-факт. Если хотя бы одно звено не подтверждено, задача не закрыта как безопасная: она остаётся ограниченным расследованием с назначенным владельцем.

\n

Проверяемые источники

\n" }