PyPI устранил две уязвимости контроля доступа уровня High, выявленные во время второго аудита безопасности Warehouse — open source-приложения, которое отвечает за загрузку и распространение пакетов в экосистеме Python.
Проверку провела Trail of Bits. Аудит был сосредоточен на аутентификации/авторизации, границах прав внутри организаций, пайплайне OIDC Trusted Publishing, путях загрузки пакетов и валидации метаданных, операциях жизненного цикла API-токенов и audit logging.
Trail of Bits сообщила в общей сложности о 14 находках: две High, одна Medium, семь Low и четыре Informational. В PyPI заявили, что все, кроме двух находок, уже устранены.
Что именно проверяли и почему это важно#
Warehouse — это control plane для PyPI: он определяет, кто может загружать пакеты, как валидируются метаданные, как проекты перемещаются между организациями и как API-токены и механизмы Trusted Publishing превращают identity в permission.
Это поверхности с очень высоким уровнем влияния. Большинство инцидентов в software supply chain не требуют какого-то нового взлома криптографии. Достаточно сценария, в котором система выдает не те возможности не той identity, либо не отзывает старые возможности после смены владельца.
По данным отчета, Trail of Bits проводила аудит с 23 февраля по 20 марта 2026 года. Над проверкой работали два консультанта, суммарно затратив шесть engineer-weeks.
High-Severity Finding 1: участники организации могли приглашать Owners#
Первая уязвимость уровня High касалась механизма управления ролями в организациях PyPI.
Trail of Bits обнаружила, что view с именем manage_organization_roles обрабатывал и GET-, и POST-запросы в рамках одной проверки прав с меткой Permissions.OrganizationsRead. При POST этот же view вызывал функцию приглашения (_send_organization_invitation) и создавал инвайт с ролью, переданной в запросе.
Поскольку endpoint требовал только permission уровня read, участник организации мог отправить POST-запрос и пригласить нового пользователя с ролью выше той, которую сам приглашающий должен был иметь право выдавать, включая Owner, Manager или Billing Manager.
Trail of Bits описала сценарий эксплуатации, при котором пользователь с ролью Member отправляет приглашение, назначая роль Owner связанному аккаунту. Если такое приглашение принять, оно даст административный контроль над организацией, включая возможность добавлять и удалять проекты, управлять trusted publishers, менять billing и потенциально удалить исходного владельца.
Сообщается, что исправление со стороны PyPI заключалось в разделении конфигурации view: теперь GET-запросы требуют permission уровня read, а POST-запросы — соответствующего permission уровня write.
Trail of Bits также разработала кастомный запрос CodeQL для поиска похожих паттернов — обработчиков POST, изменяющих состояние, но защищенных только permission уровня read. Это полезный вывод и за пределами PyPI: mismatch между методом запроса и авторизацией — распространенный failure mode в web-приложениях, особенно когда одна view-функция обслуживает несколько HTTP-методов.
High-Severity Finding 2: перенос проектов мог оставлять устаревший доступ на загрузку#
Вторая уязвимость уровня High касалась переноса проектов между организациями.
Trail of Bits сообщила, что при переносе проекта или его удалении из организации PyPI удалял запись связи organization_project, но не очищал другие organization-scoped records прав, связанные с этим проектом.
Как объясняется в отчете, корневая проблема заключалась в том, что модель ссылалась на проекты и команды независимо друг от друга, без ограничения, которое связывало бы доступ команды к проекту с организацией, владеющей проектом в текущий момент. Во время проверки контроля доступа PyPI запрашивал все team-records для проекта и выдавал permissions участникам каждой подходящей команды, не проверяя, принадлежит ли команда текущей организации проекта.
Это создает риск dangling permissions: устаревшие роли команд могли продолжать выдавать доступ даже после смены организации-владельца проекта.
Trail of Bits отметила, что последствия зависели от уровня устаревшей роли:
- устаревшая роль уровня Owner могла дать административные permissions;
- устаревшая роль Maintainer могла дать права на upload, которых, по словам Trail of Bits, достаточно для публикации вредоносных релизов.
В отчете подчеркивается, что такой устаревший доступ было бы трудно заметить: такие записи, как сообщается, не были видны принимающей организации, не отображались в списке collaborators проекта и учитывались только во время проверки ACL.
В одном из сценариев, описанных в отчете, атакующий, входивший в команду release-engineers в исходной организации, мог сохранить доступ уровня Owner после переноса проекта, а затем загрузить релиз с бэкдором, используя сохраненную возможность upload.
Сообщается, что исправление со стороны PyPI состояло из двух частей:
- удалить записи, принадлежащие исходной организации, до удаления записи связи;
- добавить защитные ACL-фильтры, которые проверяют, что организация команды совпадает с текущей организацией проекта, прежде чем выдавать team permissions.