n1ro°
RU

Компьютеры · Инструкция

pip --extra-index-url: как избежать dependency confusion

pip --extra-index-url позволяет добавить второй индекс к основному, но для приватных пакетов такая схема создаёт риск.

Редакция N1RO · Проверено

Короткий ответ

Почему pip предупреждает об --extra-index-url, как возникает dependency confusion и как снизить риск с контролируемым индексом и hashes.

Почему pip предупреждает об --extra-index-url

Флаг `--extra-index-url` добавляет ещё один индекс пакетов к основному `--index-url`. На первый взгляд это удобно: публичные зависимости можно брать с PyPI, а внутренние — из частного репозитория. Но документация pip прямо предупреждает, что такой способ небезопасен для приватных пакетов из-за атаки dependency confusion. Причина в том, что pip рассматривает доступные источники вместе и выбирает подходящий кандидат по правилам разрешения версий, а не гарантирует приоритет «частный индекс всегда раньше публичного». Если во внешнем индексе появится пакет с тем же именем и более привлекательной версией, установка может выбрать не тот артефакт, который ожидала команда.

Важно: Не считайте порядок `--index-url` и `--extra-index-url` механизмом доверия. Для приватного имени нужно исключить неоднозначность источника, а не надеяться на порядок аргументов.

Как возникает dependency confusion в обычном проекте

Типичный сценарий начинается с уникального внутреннего имени, например `company-analytics`. Разработчики публикуют его только в корпоративном репозитории и добавляют этот адрес через `--extra-index-url`, сохраняя PyPI основным индексом. Если такое же имя становится доступно в публичном индексе, резолвер получает кандидатов из нескольких мест. Сам факт, что приватный сервер указан отдельно, не превращает его в защищённый namespace. Особенно опасна конфигурация, где внутренний пакет допускает диапазон версий без жёсткой фиксации. Проблема относится не к взлому pip, а к неоднозначной модели поиска: один requirement может быть удовлетворён разными индексами.

Предупреждение: Если внутренние имена можно угадать из requirements, логов CI, документации или опубликованных метаданных, считайте их потенциально известными внешнему миру.

Как безопаснее организовать установку приватных Python-пакетов

  1. Составьте список внутренних пакетов и найдите все места, где проект задаёт `--extra-index-url`, `PIP_EXTRA_INDEX_URL` или соответствующую настройку pip.conf.
  2. Не полагайтесь на порядок индексов как на гарантию выбора приватного пакета. Сведите выбор источника к однозначной конфигурации.
  3. Для приватной среды используйте один контролируемый `--index-url`: внутренний репозиторий или proxy, который сам управляет разрешённым набором внутренних и внешних пакетов.
  4. Зафиксируйте версии критичных зависимостей. Для чувствительных сборок добавьте hashes и режим `--require-hashes`, чтобы полученный файл должен был совпасть с ожидаемым хэшем.
  5. Храните токены репозитория в секретах CI или credential/keyring-механизме, а не в requirements.txt и не в публичном URL.
  6. Проверьте сборку в чистом окружении и просмотрите источник/URL скачанных приватных артефактов. После изменения конфигурации не ограничивайтесь тем, что установка просто завершилась без ошибки.

Совет: Переход на один контролируемый индекс лучше тестировать на чистом virtualenv или контейнере: старый wheel в локальном кэше может скрыть ошибочную конфигурацию источника.

Какие конфигурации дают разный уровень контроля

  • PyPI + --extra-index-url приватного сервера. Кандидаты доступны из нескольких источников. Высокая неоднозначность для одинаковых имён
  • Один корпоративный index/proxy. Клиент обращается к одному контролируемому адресу. Ниже, если proxy правильно управляет внешними пакетами
  • Фиксированные версии без hashes. Версия задана, но происхождение само по себе не доказывается. Нужен контроль индекса и публикации
  • Фиксированные версии + --require-hashes. pip требует совпадения разрешённых хэшей. Снижает риск подмены артефакта, но требует полного hash-пиннинга

Почему одной фиксации версии может быть недостаточно

Пин вида `company-analytics==2.4.1` уменьшает пространство выбора, но не отвечает на вопрос, из какого индекса должен прийти пакет с этой версией. Если одинаковый релиз доступен в нескольких источниках, источник всё ещё остаётся частью модели доверия. Хэши усиливают проверку: при `--require-hashes` разрешённый файл должен совпасть с заранее известным digest. Однако hash-checking mode требует дисциплины — хэши должны быть указаны для всех устанавливаемых requirements и транзитивных зависимостей, которые участвуют в установке. Поэтому его внедряют вместе с lock/requirements-процессом, а не одной строкой в случайной команде.

Важно: Версия отвечает на вопрос «какой релиз», хэш — «какой именно файл», а индекс — «откуда вообще разрешено искать». Эти три уровня не заменяют друг друга.

Как проверить существующий проект на опасную схему

Начните с фактической конфигурации, а не только с requirements.txt. pip может получать адреса индексов из командной строки, переменных окружения и конфигурационных файлов. Найдите `extra-index-url`, `PIP_EXTRA_INDEX_URL`, CI secrets и шаблоны команд установки. Затем для каждого внутреннего имени проверьте, может ли пакет с таким же названием существовать в публичном индексе и не допускает ли constraint более новую внешнюю версию. В чистом окружении запустите диагностическую установку с подробным логом и убедитесь, какие URL рассматриваются и откуда получен wheel или sdist. Цель — доказать однозначность, а не просто отсутствие ошибки.

Что делать с внутренним proxy и внешним PyPI

Практичная архитектура — дать pip один корпоративный endpoint, а уже repository manager решает, какие внутренние пакеты обслуживать локально и какие внешние пакеты проксировать. Это переносит политику доверия из клиентских команд в одно управляемое место. Но сам факт наличия proxy не делает схему безопасной автоматически: нужно настроить правила приоритета, запрет нежелательного внешнего shadowing внутренних имён, аудит публикации и контроль доступа. Если proxy разрешает внешний пакет перекрыть внутренний с тем же именем, dependency confusion остаётся возможной уже на стороне репозитория.

Чек-лист перед публикацией CI-конфигурации

  • В проекте нет необоснованного `--extra-index-url` для приватных имён.
  • Основной индекс или proxy задан явно и контролируется организацией.
  • Внутренние имена пакетов проверены на коллизии с публичным индексом.
  • Критичные версии закреплены, а для чувствительных сборок используются проверяемые hashes.
  • Credentials не записаны в репозиторий, логи или URL, которые могут утечь.
  • Тест выполняется в чистом окружении без случайно прогретого кэша.
  • Команда проверена с теми же pip config и environment variables, что и production CI.

Предупреждение: Если в логах CI уже появлялся токен приватного индекса, сначала ротируйте секрет. Исправление dependency confusion не отменяет последствий утечки credential.

Что учитывать

Флаг `--extra-index-url` добавляет ещё один индекс пакетов к основному `--index-url`. На первый взгляд это удобно: публичные зависимости можно брать с PyPI, а внутренние — из частного репозитория. Но документация pip прямо предупреждает, что такой способ небезопасен для приватных пакетов из-за атаки dependency confusion. Причина в том, что pip рассматривает доступные источники вместе и выбирает подходящий кандидат по правилам разрешения версий, а не гарантирует приоритет «частный индекс всегда…

Источники и проверка

Фактическая часть сверена по первичным источникам (в т.ч. pip install - pip documentation). Пример и формулировки — редакция N1RO на 2026-09-20.