Сканирование уязвимых зависимостей (SCA): Dependabot, osv-scanner, govulncheck

SCA на практике: почему Dependabot показывал 0, а osv-scanner нашёл полсотни; чем различаются базы advisory (GitHub Advisory, OSV, Go vuln DB, RustSec) и методы (presence-based против call-graph govulncheck); плейбук починки транзитивных CVE по экосистемам (Go, Maven, Python, Rust) и честная работа с no-fix и ложными срабатываниями

Репозиторий показывал 0 открытых Dependabot-алертов — зелёная галочка, спокойствие. Тот же код, прогнанный через osv-scanner, дал больше полусотни уязвимых пакетов, среди них high-severity. Оба инструмента не врут — они отвечают на разные вопросы: из разной базы advisory, по разной глубине дерева зависимостей, разным методом. «Ноль у Dependabot» — это не «чисто», а «чисто в пределах того, что Dependabot вообще смотрит на этом репозитории». Статья — про то, как устроен SCA (Software Composition Analysis), почему инструменты расходятся, и как реально закрывать уязвимости в зависимостях, включая транзитивные, которые нельзя «просто поднять на версию выше».

Схема: SCA-конвейер — манифесты зависимостей проходят через сканеры (Dependabot/osv-scanner/govulncheck), сверяются с базами advisory (GitHub Advisory, OSV, Go vuln DB, RustSec) и дают три исхода: закрыть бампом, нет фикса, ложное срабатывание

Смежная тема — провенанс и целостность цепочки поставки (SBOM, подпись образов)Скоро: там про «из чего собран артефакт и точно ли это мы его собрали», здесь — про «нет ли в этом составе известных дыр и как их закрыть».

В статье

Что такое SCA — и чего он не делает

SCA (Software Composition Analysis) как дисциплина шире одного скана: это инвентаризация состава (что вообще у вас в дереве), лицензионный анализ, policy-compliance и — интересующий нас здесь срез — управление уязвимостями. В этой статье под SCA я имею в виду именно последнее: есть ли в ваших зависимостях компоненты с известными уязвимостями — теми, на которые уже выпущен идентификатор (CVE, GHSA, RUSTSEC, GO-…) и запись в базе advisory. Это скан чужого кода, который вы тянете, а не своего.

Важно держать SCA на своём месте среди других проверок, чтобы не считать зелёный SCA за «безопасно»:

  • SAST анализирует ваш исходник на паттерны уязвимостей (см. secure coding и инъекцииСкоро);
  • DAST бьёт по запущенному сервису снаружи;
  • secret-scan ищет утёкшие ключи;
  • SBOM и подпись отвечают за состав и происхождение артефакта;
  • SCA — известные CVE в зависимостях.

Чего SCA не гарантирует: 0-day без публичного advisory, логические дефекты, неизвестный вредонос (typosquatting до момента, пока его не занесли в базу). Оговорка про вредонос: SCA не значит «слеп к малвари» — OSV агрегирует в том числе OpenSSF Malicious Packages, поэтому уже каталогизированный вредоносный пакет (даже без CVE) sca-сканер найдёт; не находит он лишь то, чего ещё нет в базе. SCA — необходимая, но не достаточная линия: он закрывает «известное плохое», и только его.

Инструменты и их базы

Ключ к тому, почему инструменты расходятся, — они смотрят в разные базы и разным методом.

Инструмент База advisory Метод Охват
Dependabot GitHub Advisory DB presence (по dependency graph) только поддерживаемые манифесты и зависимости, попавшие в dependency graph дефолтной ветки
osv-scanner OSV (агрегатор: GitHub Advisory + Go vuln DB + RustSec + PyPA + …) presence; для Go на собирающихся модулях — авто-reachability (experimental_analysis) lock-файлы и манифесты (go.mod, pom.xml, requirements.txt, …), много экосистем
govulncheck Go vuln DB call-graph только Go, но флагует лишь реально вызываемые уязвимые символы
cargo audit RustSec presence (по Cargo.lock) только Rust
Trivy / Grype своя + NVD/GHSA presence образы, ФС, манифесты

