Безопасность зависимостей

Трёхдневная задержка Dependabot: проверяем защиту от опасных релизов

Свежая версия пакета может попасть в автоматический pull request раньше, чем сообщество обнаружит вредоносный код, ошибку публикации или отзыв релиза. Проверим, как трёхдневный cooldown меняет поведение Dependabot, не устанавливая потенциально опасные пакеты.

Уровень: средний Чтение: до 12 минут

Что мы получим

В конце эксперимента у нас будут две рабочие конфигурации .github/dependabot.yml для npm-проекта:

  • вариант без задержки, явно исключающий все пакеты из cooldown;
  • вариант с минимальным возрастом релиза в три дня;
  • таблица наблюдений, показывающая, когда свежая версия становится кандидатом на pull request.

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

Как работает задержка

Во время запланированной проверки Dependabot получает доступные версии из реестра и сопоставляет возраст релиза с правилами cooldown. Если версия моложе заданного окна, Dependabot пропускает её в текущем запуске. После окончания окна версия снова рассматривается по обычным правилам обновления.

Сценарий Релизу менее трёх дней Релизу не менее трёх дней
Cooldown отключён через exclude: ["*"] Версия может участвовать в version update сразу Версия может участвовать в version update
default-days: 3 Версия пропускается Версия снова рассматривается
Security update Cooldown не задерживает обновление Cooldown не задерживает обновление

Формулировка «может участвовать» существенна: наличие подходящей версии не гарантирует pull request. На результат также влияют диапазон версий в манифесте, lock-файл, правила ignore и allow, лимит открытых PR и совместимость разрешения зависимостей.

Подготовка безопасного стенда

Используйте отдельный тестовый репозиторий или временную ветку проекта. Эксперимент не требует публикации собственного пакета, токенов реестра или запуска кода из новой зависимости.

1. Проверьте структуру npm-проекта

git status --short
test -f package.json && echo "package.json найден"
test -f package-lock.json && echo "package-lock.json найден"

Команды только читают состояние рабочей копии. Если lock-файла нет, создавайте его обычным для проекта способом только после проверки скриптов в package.json. Не запускайте npm install для неизвестной свежей версии ради эксперимента.

2. Выберите наблюдаемую зависимость

Подойдёт уже используемая прямая npm-зависимость, у которой существует более новая совместимая версия. Не фиксируйте в статье или автоматизации конкретный пакет: доступность свежего релиза меняется со временем.

Версии прямых зависимостей можно посмотреть без установки:

npm outdated --depth=0

npm outdated обращается к настроенному реестру, но не устанавливает найденные версии. Для выбранного пакета проверьте время публикации:

npm view <имя-пакета> time --json

Замените <имя-пакета> именем существующей зависимости. Сопоставьте номер доступной версии с её временной меткой. Для чистого сравнения нужен релиз моложе трёх суток. Если такого релиза нет, сохраните конфигурацию и дождитесь следующей публикации; искусственно устанавливать подозрительную версию не нужно.

3. Зафиксируйте исходные условия

Запишите время публикации версии, время проверки Dependabot, текущую и целевую версии, ветку и наличие других открытых Dependabot PR. Возраст следует считать по временной метке реестра, а не по дате коммита или GitHub Release.

Пакет:
Текущая версия:
Целевая версия:
Опубликована в реестре:
Запуск Dependabot:
Возраст релиза на момент запуска:
Открыт version update PR: да / нет

Эксперимент A: поведение без cooldown

Поскольку отсутствие блока теперь означает стандартную задержку в три дня на GitHub.com, для контрольного варианта явно исключим все зависимости из cooldown.

version: 2
updates:
  - package-ecosystem: "npm"
    directory: "/"
    schedule:
      interval: "daily"
      time: "09:00"
      timezone: "Europe/Moscow"
    cooldown:
      default-days: 3
      exclude:
        - "*"
    open-pull-requests-limit: 10

Список exclude имеет приоритет над include и общими сроками. Шаблон "*" исключает из задержки все зависимости этого блока, поэтому свежие версии могут рассматриваться немедленно.

Сохраните файл по обязательному пути и проверьте его:

mkdir -p .github
git diff --check
git status --short
git diff -- .github/dependabot.yml

Создать каталог безопасно, но сам файл следует добавить редактором. Не помещайте в конфигурацию токены. Для публичного npm-реестра в этом примере раздел registries не нужен.

После отправки изменения в ветку по умолчанию откройте в репозитории страницу Insights → Dependency graph → Dependabot или соответствующий раздел настроек безопасности. Запустите проверку вручную, если интерфейс репозитория предлагает Check for updates; иначе дождитесь расписания.

Ожидаемое наблюдение для версии моложе трёх дней: она не блокируется правилом минимального возраста. Если все остальные условия выполнены, Dependabot может создать version update PR.

Эксперимент B: явная задержка на три дня

Замените контрольную конфигурацию следующей. Явная настройка полезна как документация политики проекта и не зависит от того, знает ли читатель о текущем значении по умолчанию.

