Управление зависимостями: npm, pip, Go modules — playbook
Обычный JavaScript-сервис импортирует 47 прямых зависимостей и в итоге резолвит 2500+ транзитивных пакетов. Тот же сервис, переписанный на Go, импортирует 12 модулей и резолвит 42. pip-эквивалент — около 180. Это не вкусовщина, это форма каждой экосистемы. Ваша стратегия зависимостей обязана стартовать именно с этой реальности.
Уровень supply-chain-риска, дисциплина lockfile и каденция апгрейдов должны быть разными в каждой экосистеме. Это playbook, как это сделать в npm, pip и Go modules — трёх экосистемах, которые по данным Stack Overflow Developer Survey 2025 покрывают примерно 84% production-кода на бэкенде.
{/* truncate */}
Проблема: одна политика не ложится на три экосистемы
Большинство инженерных организаций пишут один «dependency policy» документ, применяют его ко всем репозиториям и удивляются, почему он везде ощущается неправильно. Он действительно везде неправильный — потому что экосистемы устроены разными компромиссами:
- npm оптимизирован на ширину. Крошечные пакеты, глубокие деревья, быстрая установка — и история supply-chain-инцидентов (
event-stream,ua-parser-js,node-ipc). По данным Sonatype State of the Software Supply Chain 2024, на npm пришлось 68% обнаруженных загрузок вредоносных пакетов в 2023. - pip оптимизирован на научные вычисления и системные биндинги. Пакетов меньше, но часто C-расширения, платформенные wheels и резолв-фейлы вида «у меня работает».
- Go modules оптимизирован на minimal version selection и воспроизводимость. Маленькие деревья, криптографический sum-файл, пиннинг зашит по умолчанию.
Среднее число транзитивных зависимостей для типичного backend-сервиса. Форма экосистемы диктует форму вашей политики.
Это не провал одного и преимущество другого. Это разные ответы на разные задачи, и одна скопированная политика промахивается по реальному риску везде.
Фреймворк: 6 шагов, адаптируемых под экосистему
Шаг 1 — Инвентаризация того, от чего вы реально зависите
Перед тем как что-либо улучшать, составьте список всех прямых зависимостей по всем сервисам. По каждой — фиксируйте:
| Поле | Почему важно |
|---|---|
| Имя + версия | Базовая идентификация |
| Прямая или транзитивная | Прямую вы фиксите сами; транзитивная — наследство |
| Лицензия | GPL в проприетарном репозитории — реальный риск |
| Дата последнего релиза | Всё, что не трогали >24 мес. — красный флаг |
| Число мейнтейнеров | Один человек = bus factor |
| Тренд скачиваний | Падающий пакет часто = заброшенный |
Инструменты делают почти всё: npm ls --all, pip list --format=json + pipdeptree, go list -m all. Для лицензий — license-checker (npm), pip-licenses, go-licenses.
Шаг 2 — Дисциплина lockfile для каждой экосистемы
| Экосистема | Lockfile | Коммитить? | Что в CI |
|---|---|---|---|
| npm | package-lock.json | Да | npm ci (не npm install) |
| pnpm | pnpm-lock.yaml | Да | pnpm install --frozen-lockfile |
| pip | requirements.txt с хешами, или uv.lock / poetry.lock | Да | pip install --require-hashes |
| Go | go.sum | Да | go mod verify в CI |
Контринтуитивная часть: не используйте npm install в CI. Он молча обновляет package-lock.json, если девмашина разошлась с lockfile — именно так скомпрометированная версия просачивается в прод без PR. Всегда npm ci.
Шаг 3 — Каденция апгрейдов по tier
Не все зависимости заслуживают одинакового внимания. Разбейте на тиры:
| Tier | Пример | Каденция | Auto-merge? |
|---|---|---|---|
| Runtime + критично по безопасности | openssl, express, django, golang.org/x/crypto | В 72 часа после CVE | Нет (review) |
| Ядро фреймворка | react, fastapi, gin | Minor/patch в 2 недели | Да, если тесты зелёные |
| Build-тулинг | webpack, ruff, goreleaser | Месячный батч | Да |
| Мелкие утилиты | lodash.chunk, humanize-duration | Квартальный батч | Да |
Статья Microsoft Research «Keeping Dependencies Updated» (Mirhosseini et al., 2023) показала: команды, которые батчили апгрейды еженедельно, ломали билды из-за дрейфа зависимостей в 2.8 раза реже, чем те, кто апгрейдился по запросу. Механизм очевиден после того, как его увидел: маленькие diff легче биссектить.
Шаг 4 — Поднимите бота, но настройте его по-разному для экосистем
Dependabot и Renovate оба нормально работают. Renovate гибче, Dependabot бесплатен и встроен в GitHub. В любом случае:
# renovate.config.json набросок
{
"packageRules": [
{ "matchManagers": ["npm"],
"matchDepTypes": ["devDependencies"],
"groupName": "npm dev deps",
"automerge": true,
"schedule": ["before 5am on monday"] },
{ "matchManagers": ["pip_requirements"],
"matchPackageNames": ["cryptography", "urllib3", "requests"],
"automerge": false,
"labels": ["security-review"] },
{ "matchManagers": ["gomod"],
"matchUpdateTypes": ["patch"],
"automerge": true,
"groupName": "go patches" }
]
}
Асимметрия намеренная: патчи Go редко что-то ломают, npm dev-deps можно батчить, а pip crypto-смежные библиотеки всегда заслуживают человеческих глаз.
Шаг 5 — Пускайте pipeline через SCA, не только через lockfile
Software Composition Analysis ловит то, чего не видит lockfile: известные CVE в резолвленном дереве. Минимум:
- npm:
npm audit --audit-level=high+osv-scanner - pip:
pip-audit(официальный от PyPA) +safety check - Go:
govulncheck(официальный, статический анализ — игнорирует недостижимые пути в коде, что важно при больших транзитивных деревьях в stdlib-смежных модулях)
Тихая суперсила — govulncheck. Он читает реальный call graph и говорит: «CVE есть в твоей зависимости, но твой код туда не доходит». Это убивает огромное количество шума. У npm и pip ничего настолько же точного пока нет.
Шаг 6 — Архивируйте, а не обновляйте формально
Если зависимость не трогали 24+ месяца и её тесты плывут на текущем runtime — не «просто бампните версию». Решите:
- Заменить — найти поддерживаемый аналог (
moment→date-fnsилиTemporal APIв Node). - Вендоринг — для мелких утилит скопируйте исходник в
vendor/и владейте им сами. - Архивировать сервис — если сам сервис не стоит модернизации, перестаньте тратить время на патчи.
Неприятная правда: 30% тикетов на апгрейд зависимостей в средних репозиториях должны быть решениями «заменить или вендорить», а не апгрейдами. Команды редко это признают, потому что апгрейд ощущается как прогресс.
Типичные ошибки
| Ошибка | Почему вредит | Как фиксить |
|---|---|---|
npm install в CI | Дрейф lockfile, тихие смены версий | npm ci |
| Merge бот-PR без тестов | 18% апгрейдов что-то ломают (IEEE 2023) | Зелёный CI обязателен |
| Апгрейдить всё разом | Не биссектнуть поломку | Tier + батч еженедельно |
| Игнор транзитивных CVE | Большинство эксплойтов приходят через транзитивы | SCA в CI, не только lockfile-review |
| Пиннинг точной версии везде | Хрупко, застреваете на CVE | Пиннинг — для приложений, диапазоны — для библиотек |
| Общая политика на npm, pip, Go | Разные экосистемы = разные риски | Разные политики на разные манифесты |
Как понять, что работает
Трекайте по репозиторию по месяцам:
- Среднее время до патча критического CVE — цель меньше 48 часов для runtime-зависимостей. В книге DORA Accelerate это косвенно связано с change-failure rate.
- Lockfile drift в CI — число PR, где lockfile меняется неожиданно. Цель — ноль.
- Процент прямых зависимостей старше 24 месяцев — держите ниже 10%. Выше 20% — медленно тлеющий долг модернизации.
- Рост транзитивного дерева месяц к месяцу — плато или уменьшение.
PanDev Metrics поднимает метрику «time-to-patch», связывая Git-события с датами раскрытия CVE: когда package-lock.json или go.sum меняется в ответ на известный advisory, это видно в дашборде рядом с остальной работой инженера, без отдельного security-инструмента. Для команд, которые и так считают DORA через нашу платформу, гигиена зависимостей ложится в тот же экран.
Когда этот фреймворк не подходит
- Чисто исследовательские Python-проекты — научная воспроизводимость часто значит «замёрзнуть на старой версии навсегда». Tiering сверху не работает; используйте
uv/condalockfiles и примите долг как цену воспроизводимости статьи 2022 года. - Соло-мейнтейнеры open source — большая часть этого — оверхед, когда вы один. Dependabot +
npm auditв CI достаточно. «Тиринг» начинает иметь смысл примерно от 5 контрибьюторов. - Строго регулируемые среды (HIPAA, DO-178C) — другая история: SBOM-pipeline, approved vendor lists, provenance-подписи (SLSA, Sigstore). Детали — в нашем гайде по MedTech метрикам.
Честная оговорка: наши данные IDE-heartbeat говорят, сколько времени инженеры тратят на коммиты, связанные с зависимостями, но не скажут, стоил ли конкретный апгрейд стратегически. Это всё ещё зона человеческого решения по release notes.