Оговорки к охвату, чтобы не переоценивать «видит всё»: osv-scanner читает не только lock-файлы, но и манифесты (pom.xml, requirements.txt, go.mod), и «всё дерево» не гарантировано — например, Maven test-scope зависимости не поддерживаются, а резолюция через deps.dev может отличаться от вашего private registry и активных профилей. И про метод — тут у osv-scanner 2.4.0 есть нюанс, который легко описать неточно. Базово это presence-скан, но для Go на собирающихся модулях он делает анализ достижимости по умолчанию: в JSON-выводе Go-записи получают поле experimental_analysis.called (проверено на нашем снимке — для NATS x/cryptoGO-2026-5932: called=false, флаг --call-analysis при этом не передавался). В человекочитаемой таблице called:false он прячет, поэтому кажется «чистым presence». Для не-Go экосистем и для не-собирающихся модулей — presence. Итог: у osv-scanner reachability для Go есть и без явного флага, но полноценный call-graph-инструмент — это govulncheck (см. края и govulncheck-nats.txt в снимке).

Два принципиальных различия:

  1. База. OSV агрегирует несколько источников — GitHub Advisory DB, Go vuln DB, RustSec, PyPA и другие. Это не строгое надмножество GitHub Advisory DB: импорт может задерживаться или отбраковываться по quality-bar, так что «в OSV всегда есть всё, что в GitHub» — неверно. Но на практике запись GO-2026-… или RUSTSEC-…, которой нет в GitHub Advisory DB, у Dependabot не всплывёт, а у osv-scanner — да.
  2. Метод. presence-based (Dependabot, osv-scanner) флагует пакет по факту его наличия в дереве. call-graph (govulncheck) идёт дальше: уязвимость в подпакете, который вы не импортируете, он не покажет — это радикально режет ложные срабатывания в Go. Но call-graph — не гарантия отсутствия риска: вызовы через рефлексию и unsafe он не видит (возможен false negative), а интерфейсные и через указатель-на-функцию — анализирует консервативно. «govulncheck молчит» означает «на статически видимом графе символ не достижим», а не «уязвимости точно нет».

Ставится osv-scanner как обычный Go-бинарь:

go install github.com/google/osv-scanner/v2/cmd/osv-scanner@latest
osv-scanner scan --recursive .

Почему «ноль у Dependabot» обманчив

История с конкретного репозитория. Стартовое состояние по Dependabot — сотни алертов; сводку удобно снять через API:

gh api -X GET repos/OWNER/REPO/dependabot/alerts \
  -f state=open -f per_page=100 --paginate \
  | jq -r '.[].security_advisory.severity' | sort | uniq -c
329 open  →  136 critical, 60 high, 124 moderate, 9 low

136 critical свернулись всего в три Go-пакета — golang.org/x/crypto (SSH-CVE, фикс 0.52.0), jackc/pgx/v5 (memory-safety, фикс 5.9.x) и google.golang.org/grpc (обход авторизации, фикс 1.79.3). После адресных бампов по всем затронутым go.mod и push Dependabot пересканировал ветку и показал 0. Зелёная галочка.

Дальше — контрольный прогон osv-scanner по тем же веткам. И вот тут «ноль» рассыпался:

PyPI   urllib3 1.26.20   → 10 advisory (до 8.9), фикс 2.7.0   requirements.txt
PyPI   idna 3.9.0        → фикс 3.15                          requirements.txt
Maven  httpclient5 5.6   → фикс 5.6.1 (high)                  pom.xml (транзитив)
Maven  io.netty:*        → десятки advisory                   pom.xml (транзитив)
Maven  jackson-databind  → серия advisory                     pom.xml (транзитив)
crates rsa 0.9.10        → RUSTSEC, фикса нет                  Cargo.lock (транзитив)
...

Почему Dependabot этого не показал (осторожно с формулировками — «просто не включено» тут неточно):

  • Состояние графа для Python. requirements.txt поддерживается dependency graph, а Dependabot alerts считаются по дефолтной ветке. Так что причина не «экосистема не поддерживается», а конкретное состояние: попал ли манифест в граф на нужной ветке и по нужному пути, дошёл ли до alerts. У нас пласт urllib3/idna не всплыл — это надо диагностировать в Insights → Dependency graph (какой манифест виден, на какой ветке), а не списывать одной фразой.
  • Глубина/транзитив. Часть транзитива (netty, jackson, httpclient5) видна только при полной submission дерева — если её нет, эти узлы графу неизвестны.
  • Другая база. RUSTSEC-записи и часть Go vuln DB (GO-…) в GitHub Advisory DB на момент прогона отсутствовали — это уже про источник, а не про граф.

Вывод, который стоит закрепить: один сканер — не истина в последней инстанции. Кросс-проверка вторым инструментом с другой базой (osv-scanner поверх Dependabot, govulncheck для отсева ложных) — не паранойя, а базовая гигиена.

