Опирается на правила: R-SEC-DEP-1R-SEC-DEP-4 и R-SEC-DEP-X1R-SEC-DEP-X2 из Security Style Guide → раздел 2. CVE в зависимостях.

Важно знать

  • pip-audit (PyPA Advisory Database) — основной инструмент CVE-сканирования для Python.
  • Запускается на merge в main + nightly + release, не на каждом PR (новые CVE появляются в NVD, не из PR).
  • Lock-файл (uv.lock / poetry.lock) обязательно коммитится — без него сборка невоспроизводима.
  • Renovate / Dependabot — авто-PR на minor/patch, major — manual review.
  • CVSS ≥ 7.0 (HIGH/CRITICAL) ломает сборку; 4.0–6.9 — отчёт + 30 дней на патч; ниже — игнор.
  • Suppressions со сроком (until) обязательны — бессрочное подавление запрещено (R-SEC-DEP-X1).
  • Pre-release / незапиненные зависимости в production запрещены (R-SEC-DEP-X2).

Уязвимость живёт не в твоём коде, а в библиотеках. requests с SSRF, pydantic с парсинг-ошибкой, cryptography с padding-оракулом — CVE в зависимостях. Код OrderService и CustomerRepository написан чисто, но production скомпрометирован через транзитивный пакет. UCP задаёт обязательный сканер + workflow обновления.

pip-audit — основной сканер

R-SEC-DEP-1: подключение pip-audit.

# pyproject.toml
[tool.pip-audit]
# PyPA Advisory Database + OSV
vulnerability-service = ["pypi", "osv"]
# fail на любой CVSS ≥ 7.0
fail-on-vuln = true
output = "json"
# CI-команда
pip-audit --requirement requirements.txt --output json --format json \
    --vulnerability-service pypi --vulnerability-service osv \
    -o pip-audit-report.json

С uv (рекомендуется для скорости):

uv pip audit --format json -o pip-audit-report.json

Где запускается — расслоение R-SEC-2:

ЭтапЗапускать pip-audit?Причина
IDE / каждый buildНетЛишние секунды на каждый запуск
Pre-commitНетCVE не появляется из PR
PRНетТо же
Merge в mainДаBaseline обновляется
NightlyДаНовые CVE в существующих deps
Release tagДаФинальная проверка перед выпуском
# .github/workflows/security.yml
name: Security — CVE scan
on:
  push:
    branches: [main]
  schedule:
    - cron: "0 3 * * *"   # nightly в 03:00 UTC
  release:
    types: [created]

jobs:
  pip-audit:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: astral-sh/setup-uv@v4

      - name: Install dependencies
        run: uv sync --frozen

      - name: Run pip-audit
        run: |
          uv pip audit --format json -o pip-audit-report.json
          uv pip audit --format sarif -o pip-audit.sarif

      - name: Upload SARIF
        uses: github/codeql-action/upload-sarif@v3
        with:
          sarif_file: pip-audit.sarif
          category: pip-audit

      - name: Upload report artifact
        uses: actions/upload-artifact@v4
        with:
          name: pip-audit-report
          path: pip-audit-report.json

SARIF идёт в GitHub Security tab — единый дашборд без отдельной инфры (R-SEC-FIND-3).

Lock-файл обязателен

R-SEC-DEP-4: воспроизводимость сборки.

# uv — рекомендуется
uv lock          # генерирует uv.lock
uv sync --frozen # CI: только из lock, никакого resolve

# poetry
poetry lock
poetry install --no-root --sync

# pip-compile (pip-tools)
pip-compile requirements.in -o requirements.txt --generate-hashes

Lock-файл коммитится в репо. Без него:

  • pip install в CI может поднять другую версию пакета, чем на локальной машине.
  • CVE в OrderService-зависимости незаметно появляется между двумя деплоями.
  • Rollback impossible — старые версии без lock не восстановить детерминированно.
# pyproject.toml — пиним minor-диапазон, lock дожимает до точной версии
[project]
dependencies = [
    "fastapi>=0.111,<0.112",
    "pydantic>=2.7,<3",
    "sqlalchemy>=2.0,<3",
    "httpx>=0.27,<0.28",
]

Диапазон в pyproject.toml — семантика совместимости; точная версия — в lock. Renovate читает оба.

Renovate / Dependabot

R-SEC-DEP-2: авто-PR на обновления.

