Руководства по безопасности
osv-scanner на практике — pnpm, игнорирование CVE, офлайн-режим и типичные ошибки
Вопросы второго дня: поддержка pnpm-lock.yaml, игнорирование одной CVE через osv-scanner.toml, три офлайн-флага и ошибка, которую они вызывают, коды завершения 0/1/127/128 в CI и флаги, переименованные при переходе с v1 на v2.
Для кого: для тех, кто уже установил osv-scanner и столкнулся с трудностями. Здесь разобраны вопросы второго дня, а не установка. Всё изложенное следует официальной документации.
Указать lock-файл
# a single lockfile
osv-scanner scan source -L pnpm-lock.yaml
# walk a directory
osv-scanner scan source -r ./my-project/Если формат нельзя определить по имени файла, добавьте перед путём нужный парсер:
# parse an oddly named file as requirements.txt
osv-scanner scan source -L 'requirements.txt:/path/to/extra-requirements.txt'
# path containing a colon — lead with a bare ':' to keep filename inference
osv-scanner scan source -L ':/path/to/my:projects/package-lock.json'Почему lock-файл, а не манифест
Манифест (package.json и подобные) фиксирует диапазон, который вы запросили, а не то, что реально установлено. Lock-файл содержит разрешённые точные версии, поэтому скан совпадает с тем, что реально есть в дереве. Судить по диапазону и надеяться — гораздо более слабое доказательство.
Как проверить свою конфигурацию
Если результаты выглядят неправильно, пройдите эти пять проверок, прежде чем добавлять новые флаги. Каждая только осматривает ваш собственный проект.
Проверьте версию
osv-scanner --version. v2, выпущенная в 2025 году, изменила вид команд и названия ряда флагов, поэтому инструкции, написанные для v1, часто не работают как есть. Официальная документация теперь ориентирована на v2.Проверьте, что он прочитал
--format json --all-packages (--all-packages работает только с выводом JSON).Проверьте код завершения
echo $? в bash или $LASTEXITCODE в PowerShell. Значения — в таблице ниже.Проверьте, что конфигурация реально применилась
osv-scanner.toml применяется только к файлам в той директории, где он лежит; на дочерние директории он не распространяется. В монорепозитории, сканируемом с -r, кладите его рядом с каждым lock-файлом или передайте один файл на всё через --config.Проверьте, где лежит офлайн-база и насколько она стара
OSV_SCANNER_LOCAL_DB_CACHE_DIRECTORY, если переменная задана, иначе из пользовательского каталога кэша, затем из системного временного каталога. Внутри файлы лежат по схеме osv-scanner/<ecosystem>/all.zip. Если даты их изменения старые, то и ваш вердикт устарел.Коды завершения и как CI должен их обрабатывать
| Код завершения | Официальное значение | В CI |
|---|---|---|
0 | Пакеты найдены, известных уязвимостей не совпало | Проходит |
1 | Пакеты найдены, уязвимости совпали | Падает (находка: исправьте или проигнорируйте со сроком) |
127 | Общая ошибка | Падает (сломался сам скан) |
128 | Пакеты не найдены (ничего читаемого не подхвачено) | Падает (исправьте путь или добавьте lock-файл) |
Документация резервирует 1–126 для исходов, связанных с результатами, а 129–255 — для ошибок, не связанных с результатами. Если разделить обработку в CI на три случая — 0 проходит, 1 — находка, всё остальное — сломанный скан, — сбои диагностируются гораздо быстрее.
Игнорирование конкретной уязвимости
Положите osv-scanner.toml в сканируемую директорию:
[[IgnoredVulns]]
id = "GHSA-xxxx-xxxx-xxxx"
ignoreUntil = 2026-12-31
reason = "Feature is not used and the code path is unreachable; re-evaluate at the December inventory"id(обязательно): идентификатор уязвимостиignoreUntil(необязательно): после этой даты она снова сообщаетсяreason(необязательно): всё равно пишите — для следующего человека, которым обычно оказываетесь вы
--config=/path/to/config.toml переопределяет файлы в директориях и действует везде. Учтите, что игнорирование уязвимости игнорирует и те, что считаются её псевдонимами (aliases).
Задавайте каждому исключению срок, а не удаляйте навсегда
Запись без ignoreUntil — это пункт, который исчезает навсегда и к которому никто не возвращается. Наше правило: у каждого исключения есть срок и причина — когда дата проходит, оно возвращается, поэтому отложенное решение нельзя тихо забыть. Бессрочные исключения, добавленные, чтобы заглушить CI, — именно то, что через полгода превращается в инцидент.
Офлайн-режим: три флага, три задачи
Здесь больше всего путаницы.
| Флаг | Что делает |
|---|---|
--offline | Полностью офлайн. Сверяет с ранее загруженной локальной базой, не обновляет её и никуда не отправляет информацию о проекте или зависимостях |
--offline-vulnerabilities | Офлайн только сопоставление с уязвимостями; остальная работа (например, разрешение транзитивных зависимостей) может по-прежнему использовать сеть |
--download-offline-databases | Разрешает загрузку/обновление локальной базы — действует только вместе с офлайн-флагом |
--offline
полностью офлайн; база не обновляется, наружу ничего не отправляется
--offline-vulnerabilities
локально только сопоставление; остальная работа может использовать сеть
--download-offline-databases (сам по себе не действует)
нужен один из флагов выше → в одиночку вы получите "databases can only be downloaded when running in offline mode"
Ошибки и их причины
databases can only be downloaded when running in offline mode→--download-offline-databasesпередан сам по себе; рядом нужен офлайн-флаг--offlineбез локальной базы → вы получите ошибку о том, что база отсутствует; первый запуск должен её загрузитьno package sources found→ по этому пути ничего не распознано как lock-файл или манифест; укажите файл через-Lили обойдите дерево с-r
Как выбрать без догадок
- Запуск в изолированной сети →
--offline(плюс--download-offline-databasesпри первом запуске) - Локальными должны остаться только данные об уязвимостях →
--offline-vulnerabilities - Зафиксировать, где лежит база → переменная окружения
OSV_SCANNER_LOCAL_DB_CACHE_DIRECTORY - Отладка → сначала запустите без флагов, затем сужайте
Когда не работают флаги из статьи про v1
Многие результаты поиска написаны для v1. Основные изменения из официального руководства по миграции:
| v1 | v2 |
|---|---|
--experimental-offline | --offline |
--experimental-download-offline-databases | --download-offline-databases |
--experimental-call-analysis | --call-analysis |
--experimental-licenses и связанные | объединены в --licenses |
--docker / -D (контейнеры) | osv-scanner scan image <image> |
--json | --format=json |
--skip-git | --include-git-root (смысл обратный) |
osv-scanner <dir> по-прежнему работает как сокращение для osv-scanner scan source <dir>. Если в скриптах писать scan source полностью, намерение будет понятно следующему читателю.
Частые ошибки и как их исправить
Частые ошибки
- CI считает успехом «любой код завершения, кроме 1»
|| trueдобавлен, чтобы сбои исчезли- Один
osv-scanner.tomlв корне репозитория, который якобы покрывает подкаталоги - Закоммичен только
package.json, без lock-файла - Предположение, что
--offlineвсё равно разрешает транзитивные зависимостиpom.xml - Исключения без
ignoreUntil
Исправление
- Пропускать только 0, чтобы 128 (ничего не прочитано) не стал зелёным
- Глушить отдельные находки исключениями со сроком
- Класть файл в каждую директорию или применять один везде через
--config - Коммитить lock-файл (для JavaScript:
package-lock.json,pnpm-lock.yaml,yarn.lockилиbun.lock) - Офлайн разрешение транзитивных зависимостей отключено; если сеть разрешена, используйте
--offline-vulnerabilities - Всегда добавлять дату и причину и заново решать, когда дата наступит
Maven pom.xml не перечисляет все разрешённые версии так, как lock-файл. По умолчанию osv-scanner вычисляет транзитивный граф с помощью резолвера deps.dev (или Maven Central с --data-source=native). Для этого нужна сеть, и при --offline и --no-resolve этот шаг пропускается. При полностью изолированном запуске ожидайте, что результаты покрывают только зависимости, прямо записанные в pom.xml.
Реальный пример: почему на вопрос о Log4Shell было трудно ответить
Когда в декабре 2021 года появилась Log4Shell (CVE-2021-44228), многие организации не могли быстро сказать, затронуты ли они. Сами они Log4j не добавляли; он приходил через фреймворки и другие библиотеки. Рекомендации CISA просили организации полностью перечислить установленное ПО и сравнить его со списком затронутого ПО.
Применительно к osv-scanner это значит проверить две вещи:
- Транзитивные зависимости действительно читаются: lock-файлы там, где они есть в экосистеме, а для Maven — запуск с включённым разрешением (не офлайн, не
--no-resolve) - Перечень действительно содержит библиотеки, приходящие косвенно, — выведите его через
--format json --all-packagesи посмотрите
Сможете ли вы ответить на вопрос «затронуты ли мы?» за минуты в день раскрытия, зависит от того, насколько правильно этот перечень собран заранее.
Чем он отличается от соседних инструментов
| Инструмент | Какие данные читает | В чём силён |
|---|---|---|
| osv-scanner | OSV.dev | По разным экосистемам, по lock-файлам, может работать офлайн |
npm audit / pnpm audit | База уведомлений npm | Сразу для npm-проектов, ничего не нужно устанавливать дополнительно |
| Dependabot | GitHub Advisory | Открывает PR с обновлениями — предлагает исправление, а не только находку |
| Trivy | Несколько (включая OSV) | Образы контейнеров и пакеты ОС, а также зависимости приложения |
Ошибка — выбрать ровно один. Разные базы показывают разное. Мы запускаем pnpm audit как ежедневную автоматическую проверку и используем osv-scanner для проверок по нескольким lock-файлам.
Взгляд этого сайта: сканер не скажет, что ваша фиксация устарела
Одна проблема проявляется только в реальной эксплуатации. Когда вы фиксируете транзитивную зависимость на точной версии через overrides, этот пакет перестаёт получать патч-обновления — а сканер подаёт голос, только когда существует известная уязвимость. Поэтому состояние «версия, которую вы заморозили, уже старая» никто не сообщает. Мы столкнулись с этим трижды, прежде чем добавили собственную ежедневную проверку, сравнивающую каждую фиксацию с новейшим патчем в её основной линии. Сканер показывает известные уязвимости сегодня; условия, которые, вероятно, оставят вас уязвимыми позже, требуют другого механизма.
Источники
- Документация OSV-Scanner — Usage / Offline Mode / Configuration
- Supported Artifacts and Manifests
- Документация OSV-Scanner — Output (форматы и коды завершения) / Migration Guide (с v1 на v2)
- CISA — Apache Log4j Vulnerability Guidance
Читать дальше
- Начало работы: установка и использование osv-scanner
- Приоритизация: что исправлять в первую очередь (CVSS, EPSS, KEV)
- Инцидент: Log4Shell (когда транзитивные зависимости скрывали, что используется)
- Практика: практика реагирования на уязвимости / защита от червей в цепочке поставок npm
FAQ
QПоддерживает ли osv-scanner pnpm-lock.yaml?
Да. Поддерживаются lock-файлы npm, yarn и pnpm. Укажите файл напрямую через `osv-scanner scan source -L pnpm-lock.yaml` или обойдите дерево с `-r`.
QКак игнорировать одну CVE?
Положите `osv-scanner.toml` в сканируемую директорию и добавьте запись `[[IgnoredVulns]]` с `id`. Можно также задать `ignoreUntil` (дату окончания) и `reason`, чтобы исключение фиксировало, когда и почему, а не исчезало навсегда. Передача `--config=/path/to/config.toml` переопределяет файлы в директориях и действует глобально.
QПочему я получаю «databases can only be downloaded when running in offline mode»?
Потому что `--download-offline-databases` действует только вместе с офлайн-флагом — сам по себе он не используется. Сочетайте его с офлайн-флагом, если хотите, чтобы локальная база загружалась в рамках запуска.
QЕсли у меня уже есть npm audit и Dependabot, нужен ли osv-scanner?
Они читают разные базы данных и покрывают разное. npm audit использует базу уведомлений npm и работает только с npm; Dependabot в основном предлагает обновления на GitHub; osv-scanner читает OSV.dev по разным экосистемам (npm, PyPI, Go, Maven и др.), работает по lock-файлам и может работать полностью офлайн. То, что один находит что-то, что пропускает другой, — нормально, поэтому считайте их разными задачами, а не выбирайте один.
QКак CI должен обрабатывать коды завершения osv-scanner?
По официальной документации: 0 — пакеты найдены, и ни один не совпал с известной уязвимостью, 1 — найдены уязвимости, 127 — общая ошибка, 128 — пакеты вообще не найдены. Считайте успехом только 0. Правило вида «всё, кроме 1, проходит» превращает 128 — когда ничего не было просканировано — в зелёную галочку.
QПочему не работают --experimental-offline или --docker из старых статей?
В v2 они были переименованы или перенесены. Официальное руководство по миграции сопоставляет --experimental-offline с --offline, --experimental-download-offline-databases с --download-offline-databases, --docker с командой `osv-scanner scan image`, а --json с `--format=json`. Сначала выполните `osv-scanner --version`, чтобы узнать, какая у вас основная версия.