Прямая зависимость против транзитивной

Главный водораздел в починке — прямая ли для вас зависимость или транзитивная. И определяется это не наличием строки в манифесте, а тем, зависит ли ваш код от компонента непосредственно: манифесты и lock-файлы этой разницы сами по себе не проводят. Go, например, пишет транзитивы в тот же go.mod с пометкой // indirect; lock-файлы (Cargo.lock, package-lock.json) вообще содержат всё дерево; а constraints/overrides явно называют именно транзитивный пакет, не делая его прямым.

  • Прямая — вы объявили её сами и непосредственно от неё зависите. Это не только «прикладной код импортирует пакет»: сюда же runtime-, build-, plugin- и tool-зависимости (кодогенераторы, линтеры, build-плагины), которые из прикладного кода не импортируются, но объявлены вами напрямую. Чинится очевидно — поднять версию в вашем манифесте.
  • Транзитивная — приезжает через вашу прямую зависимость; вы её не объявляли. Поднять «на версию выше» в обычном смысле негде — её тянет чужой манифест. Три пути:
    1. пин через механизм менеджера (заставить резолвер выбрать безопасную версию);
    2. апгрейд родителя — той прямой зависимости, что тянет дыру, до версии, где она уже пропатчена;
    3. замена родителя — если он заброшен и фикса не будет.

На реальном прогоне подавляющее большинство находок были именно транзитивными. Поэтому дальше — плейбук ровно про этот случай, по каждой экосистеме.

Плейбук починки по экосистемам

Go

Прямые и indirect-пины двигаются одним go get. Важная оговорка: MVS (minimal version selection) выбирает максимальную из минимально требуемых версий в текущем графе, но это не значит, что случайно понизить нельзя — go get pkg@vX умеет и downgrade, и вслед за целевым модулем понизить/удалить связанные (это прямо описано в Go Modules Reference). Поэтому целиться нужно в max(текущая, фикс) и после бампа проверять, что уже-пропатченные соседи не откатились назад.

go get golang.org/x/crypto@v0.52.0
go get github.com/jackc/pgx/v5@v5.9.2
go mod tidy

Две тонкости, на которых легко ошибиться:

  • Одиночная require. Версию нельзя вытаскивать наивным regex по go.mod: пакет в блоке пишется \tpkg vX, а одиночная прямая зависимость — require pkg vX с префиксом. Скрипт, ловивший только блочную форму, пропустил require github.com/jackc/pgx/v5 v5.7.6 и оставил два critical открытыми. Правильно — читать выбранную версию через go list:
go list -m -f '{{ .Version }}' github.com/jackc/pgx/v5
  • Директива go может подняться. Если пропатченные зависимости требуют более свежий язык (у golang.org/x/crypto v0.52.0 в go.mod указано go 1.25.0), go build -mod=mod поднимет строку go в вашем go.mod до этого минимума. Это не шум и не ошибка, а необходимое следствие апгрейда — просто держите в голове, что минимальный тулчейн для сборки вырос.

Отдельно — govulncheck. Он по call-graph отсекает то, что presence-сканеры флагуют зря (подробнее — в разделе про края):

govulncheck ./...

Maven

Транзитив в Maven пинится через <dependencyManagement> — он форсирует версию для всего дерева, не меняя того, кто её тянет:

<dependencyManagement>
  <dependencies>
    <dependency>
      <groupId>org.apache.httpcomponents.client5</groupId>
      <artifactId>httpclient5</artifactId>
      <version>5.6.1</version>
    </dependency>
  </dependencies>
</dependencyManagement>

Для семейств (Netty, Jackson) чище импортировать BOM (netty-bom, jackson-bom) — один пин на весь набор артефактов, чтобы версии не разъезжались. Альтернатива пину — апгрейд самого родителя (клиентской библиотеки), который тянет дыру, если у него есть свежий релиз с уже поднятой транзитивной версией.

Проверять, что пин победил, — по дереву:

mvn dependency:tree | grep httpclient5
# +- org.apache.httpcomponents.client5:httpclient5:jar:5.6.1:compile

Python

Транзитивные urllib3/idna (их тянет, например, requests/клиент) ограничивают. Правильный инструмент для этого — constraints.txt, а не строка в requirements.txt: pip прямо рекомендует constraints для дополнительных ограничений транзитива, потому что запись в requirements.txt превращает пакет в явно устанавливаемую прямую зависимость (вы берёте на себя urllib3 как свою, хотя он транзитив). Constraints же только ограничивают версию, если пакет вообще будет затянут:

