Прямой ответ
Trivy — универсальный сканер безопасности (один бинарник, без сервера/агента — «поставил и сканирует» — Aqua-opensource): контейнерные образы (CVE в слоях ОС+языковые пакеты — Python/Node/Go/Java-зависимости — единый проход), файловые системы/репозитории (зависимости без образа — trivy fs . — requirements/pyproject/аудит зависимостей — один инструмент вместо pip-audit+safety+osv-сканеров), конфиги (misconfiguration — Dockerfile/k8s-манифесты/terraform — trivy config . — «FROM :latest», привилегированные поды — харднинг), секреты в коде (встроенный детектор — «ключ в репо» — секреты); базовое использование: trivy image myapp:1.2 (таблица CVE по слоям — severity CRITICAL/HIGH/MEDIUM/LOW — fix-версии где есть), trivy image --severity CRITICAL,HIGH --exit-code 1 myapp:1.2 (порог — CI-гейт), trivy fs --scanners vuln,secret . (репозиторий), trivy config . (IaC/манифесты); главная проблема внедрения — не запуск, а шум: первый прогон на типовом Python-образе — 300–800 находок (базовый слой debian/python — CVE в libc/openssl, половина без фикса — «включили — CI красный навсегда — выключили» — классический провал); рабочая политика (по шагам): (1) начать с CRITICAL + fixable (--severity CRITICAL --ignore-unfixed — «критичное, что лечится обновлением» — 5–20 находок вместо 800 — посильно); (2) обновить базовые образы (multistage/slim — python:3.12-slim против 3.12 — минус сотни CVE в слоях; distroless — ещё меньше — distroless); (3) зафиксировать baseline (.trivyignore — CVE без фикса/неприменимые — с обоснованием и сроком — «ignore без причины — кладбище» — техдолг-дисциплина — каждая строка — комментарий «почему» + дата пересмотра); (4) гейт в CI — на новые находки (baseline-сравнение — «старые 40 живём по плану, новое критичное — блок» — CI-практика — «всё или ничего» не работает на легаси); (5) расписание — ночной скан (новые CVE публикуются ежедневно — «вчерашний чистый образ сегодня критичен» — scheduled workflow — алерт в Alertmanager/оповещения — Dependabot-ритм); формат отчётов — SARIF (--format sarif — GitHub Security tab — находки в PR-ревью — не в логе джобы — review-культура), JSON (своя обработка — метрики тренда — метрики — «число CRITICAL по сервисам» — дашборд безопасности).
Практика: CI-пайплайн и тритмент находок
GitHub Actions-джоба (рабочий каркас): checkout → build образа (или pull — скан без сборки — trivy image registry/app:sha-тег — тег-стратегии — сканируем то, что деплоим, не «локальную сборку») → trivy image --severity CRITICAL --ignore-unfixed --exit-code 1 --format sarif --output trivy.sarif → upload-sarif (Security tab) → отдельная ночная джоба без --exit-code (полная картина — trend-метрики); кэш Trivy-БД (actions/cache на ~/.cache/trivy — база CVE ~500 МБ — без кэша каждая джоба качает — CI-скорость); тритмент находок (процесс важнее инструмента): классификация при triage — (а) фикс доступен — обновляем зависимость/базу (Dependabot-PR — тривиальные — авто-merge при зелёных тестах — тесты — страховка); (б) фикса нет — mitigation (харднинг — CVE в lib, которым не пользуемся — non-root/read-only снижает применимость — обоснование в .trivyignore); (в) неприменимо (CVE в инструменте, которого нет в рантайме — build-зависимости в multistage — «curl уязвим, а curl только в builder-слое» — итоговый образ чист — Trivy это видит по слоям — игнор не нужен); SLA по severity (CRITICAL с фиксом — 7 дней, HIGH — 30, MEDIUM — квартал — SLO-подход к безопасности — «чиним всё сразу» — нереально и не нужно — экспозиция разная); supply chain-контекст: Trivy сканирует и подписи/аттестации (--verify-signature — cosign-интеграция — подпись образов — «запускаем только подписанное» — политика Kyverno/admission — политики); SBOM-генерация (trivy image --format cyclonedx — CycloneDX/SPDX — «что внутри образа» — инвентарь для аудита — supply chain — когда выйдет CVE в библиотеке — grep по SBOM всех сервисов — минуты вместо паники).
Практический сценарий: внедрение Trivy в пайплайны за две недели без остановки разработки
Контекст: 12 Python-сервисов, CI — GitHub Actions, безопасности-сканирования нет («была попытка — 2000 находок — выключили»); план (по принципу «сначала видим, потом блокируем»): (1) неделя 1 — режим наблюдения: ночная джоба сканирует все 12 образов (без exit-code — SARIF в Security tab — метрики — тренд-дашборд в Grafana — CRITICAL/HIGH по сервисам); картина: 41 CRITICAL (30 — один базовый образ python:3.11 без обновлений год — «образ-долгожитель»), 210 HIGH; (2) быстрые победы (3 дня): базовые образы → python:3.12-slim (multistage-рефакторинг у 9 сервисов уже был — замена FROM — CI-пересборка) — минус 31 CRITICAL; Dependabot-волна по pyproject (аудит-практика — 14 PR — авто-merge зелёных — 6 CRITICAL-фиксов); (3) остаток — triage (2 дня): 4 CRITICAL — фиксов нет (CVE в glibc без патча в Debian-ветке) — mitigation: distroless-переход для 2 критичных сервисов (distroless — CVE в шелле/утилитах исчезли — поверхности нет), .trivyignore с обоснованиями для остальных (переоценка — квартал); (4) неделя 2 — гейт: PR-джоба --severity CRITICAL --ignore-unfixed --exit-code 1 (новое критичное с фиксом — PR не мёрж — исключение — override-лейбл с комментарием — review — «аварийный деплой не блокируем, но след видим»); ночная полная — trend; (5) процесс: еженедельный security-триаж 30 минут (новые находки из ночного скана — классификация — incident-культура — «без ритуала сканер умирает»); итоги месяца: CRITICAL 41 → 2 (оба — игноры с датами), HIGH 210 → 47 (SLA-план), время PR-гейта — 40 с с кэшем БД (CI-кэш); главный вывод: сканер — не «проверка», а процесс (наблюдение → быстрые победы → гейт на новое → регулярный триаж — «включить exit-code 1 в первый день» — путь к выключенному сканеру — техдолг — безопасность внедряется как продукт, не как флаг).
FAQ
Trivy против Snyk/Grype/Dependabot — что выбрать? Trivy: бесплатно/opensource, образы+fs+конфиги+секреты одним бинарником, CI-нативно, без SaaS-привязки — дефолт для self-hosted-практики; Snyk: SaaS-приоритизация (exploit-зрелость, reachability — «CVE есть, но код не достигается» — умнее сортировка), платно от масштаба — когда находок сотни и нужна приоритезация выше severity; Grype: аналог Trivy по CVE-скану (Anchore-экосистема — Synergy с Grype — SBOM-поток — SBOM — если уже в Anchore-стеке); Dependabot: не сканер, а обновлятор (PR на новые версии — Dependabot — alerts есть, но без слоёв образа); комбинация-стандарт: Trivy (скан образов/конфигов в CI) + Dependabot/Renovate (обновления) — закрывают 90% нужд бесплатно; «выбрать один» — ложная дилемма — разные роли.
Что делать с CVE, у которых нет фикса? Классификация: достижима ли уязвимость в вашем контексте (CVE в парсере XML, если XML не парсите — экспозиция нулевая — обоснование документировать); mitigation-слои (харднинг — non-root/read-only/capabilities-drop — большинство CVE-эксплойтов требуют прав/записи — «уязвим, но не эксплуатируем в нашей конфигурации» — честная позиция); distroless/minimal-база (нет шелла/утилит — нет поверхности — distroless — радикальное снижение); .trivyignore с обязательными комментарием и датой пересмотра (без даты — кладбище игноров — ревизия раз в квартал — техдолг-ритуал); мониторинг выхода фикса (ночной скан без --ignore-unfixed покажет — «фикс вышел — игнор снимаем» — тренд-метрика игноров растёт — красный флаг процесса); правило: игнор — решение с обоснованием, не способ сделать CI зелёным — OWASP-культура.
Сканировать образ или репозиторий — в чём разница?
trivy fs/repo — зависимости исходников (pyproject/requirements/package.json — находит до сборки — ранний фидбек в PR — быстро); trivy image — что реально в образе (языковые пакеты + слои ОС — glibc/openssl/apk-пакеты — fs-скан это не видит — половина CVE живёт в базовом слое — multistage-контекст; плюс — установленные в рантайме пакеты/pip-install в Dockerfile мимо lock-файлов — «requirements чистый, а в образе pip поставил свежий уязвимый»); оба обязательны: fs — в PR-джобе (секунды, ранний сигнал), image — в build/nightly (полная картина того, что деплоим — реестр — скан по тегу из реестра — «сканируем артефакт, а не намерение»); расхождение fs и image — само по себе находка (дрейф сборки — supply chain — SBOM образа — источник правды).
Как сканировать приватные образы и не качать базу CVE каждый раз?
Приватный реестр: trivy image registry.local/app:tag после podman/docker login (Trivy использует credential-хелперы/DOCKER_CONFIG — подман-контекст — работает и rootless); GHCR/ECR/GCR — соотв. login-шаги в CI (token-scoped — секреты CI); кэш БД — actions/cache на ~/.cache/trivy (ключ по дате — база обновляется ежедневно — TTL сутки — CI-кэш — первая джоба дня качает, остальные — кэш); air-gapped/строгая сеть: trivy image --download-db-only на машине с интернетом → перенос кэша → --skip-db-update (внутренний контур без прямого доступа — файрвол-контекст — зеркало БД во внутреннем хранилище — OCI-артефакт — реестр-как-хранилище); self-hosted runner'ы (CI-практика) — БД локально — скан без внешнего трафика; приватность находок — SARIF в Security tab приватного репо (не в публичные дашборды — комплаенс).
Источники: документация Trivy (docs, target/container_image, filtering) — ссылки в шапке.