{ "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».
\nAdvisory сообщает, какие имена и диапазоны версий описывает источник. Он не знает конфигурацию вашего сервиса. Manifest показывает намерение автора: прямую зависимость и допустимый range. Lockfile показывает дерево, которое resolver выбрал для конкретного состояния проекта. Это важный снимок, но не журнал уже запущенного процесса.
\nSBOM описывает компоненты и связи выбранного артефакта. Он отвечает на вопрос «что заявлено в этом build», если документ действительно связан с commit и digest. Runtime evidence отвечает на другой вопрос: что загрузил конкретный процесс. Эти слои можно сопоставить, но нельзя заменить один другим. Наличие строки в lockfile не доказывает наличие строки в образе. Наличие компонента в SBOM не доказывает вызов уязвимой функции.
\n| Симптом | Причина | Проверка | Действие |
|---|---|---|---|
| Advisory совпал с package@version | Внешний сигнал приняли за факт о сервисе | Сохранить URL, дату, диапазон и источник | Открыть triage, не объявляя production-уязвимость |
| Пакет есть в lockfile | Resolved tree смешали с deployed artifact | Найти путь в дереве и сверить build identity | Проверить SBOM того же артефакта |
| Пакет есть в SBOM | Inventory приняли за runtime trace | Сверить компонент с образом и режимом запуска | Проверить импорт или загрузку в нужной конфигурации |
| Прямого импорта нет | Забыли транзитивный, optional или dynamic path | Проверить graph, plugin registration и feature flag | Описать достижимость как гипотезу с методом проверки |
| После update изменилось много записей | Resolver пересобрал дерево, а scope не зафиксировали | Разобрать lockfile diff и peer/optional branches | Сузить изменение или отдельно подтвердить совместимость |
Начните с exact package и version. Запишите commit, в котором возник сигнал, и место записи в lockfile. Если пакет транзитивный, сохраните parent path: без него невозможно понять, какая прямая зависимость привела компонент в дерево. Затем найдите SBOM, созданный для candidate build. У него должен быть устойчивый идентификатор: commit, image digest или иной идентификатор артефакта. Файл с названием sbom.json без такой связи — только неподтверждённый документ.
После этого сформулируйте путь достижимости. Не пишите «пакет используется». Пишите: «entry point A при конфигурации B импортирует модуль C, который вызывает ветку D». Для статического графа это возможная связь. Для теста — путь выбранного сценария. Для trace — наблюдение одного запуска. У каждого метода есть границы. Ни один метод сам по себе не перечисляет все платформы, флаги и входы.
\nadvisory 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Ниже приведён ограниченный учебный пример. Имена demo-service, demo-parser и SYNTHETIC-ADVISORY-001 вымышлены. Они не взяты из registry, scanner, lockfile, SBOM или production trace. Код только сравнивает заранее заданные значения в памяти. Он не устанавливает пакет, не строит image и не запускает приложение.
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 как результат сканирования.
Изменение одной строки в 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Описанная схема не вычисляет exploitability и не заменяет security research. Она не знает private registry, ОС-зависимости, контейнерные слои, generated code, runtime flags и все возможные входы. SBOM может быть неполным. Статический граф может включать недостижимую ветку. Trace может пропустить редкий сценарий. Поэтому вывод всегда должен содержать scope: какой commit, build, режим и метод проверены.
\nНе закрывайте alert фразой «пакет не импортируется напрямую». Не закрывайте его и фразой «версия обновлена». В первом случае остаются транзитивные и динамические пути. Во втором остаются новый graph, совместимость и факт выпуска. Если доказательств не хватает, оставьте владельца и следующий проверяемый шаг.
\nРазбор готов, когда другой инженер без устного контекста может восстановить цепочку: advisory → exact package@version → lockfile path → SBOM и immutable artifact → entry point/configuration → evidence достижимости → результаты install, tests и smoke → решение и rollback. Каждый результат имеет ссылку или явно отмечен как unknown. Ни один учебный PASS не выдан за production-факт. Если хотя бы одно звено не подтверждено, задача не закрыта как безопасная: она остаётся ограниченным расследованием с назначенным владельцем.
\n