version: 2
updates:
  - package-ecosystem: "npm"
    directory: "/"
    schedule:
      interval: "daily"
      time: "09:00"
      timezone: "Europe/Moscow"
    cooldown:
      default-days: 3
      include:
        - "*"
    open-pull-requests-limit: 10

Снова отправьте файл в ветку по умолчанию и выполните проверку обновлений. Не закрывайте контрольный PR до того, как запишете его номер и время создания. Для чистого повторного наблюдения его можно закрыть перед вторым запуском, не объединяя изменения.

Ожидаемый результат:

  • версия младше трёх суток пропускается как version update;
  • после достижения возраста в три дня она снова становится кандидатом;
  • security update не ждёт окончания cooldown.

Если нужен разный срок для типов SemVer-обновлений, конфигурацию можно уточнить:

version: 2
updates:
  - package-ecosystem: "npm"
    directory: "/"
    schedule:
      interval: "daily"
      time: "09:00"
      timezone: "Europe/Moscow"
    cooldown:
      default-days: 3
      semver-major-days: 14
      semver-minor-days: 7
      semver-patch-days: 3
      include:
        - "*"

Это пример более консервативной политики, а не универсальная рекомендация: подходящие интервалы зависят от частоты релизов, риска и скорости реакции команды.

Проверка результата

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

Проверка Без задержки С задержкой 3 дня
Версия моложе 72 часов Не отфильтрована cooldown Отфильтрована до окончания окна
Версия старше 72 часов Рассматривается Рассматривается
Изменён манифест или lock-файл Возможен PR Возможен после окна
Исполнен код новой версии Нет, пока PR не собирался и не объединялся Нет

Для каждого запуска откройте журнал Dependabot и найдите выбранную зависимость. Сохраните текст причины пропуска или создания PR. Интерфейс и формулировки журнала могут меняться, поэтому главным доказательством остаётся сочетание временной метки публикации, конфигурации и фактического результата запуска.

Почему результаты иногда не совпадают с ожиданием

Удалили cooldown, но задержка осталась

На GitHub.com это ожидаемо: трёхдневная задержка применяется по умолчанию. Для контрольного режима используйте exclude: ["*"].

Dependabot не открыл PR даже без задержки

Проверьте журнал. Возможные причины: целевая версия запрещена диапазоном, действует ignore, исчерпан open-pull-requests-limit, обновление уже представлено открытым PR, lock-файл не разрешается или конфигурация ещё не попала в ветку по умолчанию.

Тестируют security update

Cooldown предназначен для version updates и не задерживает security updates. Это специально сделано, чтобы известное исправление уязвимости не ожидало минимального возраста.

Считают три календарных даты вместо возраста релиза

Ориентируйтесь на точное время публикации в реестре. Релиз, опубликованный вечером в понедельник, утром в четверг может быть всё ещё моложе 72 часов.

Используют два перекрывающихся блока npm

Для одного ecosystem, target branch и каталога не создавайте конфликтующие записи. Сравнивайте конфигурации последовательно или в отдельных тестовых репозиториях.

Забывают кавычки вокруг звёздочки

В YAML символ * имеет специальное значение. Пишите шаблон как строку: "*".

Путают exclude и исключение обновления

cooldown.exclude не запрещает обновление пакета. Он исключает пакет только из задержки, то есть позволяет рассмотреть его раньше.

Ограничения защиты

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

  • Долго скрывающийся бэкдор переживёт трёхдневное ожидание.
  • Скомпрометированная старая версия не становится безопаснее из-за возраста.
  • Ошибки в транзитивных зависимостях и сборочной инфраструктуре требуют отдельных мер.
  • Автоматическое объединение PR после cooldown всё равно нуждается в тестах и правилах защиты ветки.
  • В GitHub Enterprise Server доступность нового значения по умолчанию зависит от версии сервера.

Используйте задержку как один слой защиты вместе с lock-файлами, рецензированием изменений, минимальными разрешениями CI, проверкой происхождения артефактов и контролем install-скриптов. Дополнительные практические материалы собраны в разделе руководств, а определения терминов — в глоссарии.

Итоговая конфигурация

Для обычного npm-проекта разумная стартовая политика выглядит так:

version: 2
updates:
  - package-ecosystem: "npm"
    directory: "/"
    schedule:
      interval: "daily"
      time: "09:00"
      timezone: "Europe/Moscow"
    cooldown:
      default-days: 3
      include:
        - "*"
    open-pull-requests-limit: 10
    commit-message:
      prefix: "chore(deps)"

Она явно требует трёхдневного возраста для обычных обновлений всех npm-зависимостей. Если конкретный внутренний пакет должен обновляться немедленно, добавьте его точное имя в exclude, понимая, что исключение ослабляет именно временной барьер.

Проверка считается успешной, если один и тот же свежий релиз допускается контрольной конфигурацией с exclude: ["*"], пропускается конфигурацией с include: ["*"] до истечения трёх суток и снова рассматривается после окончания окна. Сам пакет при этом устанавливать или объединять в основную ветку не требуется.

Официальная документация