PyPI 博客:项目隔离功能介绍
今年早些时候,我简要介绍了 Python 包索引(PyPI)新增的一项功能——项目隔离。这项功能允许 PyPI 管理员将某个项目标记为潜在有害,并防止用户轻易安装它,从而避免进一步的危害。在这篇文章中,我将详细讨论该功能的实现及其未来改进。
背景
在 PyPI 上,恶意软件一直是一个持续存在的问题。PyPI 有三个主要概念:项目、版本和文件。这些是独立的数据模型,根据其特性表现略有不同。一个项目可以有零个或多个版本,每个版本可以有一个或多个文件。研究人员通常会报告某个项目包含恶意软件,并提供具体文件中的位置链接,按照 PyPI 安全流程进行处理。PyPI 收到的恶意软件报告往往涉及整个项目。简单来说,一个项目及其所有版本(通常是1个)和文件(通常是1-2个)都属于同一类恶意活动,应该从 PyPI 中移除以保护终端用户。
然而,情况并非总是如此。有时,恶意软件会被添加到已有的成熟项目中,通过新的版本发布来传播。因此,可能需要考虑针对特定版本或文件报告恶意软件的情况,这尚未完全通过观察或测试版恶意软件 API 实现。当审核和处理恶意软件报告时,PyPI 管理员的主要工具就是从数据库中完全删除该项目,并禁止重用该项目名称。PyPI 还有其他功能来防止文件名重用。这些删除操作可能会造成干扰,并且一旦删除几乎不可撤销。此外,恶意项目在公开可用的时间越长,终端用户就越有可能安装并成为受害者。
由于 PyPI 目前只有1名全职安全人员,恶意软件可能会在较长时间内保持可安装状态。要求志愿者管理员额外工作也不可持续。减少恶意项目/版本/文件对终端用户的暴露时间是一种改进措施,也能进一步降低恶意行为者利用 PyPI 作为分发渠道的动机。
实现
在了解了项目的各种可能状态后,我开始设计和实现项目隔离功能。以下是基本需求:
-
项目存在于 PyPI 上,包含版本和文件
-
项目处于隔离状态时不可安装(从简单索引中隐藏)
-
项目处于隔离状态时项目所有者无法修改
-
项目状态对项目所有者、安全研究人员和 PyPI 管理员可见
-
项目状态可以由 PyPI 管理员恢复以重新公开
-
项目可以由 PyPI 管理员删除
参考“撤回”功能
在此之前,PyPI 已经有一个名为“撤回”的功能(PEP 592)。如果一个项目没有版本,则会在简单仓库 API 中列出,但不会有任何链接,使其实际上无法安装。一个想法是在隔离项目时,将其标记为没有版本,从而排除在索引之外。与“撤回”不同的是,撤回的版本仍然可以通过客户端安装,而隔离的项目不应被安装。因此,我们需要探索在哪里进行更改以及如何影响客户端。撤回功能应用于单个版本及其所有文件,而不是整个项目。我们可以对每个版本进行更改,而不是在整个项目范围内,从而为隔离单个版本铺平道路。这种方法更复杂,试图解决罕见的边缘情况,即成熟的项目发布了需要隔离的新版本,同时不影响现有用户的使用。我们接受这种情况可能发生,并密切关注其发生情况,推迟实施直到必要时。
创建仅观察者可见性 API
我之前构建了一个新的测试版 API 基础设施,允许观察者报告恶意项目。一个想法是添加一个新的经过身份验证的 API 端点,允许查询当前隔离项目的列表,并提供其版本和文件的链接供消费。这样,研究人员可以下载相关文件,但不能通过 pip install
安装。最终我没有采用这种方法,因为测试版认证 API 仍在开发中,我不想在此基础上增加更多功能,直到我们解决了关键的身份验证和授权问题。
生命周期状态
从简单仓库 API 中移除项目的探索取得了成果,并引导我实现了生命周期状态(LifecycleStatus),这是一个应用于项目的新状态。以下是项目状态转换图:
将生命周期状态添加到项目模型有助于代码中的其他功能做出单一决策,并允许在未来实现更复杂的状态机。潜在状态包括“归档”、“弃用”等。