// renovate.json
{
  "extends": ["config:base"],
  "schedule": ["before 8am on monday"],
  "labels": ["dependencies"],
  "packageRules": [
    {
      "matchManagers": ["pip_requirements", "poetry", "uv"],
      "matchUpdateTypes": ["minor", "patch"],
      "automerge": true,
      "automergeType": "pr",
      "platformAutomerge": true
    },
    {
      "matchUpdateTypes": ["major"],
      "automerge": false,
      "labels": ["dependencies", "major-update"]
    },
    {
      "matchPackagePatterns": ["fastapi", "pydantic", "sqlalchemy"],
      "matchUpdateTypes": ["minor"],
      "automerge": false
    }
  ],
  "vulnerabilityAlerts": {
    "enabled": true,
    "labels": ["security"]
  }
}

Что делает:

  • Minor/patch автоматически merge-ятся после прохождения CI.
  • Major — manual review (breaking changes).
  • FastAPI / Pydantic / SQLAlchemy minor — manual review (часто содержат поведенческие изменения).
  • Vulnerability alerts — отдельные PR с меткой security, вне расписания, немедленно.

Без Renovate зависимости устаревают: через полгода CustomerService тянет pydantic 1.x с известным CVE, а обновление до 2.x — уже миграция на неделю.

CVSS → действие

R-SEC-DEP-3: severity по CVSS score.

CVSS ScoreSeverityДействие
9.0–10.0CRITICALСборка падает, hotfix ≤ 24ч
7.0–8.9HIGHСборка падает, патч ≤ 2 недели
4.0–6.9MEDIUMОтчёт, патч ≤ 30 дней
0.1–3.9LOWИгнор

Release блокируется только на новые HIGH+ findings относительно baseline (R-SEC-4). Старый долг — отдельная задача, не блокирует выпуск: иначе первый протухший CVE без upstream-патча останавливает pipeline на месяц, и команда начнёт отключать сканер.

Baseline-файл для сравнения:

# После merge в main — обновляем baseline
pip-audit --format json -o security-baseline.json
git add security-baseline.json
git commit -m "chore: update security baseline"

На release сравниваем текущий отчёт с baseline — блокируем только новое.

Suppressions со сроком

R-SEC-DEP-4: каждая suppression имеет срок.

pip-audit поддерживает ignore-файл:

# pyproject.toml
[tool.pip-audit]
ignore-vuln = [
    # CVE-2024-12345: httpx используется только для внутренних API за mTLS.
    # Exploitable только через MITM на публичный endpoint — у нас его нет.
    # Ждём httpx 0.28.0. Пересмотр: 2026-09-01
    "GHSA-xxxx-yyyy-zzzz",
]

Либо через отдельный файл pip-audit-ignore.toml:

# pip-audit-ignore.toml
[[ignore]]
id = "PYSEC-2024-12345"
reason = "CVE в requests multipart — ProductService не обрабатывает multipart, только JSON. Патч ожидается в requests 2.33. Пересмотр: 2026-08-15."
until = "2026-08-15"

[[ignore]]
id = "GHSA-abcd-efgh-1234"
reason = "false positive — cryptography используется в test scope, не в production artifact CustomerService. Пересмотр: 2026-07-01."
until = "2026-07-01"

Правила:

  • until — обязательная дата пересмотра в ISO 8601.
  • reason — объяснение: почему false positive / когда патч / почему acceptable risk.
  • Точечное подавление по CVE/GHSA ID — не на всю библиотеку.

Квартальный аудит просроченных suppression — R-SEC-FIND-2. Просроченная suppression = невыполненная задача.

Что запрещено

АнтипаттернПравилоЧто взамен
Suppression без untilR-SEC-DEP-X1срок обязателен
Pre-release / >=-диапазон без lock в productionR-SEC-DEP-X2lock-файл в репо
pip-audit на каждом PRR-SEC-DEP-1merge в main + nightly + release
--exit-zero в CI (игнор findings)R-SEC-1fail на HIGH/CRITICAL
Suppression на всю библиотеку без CVE IDR-SEC-DEP-4точечно по CVE/GHSA
Без Renovate / DependabotR-SEC-DEP-2один из двух
Auto-merge major updatesR-SEC-DEP-2major — manual review
Lock-файл не коммититсяR-SEC-DEP-4uv.lock / poetry.lock в репо

Куда дальше

  • Container/image-уязвимости — Trivy сканирует образ включая Python-пакеты.
  • Криптография — argon2, secrets, AES-GCM.
  • Реакция на findings — SLA per severity, SARIF в Security tab.
  • SAST по коду — bandit, semgrep, ruff S-правила.
  • Секреты в коде и истории — Gitleaks, .env-дисциплина.