# security-ограничения транзитивов: старые urllib3/idna уязвимы
urllib3>=2.7.0,<3
idna>=3.15
pip install -r requirements.txt -c constraints.txt

(В нашем демо-стенде для простоты пины стоят прямо в requirements.txt — это работает и его тоже видит osv-scanner, но в проде чище constraints.txt.)

Осторожно с env-маркерами. У пакета-родителя ограничение может быть условным по версии Python:

urllib3<1.27,>=1.26.19 ; python_version < "3.10"
urllib3!=2.2.0,!=2.2.1,<3,>=1.26.19 ; python_version >= "3.10"

osv-scanner при разборе requirements.txt может слить оба ограничения без учёта маркера и выдать мнимый конфликт >=2.7.0,<3 против <1.27. На деле на Python 3.10+ применяется только вторая строка, и pip install -r резолвит urllib3 2.7.0 без конфликта. Проверять — реальным резолвом, а не доверять предупреждению сканера:

pip install --dry-run -r requirements.txt

Rust

Транзитив из Cargo.lock часто нельзя починить бампом самого крейта — он может быть заброшен и фикса не иметь. Тогда работает апгрейд родителя. Реальный случай: tokio-tar 0.3.1 под advisory без фикса, тянется как dev-dependency через testcontainers. Апгрейд testcontainers-modules уводит дерево на поддерживаемый форк:

cargo tree -i tokio-tar
# tokio-tar v0.3.1
# └── testcontainers v0.23.3
#     └── testcontainers-modules v0.11.6 (dev-dependency)

После testcontainers-modules = "0.15" в Cargo.toml:

cargo tree -i tokio-tar
# error: package ID specification `tokio-tar` did not match any packages
cargo tree -i astral-tokio-tar
# astral-tokio-tar v0.6.3   (advisory фиксит 0.5.6 → 0.6.3 не уязвим)

tokio-tar ушёл из дерева совсем, а пришедший astral-tokio-tar 0.6.3 выше фикс-версии 0.5.6. Проверка — cargo test --no-run (компиляция тестов подтверждает, что API родителя не сломался) и cargo audit.

Честные края: no-fix, ложные, расхождение баз

Не всякий алерт закрывается бампом, и не всякий алерт — реальная дыра. Три ситуации, где нужна голова, а не автопилот.

No-fix advisory — фикса не существует. Пример: rsa (крейт) под RUSTSEC про Marvin timing-attack — исправления нет и не предвидится без потери производительности. Аналогично lz4-java имел два advisory: одно закрылось бампом до 1.8.1, второе (GHSA-cmp6-m4wj-q63q) фикса не имеет. Решения честные: убрать/заменить зависимость, либо осознанный dismiss с обоснованием (dev-only, не на пути недоверенного ввода, риск принят) — но не «замьютили и забыли».

Корректная presence-находка, недостижимая в вашем приложении. Важно не путать это с «ошибкой сканера». GO-2026-5932 на golang.org/x/crypto — про подпакет openpgp («unmaintained, unsafe by design»), затронуты все версии, фикса нет. Presence-сканер сработал правильно: уязвимый модуль в дереве действительно есть. Но подпакет openpgp в стенде не вызывается — и govulncheck по call-graph помещает запись в раздел «modules you require, but your code doesn’t appear to call these» (проверено, govulncheck-nats.txt в снимке). То есть сканер не соврал — это вы, зная, что символ недостижим, осознанно понижаете приоритет находки. Формулировать это как «ложное срабатывание» неточно: находка верная, снижает применимость контекст приложения.

Расхождение диапазонов между базами. Конкретный воспроизводимый случай: GHSA-5jmj-h7xm-6q6v (jackson-databind, case-insensitive deserialization bypass). После бампа до 2.18.9 Dependabot алерт закрыл (по данным GitHub Advisory эта версия — фикс для 2.x-линии). А osv-scanner (проверено на 2.4.0) на той же 2.18.9 продолжает флагать. Разбор записи в OSV точнее, чем «весь 2.x уязвим»: для старого координата com.fasterxml.jackson.core:jackson-databind заведено несколько introduced-диапазонов (2.8.0, 2.19.0, 2.22.0) — и ни у одного из них нет события fixed (есть лишь last_known_affected_version_range). Чистое fixed (3.1.4) кодируется только на новом координате tools.jackson.core:jackson-databind (Jackson 3, пакет переименован). Из-за отсутствующих fixed-событий на старом координате сканер трактует подходящие 2.x-версии как затронутые. Это не «OSV умнее/строже», а именно как закодирован диапазон в конкретной записи и как сканер это интерпретирует: две базы описали один дефект по-разному. Практический вывод: понимать, чью трактовку вы гейтите, и не тянуть в стенд миграцию на мажор ради строки в одной из баз — но и знать, что «зелено у Dependabot» не равно «зелено у OSV».

CI-гейт

Скан имеет смысл в двух точках: до пуша (быстрая обратная связь разработчику) и периодически по ветке/реестру (новые advisory появляются на уже вмёрженный код). Простой гейт на GitHub Actions:

Важно: у google/osv-scanner-action корневой action.yml не исполняемый — это не обычный step-action. Официальный способ — reusable workflow. И режимы нужно разделить: на PR — дифференциальный osv-scanner-reusable-pr.yml (только новые уязвимости относительно базовой ветки, иначе PR будет падать на всём унаследованном долге), на push/schedule — полный osv-scanner-reusable.yml. Пиновать сторонний workflow — по полному commit SHA (версия — в комментарии): tag можно переписать, SHA — нет; это единственный неизменяемый способ подключения (рекомендация GitHub «Security hardening»).

name: sca
on:
  pull_request:
    branches: [main]
  push:
    branches: [main]
  schedule:
    - cron: "0 6 * * 1"   # еженедельно — на новые CVE на уже вмёрженном коде
permissions:
  actions: read
  contents: read
  security-events: write   # выгрузка SARIF в Security-таб
jobs:
  pr-diff:                 # PR: только НОВЫЕ уязвимости vs базовая ветка
    if: github.event_name == 'pull_request'
    uses: "google/osv-scanner-action/.github/workflows/osv-scanner-reusable-pr.yml@9a498708959aeaef5ef730655706c5a1df1edbc2"  # v2.3.8
  full:                    # push/schedule: полный скан всего дерева
    if: github.event_name != 'pull_request'
    uses: "google/osv-scanner-action/.github/workflows/osv-scanner-reusable.yml@9a498708959aeaef5ef730655706c5a1df1edbc2"  # v2.3.8
    with:
      scan-args: |-
        --recursive
        ./

Три правила, чтобы гейт помогал, а не бесил:

  • Severity + fixable — целиком ваша логика поверх. osv-scanner.toml не умеет переопределять severity: он поддерживает игнор по ID, package-overrides и license-overrides — и только. Порог по severity и учёт fixable реализуются обработкой JSON-вывода (--format json) отдельным шагом, который решает exit-code. Отдельно — политика для advisory без CVSS: тот же GO-2026-5932 приходит с severity Unknown, и наивный порог «блокировать от High» его молча пропустит; для Unknown нужно решать явно (ревью/allowlist), а не «ниже порога — значит ок».
  • No-fix ≠ «всегда пропускать». Унаследованный no-fix долг не должен валить каждый PR — да. Но новая no-fix-зависимость, которую добавляет сам PR, наоборот должна блокироваться или требовать явного risk-acceptance: fixable описывает способ устранения, а не допустимость риска. Различайте «старый долг» (не блокируем, ведём в allowlist) и «новый ввод» (блокируем/принимаем осознанно).
  • Allowlist с причиной и сроком. Каждый игнор — с обоснованием и датой пересмотра: в osv-scanner.toml у ignore-записи для этого есть поля reason и ignoreUntil (после этой даты запись снова начинает флагаться). Молчаливый мьют без срока = дыра, про которую забыли.

Верификация починки

Пин в манифесте — это ещё не починка. Нужно подтвердить два факта: менеджер резолвит фикс-версию и проект собирается. Пин, который не победил в дереве (перебит транзитивным требованием) или сломал сборку, — хуже, чем его отсутствие, потому что создаёт ложную уверенность.

  • Резолв: go list -m, mvn dependency:tree, pip install --dry-run, cargo tree — увидеть глазами, что стоит фикс-версия.
  • Сборка: go build ./..., mvn verify, cargo test --no-run — что пин ничего не сломал.

Практические подводные камни самой проверки (собраны на реальном прогоне через WSL):

  • чужой ~/.m2 под root → писать в свежий локальный репо -Dmaven.repo.local=$HOME/.m2-osv;
  • сборка Maven на смонтированном Windows-диске (drvfs) роняет запись target/*.class и mojo-status → копировать стенд на нативную ФС и собирать там; для multi-module — весь реактор, сохранив parent.relativePath;
  • разные release-таргеты стендов (17/21/25) → под каждый нужен свой JDK (portable-дистрибутив ставится без root).

Демо и версии

  • Материал якорится на реальном security-sweep двух репозиториев: github.com/khorost-tech/digital-cookbook (публичный) и digital-cookbook-staging (приватный). Это снимок во времени — счётчики advisory меняются с выходом новых записей, так что цифры ниже привязаны к дате, а не «навсегда».
  • Временная шкала (снимок 2026-07-13):
    • старт: Dependabot по staging — 329 открытых (136 critical, 60 high, 124 moderate, 9 low), critical сворачивались в три Go-пакета (x/crypto, pgx/v5, grpc);
    • после Go/Java/Rust-фиксов и push: Dependabot → 0 в обоих репозиториях;
    • контрольный osv-scanner по тем же веткам нашёл ещё пласт (Python urllib3/idna, транзитивный Maven netty/jackson/httpclient5, Rust rsa);
    • остаточное состояние: в public — 0, в staging держатся 3 high — все по org.lz4:lz4-java (GHSA-cmp6-m4wj-q63q, no-fix), см. раздел про края.
  • Живые примеры (реальные пакеты и исходы):
    • закрыто бампом/пином — Go x/crypto/pgx/x/net/grpc, Maven httpclient5/bcprov/commons-lang3, Python urllib3/idna;
    • нет фикса — Rust rsa (Marvin), lz4-java (второе advisory), tokio-tar (закрыт апгрейдом родителя, не бампом);
    • корректная presence-находка, недостижимая в приложении — Go x/crypto/openpgp (govulncheck кладёт в «не вызывается»); мнимый конфликт самого сканера — Python env-marker.
  • Воспроизводимый публичный снимокdigital-cookbook/security/sca-snapshot/ (ссылка зафиксирована на иммутабельный commit): сканируемый исходник — дерево на commit 76ca051, сами файлы снимка добавлены позже (README с worktree-инструкцией + osv-results.json + govulncheck-nats.txt). На этом снимке (уже после ремедиации) контраст в чистом виде: Dependabot — 0, а osv-scanner2 уникальных package/version, найденных в 3 манифестах, оба «краевые» кейсы статьи (x/crypto openpgp — корректная находка, недостижимая в приложении; jackson-databind 2.18.9 — расхождение баз). Приватная половина «пути 329 → 0» (transitive Maven, Rust rsa) в staging иллюстративна — читателю недоступна, поэтому опорой воспроизводимости служит публичный снимок. Оговорка: OSV — живая база, при повторном прогоне позже числа могут отличаться; зафиксированный результат — в osv-results.json.
  • Точные версии на момент снимка: osv-scanner 2.4.0 (требует Go ≥ 1.26.4 — при GOTOOLCHAIN=auto команда go сама подтягивает более новый toolchain, напр. 1.26.5), govulncheck v1.6.0, cargo-audit 0.22.2, Dependabot (GitHub Advisory DB); тулчейны Go 1.26.3, JDK 17/21/25 (Temurin), Python 3.12, Maven 3.9.16, cargo 1.96.1.
  • Оговорка про Go-пример: полный call-analysis на ORM-стенде (go/orm-gorm-vs-jet) не самодостаточен — требует предварительной PostgreSQL-миграции и codegen (jet .gen), без них падает. Prerequisites — в README стенда. Для presence-скана этого не нужно.
  • SCA зависимостей ≠ проверка stdlib/toolchain. Тот же govulncheck-nats.txt из снимка честно показывает, что код достижимо затронут тремя уязвимостями стандартной библиотеки Go 1.26.3 (GO-2026-5856 crypto/tls, GO-2026-5039 net/textproto, GO-2026-5037 crypto/x509; фиксы в 1.26.4/1.26.5). Это не про манифесты: dependency-remediation завершена (Dependabot 0), но toolchain требует обновления Go — и ловит это govulncheck по версии go, а не osv/Dependabot по зависимостям. Полное «в ноль» = бампы зависимостей плюс свежий Go.
  • Смежное: supply-chain: SBOM и подписьСкоро, secure codingСкоро, CI-пайплайн на GitHub ActionsСкоро.

Документация и первоисточники

Обсуждение в Telegram

Присоединиться →

Комментарии