PEP 710 – 记录已安装软件包的来源
- 作者:
- Fridolín Pokorný <fridolin.pokorný at gmail.com>
- 发起人:
- Donald Stufft <donald at stufft.io>
- PEP 代理人:
- Paul Moore <p.f.moore at gmail.com>
- 讨论至:
- Discourse 帖子
- 状态:
- 草案
- 类型:
- 标准跟踪
- 主题:
- 打包
- 创建日期:
- 2023年3月27日
- 发布历史:
- 2021年12月3日, 2023年1月30日, 2023年3月14日, 2023年4月3日
摘要
本 PEP 描述了一种记录已安装 Python 发行版来源的方法。该记录由安装程序创建,并以 JSON 文件 provenance_url.json 的形式在 .dist-info 目录中提供给用户。上述 JSON 文件捕获了额外的元数据,以允许记录 分发包 的 URL 以及已安装分发版的哈希。本提案基于 PEP 610,遵循 其对应的规范 PyPA 规范,并用 provenance_url.json 补充 direct_url.json,用于包通过名称(可选版本)标识的情况。
动机
安装 Python 项目 涉及从 包索引 下载 分发包 并将其内容解压到适当位置。安装过程完成后,有关所用发布工件及其来源的信息通常会丢失。然而,保留用于安装包及其来源的分发版记录存在用例。
Python wheel 可以使用不同的编译器标志或支持不同的 wheel 标签进行构建。在这两种情况下,用户可能会遇到安装程序(可能来自不同的包索引)考虑多个 wheel 的情况,立即找出安装过程中实际使用了哪个 wheel 文件可能会有所帮助。通过这种方式,开发人员可以使用有关 wheel 的信息来调试问题,确保所需的 wheel 确实已安装。另一个用例可能是报告已安装软件的工具,例如报告 SBOM(软件物料清单)的工具,这些工具可能会提供更准确的报告。还有一个用例可能是通过将每个已安装的包固定到从 Python 包索引获取的特定分发工件来重建 Python 环境。
基本原理
本 PEP 中描述的动机是 记录已安装分发版的直接 URL 来源 规范的扩展。除了记录使用直接 URL 安装的包的来源信息外,安装程序还应为从 Python 包索引按名称(和可选版本)安装的包执行此操作。
本 PEP 中描述的想法起源于一个名为 micropipenv 的工具,该工具用于在容器化环境中安装 分发包(参见报告的问题 thoth-station/micropipenv#206)。目前,组装的容器化应用程序不隐式携带已安装分发包的来源信息(除非这些包是从完整 URL 安装并通过 direct_url.json 记录)。这要求容器镜像供应商将容器镜像与其相应的构建过程、配置和应用程序源代码链接起来,以便在需要审计容器化环境中存在的软件时检查需求文件。
Discourse 线程中的后续讨论 也提到了 pip 的新 --report 选项,该选项可以 生成详细的 JSON 报告 关于安装过程。此选项可以帮助解决本 PEP 所探讨的来源问题。然而,要获取来源信息,需要 明确地 将此选项传递给 pip,并且它包含一些可能不需要用于检查来源的额外元数据(例如每个分发包的 Python 版本要求)。此外,截至撰写本 PEP 时,此选项是 pip 特有的。
请注意,当前的 记录已安装包的规范 定义了一个 RECORD 文件,该文件记录已安装的文件,但未记录这些文件来自的分发工件。已安装工件的审计可以基于匹配 RECORD 文件中列出的条目来执行。然而,这种技术需要一个预先计算的每个工件提供的文件数据库,或者与实际工件内容的比较。这两种方法都是相对昂贵且耗时的操作,而通过提议的 provenance_url.json 文件可以消除这些操作。
记录已安装分发包的来源信息,无论是从直接 URL 获取的还是从索引通过名称/版本获取的,可以简化 Python 环境的整体审计,而不仅仅是前面提到的容器化应用程序的特定用例。社区项目 pip-audit 在 pypa/pip-audit#170 中表达了他们可能的兴趣。
规范
本文档中的关键词“必须”、“不得”、“必需”、“应该”、“不应该”、“推荐”、“可以”和“可选”应按照 RFC 2119 中描述的进行解释。
当安装程序通过名称(可选地通过 版本说明符)指定 分发包 时,provenance_url.json 文件应该在 .dist-info 目录中创建。
当从指定直接 URL 引用(包括 VCS URL)的需求安装分发包时,不得创建此文件。
在给定的 .dist-info 目录中,只能存在 provenance_url.json 和 direct_url.json(来自 记录已安装分发版的直接 URL 来源 规范以及 直接 URL 数据结构 的相应规范)中的一个文件;安装程序不得同时添加两者。
provenance_url.json JSON 文件必须是字典,符合 RFC 8259 并采用 UTF-8 编码。
如果存在,它必须包含且仅包含两个键。第一个必须是 url,类型为 string。第二个键必须是 archive_info,其值定义如下。
url 键的值必须是下载分发包的 URL。如果 wheel 是从源分发版构建的,则 url 值必须是下载源分发版的 URL。如果 wheel 直接下载和安装,则 url 字段必须是下载 wheel 的 URL。如同 直接 URL 数据结构 规范中一样,出于安全原因,url 值必须剥离任何敏感的身份验证信息。
然而,URL 的 user:password 部分可以由环境变量组成,匹配以下正则表达式
\$\{[A-Za-z0-9-_]+\}(:\$\{[A-Za-z0-9-_]+\})?
此外,URL 的 user:password 部分可以是众所周知的、非安全敏感的字符串。一个典型的例子是 URL(如 ssh://git@gitlab.com)中的 git。
archive_info 的值必须是一个字典,其中包含一个键 hashes。hashes 的值是一个字典,将哈希函数名称映射到 url 值引用的文件的十六进制编码摘要。必须记录至少一个哈希。可以包含多个哈希,消费者可以决定如何处理多个哈希(它可以验证所有哈希或其子集,或者根本不验证)。
每个哈希必须是 hashlib.algorithms_guaranteed 提供的单参数哈希之一,排除 sha1 和 md5,这两者不得使用。截至 Python 3.11,在排除 shake_128 和 shake_256 作为多参数哈希之后,允许的哈希集是
>>> import hashlib
>>> sorted(hashlib.algorithms_guaranteed - {"shake_128", "shake_256", "sha1", "md5"})
['blake2b', 'blake2s', 'sha224', 'sha256', 'sha384', 'sha3_224', 'sha3_256', 'sha3_384', 'sha3_512', 'sha512']
每个哈希必须以哈希的规范名称引用,始终为小写。
由于这些哈希算法的安全限制,不得出现哈希 sha1 和 md5。相反,应该包含哈希 sha256。
从索引缓存分发包的安装程序应该保留与缓存分发工件相关的信息,以便即使从安装程序的缓存安装分发包,也可以创建 provenance_url.json 文件。
向后兼容性
遵循 记录已安装项目 规范,安装程序可以在 .dist-info 目录中保留额外的安装程序特定文件。为确保本 PEP 不会引起任何向后兼容性问题,对安装程序和库进行了全面调查,发现目前没有工具使用类似名称的文件,也没有其他主要可行性问题。
Wheel 规范 列出了 .dist-info 目录中可能存在的文件。这些文件名都没有与本 PEP 中提议的 provenance_url.json 文件发生冲突。
安装程序和库中 provenance_url.json 的存在
对 Python 生态系统中现有安装程序、库和依赖管理器进行了全面调查,分析了为每个工具添加 provenance_url.json 支持的影响。总而言之,截至撰写本 PEP 时,未发现重大的向后兼容性问题、冲突或可行性障碍。有关调查的更多详细信息,请参阅 附录:安装程序和库调查 部分。
与 direct_url.json 的兼容性
本提案未对 PEP 610 及其 相应规范 PyPA 规范 中描述的 direct_url.json 文件进行任何更改。
provenance_url.json 文件的内容设计旨在最终允许安装程序在直接 URL 指向源存档或 wheel 时重用支持 direct_url.json 的一些逻辑。
provenance_url.json 和 direct_url.json 文件之间的主要区别在于 provenance_url.json 文件中强制性的键及其值。这有助于确保 provenance_url.json 文件的使用者可以在文件存在于 .dist-info 目录中时依赖其内容。
安全隐患
provenance_url.json 文件的主要安全特性之一是能够审计 Python 环境中已安装的工件。工具可以检查哪些 Python 包索引用于安装 Python 分发包,以及它们的发布工件的哈希摘要。
举个例子,我们可以看看最近在 PyTorch 事件 中受损的依赖链。PyTorch 索引提供了一个名为 torchtriton 的包。攻击者在 PyPI 上发布了 torchtriton,该包运行了一个恶意二进制文件。通过检查 provenance_url.json 文件中声明的已安装 Python 分发版的 URL,工具可以自动检查已安装 Python 分发版的来源。在 PyTorch 事件中,torchtriton 的 URL 应该指向 PyTorch 索引,而不是 PyPI。工具可以通过检查已安装 Python 分发版的 URL 来帮助识别此类恶意安装的 Python 分发版。更精确的检查还可以包括 provenance_url.json 文件中声明的已安装 Python 分发版的哈希。这种对哈希的检查对于镜像的 Python 包索引可能很有用,因为在这些索引中,Python 分发版无法通过其源 URL 进行区分,从而确保只安装所需的 Python 包分发版。
恶意行为者可能会故意调整 provenance_url.json 的内容,以可能隐藏已安装 Python 分发版的来源信息。揭露此类恶意活动的安全检查超出了本 PEP 的范围,因为它需要监控文件系统上的操作并最终审查用户或文件权限。
如何教授此内容
provenance_url.json 元数据文件 предназначены для инструментов, и конечные пользователи无法直接看到。
示例
有效 provenance_url.json 的示例
一个有效的 provenance_url.json 列出了多个哈希
{
"archive_info": {
"hashes": {
"blake2s": "fffeaf3d0bd71dc960ca2113af890a2f2198f2466f8cd58ce4b77c1fc54601ff",
"sha256": "236bcb61156d76c4b8a05821b988c7b8c35bf0da28a4b614e8d6ab5212c25c6f",
"sha3_256": "c856930e0f707266d30e5b48c667a843d45e79bb30473c464e92dfa158285eab",
"sha512": "6bad5536c30a0b2d5905318a1592948929fbac9baf3bcf2e7faeaf90f445f82bc2b656d0a89070d8a6a9395761f4793c83187bd640c64b2656a112b5be41f73d"
}
},
"url": "https://files.pythonhosted.org/packages/07/51/2c0959c5adf988c44d9e1e0d940f5b074516ecc87e96b1af25f59de9ba38/pip-23.0.1-py3-none-any.whl"
}
一个有效的 provenance_url.json 列出了单个哈希条目
{
"archive_info": {
"hashes": {
"sha256": "236bcb61156d76c4b8a05821b988c7b8c35bf0da28a4b614e8d6ab5212c25c6f"
}
},
"url": "https://files.pythonhosted.org/packages/07/51/2c0959c5adf988c44d9e1e0d940f5b074516ecc87e96b1af25f59de9ba38/pip-23.0.1-py3-none-any.whl"
}
一个有效的 provenance_url.json 列出了用于构建和安装 wheel 的源分发
{
"archive_info": {
"hashes": {
"sha256": "8bfe29f17c10e2f2e619de8033a07a224058d96b3bfe2ed61777596f7ffd7fa9"
}
},
"url": "https://files.pythonhosted.org/packages/1d/43/ad8ae671de795ec2eafd86515ef9842ab68455009d864c058d0c3dcf680d/micropipenv-0.0.1.tar.gz"
}
无效 provenance_url.json 的示例
以下示例在 archive_info 字典中包含一个 hash 键,这最初是在 记录已安装分发版的直接 URL 来源 中记录的数据结构中设计的。为避免与 hashes 产生任何可能的混淆以及保持哈希值同步所需的额外检查,不得出现 hash 键。
{
"archive_info": {
"hash": "sha256=236bcb61156d76c4b8a05821b988c7b8c35bf0da28a4b614e8d6ab5212c25c6f",
"hashes": {
"sha256": "236bcb61156d76c4b8a05821b988c7b8c35bf0da28a4b614e8d6ab5212c25c6f"
}
},
"url": "https://files.pythonhosted.org/packages/07/51/2c0959c5adf988c44d9e1e0d940f5b074516ecc87e96b1af25f59de9ba38/pip-23.0.1-py3-none-any.whl"
}
另一个例子展示了一个无效的哈希名称。引用的哈希名称与本 PEP 和 Python 文档中 hashlib.hash.name 下描述的规范哈希名称不符。
{
"archive_info": {
"hashes": {
"SHA-256": "236bcb61156d76c4b8a05821b988c7b8c35bf0da28a4b614e8d6ab5212c25c6f"
}
},
"url": "https://files.pythonhosted.org/packages/07/51/2c0959c5adf988c44d9e1e0d940f5b074516ecc87e96b1af25f59de9ba38/pip-23.0.1-py3-none-any.whl"
}
最后一个例子展示了一个 provenance_url.json 文件,其中下载的工件没有可用的哈希
{
"archive_info": {
"hashes": {}
}
"url": "https://files.pythonhosted.org/packages/07/51/2c0959c5adf988c44d9e1e0d940f5b074516ecc87e96b1af25f59de9ba38/pip-23.0.1-py3-none-any.whl"
}
pip 命令及其对 provenance_url.json 和 direct_url.json 的影响示例
这些命令生成 direct_url.json 文件,但不生成 provenance_url.json 文件。这些示例遵循 直接 URL 数据结构 规范中的示例
pip install https://example.com/app-1.0.tgzpip install https://example.com/app-1.0.whlpip install "git+https://example.com/repo/app.git#egg=app&subdirectory=setup"pip install ./apppip install file:///home/user/apppip install --editable "git+https://example.com/repo/app.git#egg=app&subdirectory=setup"(在这种情况下,url将是 git 仓库被克隆到的本地目录,并且dir_info将存在并带有"editable": true且未设置vcs_info)pip install -e ./app
生成 provenance_url.json 文件但不生成 direct_url.json 文件的命令
pip install apppip install app~=2.2.0pip install app --no-index --find-links "https://example.com/"
此行为可以使用 PR pypa/pip#11865 中对 pip 的更改进行测试。
参考实现
在安装 Python 分发包 时创建 provenance_url.json 元数据文件的概念验证可在 pip 的 PR pypa/pip#11865 中找到。它重用了 直接 URL 数据结构 的现有实现,以便在不创建 direct_url.json 的情况下提供 provenance_url.json 元数据文件。
PDM 中支持 provenance_url.json 文件的参考实现可在 pdm-project/pdm#3013 中找到。
开发了一个名为 pip-preserve 的原型,用于演示创建 requirements.txt 文件,该文件考虑了 direct_url.json 和 provenance_url.json 元数据文件。此工具模仿 pip freeze 功能,但已安装软件包的列表还包括 Python 分发工件的哈希值。
为进一步支持此提案,pip-sbom 演示了以 SPDX 格式创建 SBOM。该工具使用了存储在 provenance_url.json 文件中的信息。
被拒绝的想法
将文件命名为 direct_url.json 而不是 provenance_url.json
为了保持与 记录已安装分发版的直接 URL 来源 的向后兼容性,该文件不能命名为 direct_url.json,根据该规范的文本
当从其他类型的需求(即名称加上版本说明符)安装分发时,不得创建此文件。
这种更改可能会对 direct_url.json 的使用者引入向后兼容性问题,这些使用者仅在通过直接 URL 引用安装分发时才依赖其存在。
弃用 direct_url.json 并仅使用 provenance_url.json
文件 direct_url.json 已经通过 直接 URL 数据结构 规范确立,并已被安装程序使用。例如,pip 使用 direct_url.json 在 pip freeze 上报告直接 URL 引用。弃用 direct_url.json 将需要对 pip 中 pip freeze 的实现进行额外更改(参见 PR fridex/pip#2),并且可能对已有的 direct_url.json 消费者引入向后兼容性问题。
将哈希键保留在 archive_info 字典中
直接 URL 数据结构 规范讨论了在 archive_info 字典中包含 hash 键和 hashes 键的可能性。本 PEP 明确不将 hash 键包含在 provenance_url.json 文件中,并且只允许 hashes 键存在。通过这样做,我们消除了文件中的可能冗余、可能的混淆以及为确保哈希同步而需要进行的任何额外检查。
允许不声明哈希
对于从 pip 缓存安装 wheel 文件并使用旧版本 pip 构建的情况,pip 不会记录下载的源分发版的哈希。由于我们没有这些下载的源分发版的哈希,provenance_url.json 文件中的 hashes 键将不包含任何条目。在这种情况下,由于来源信息不完整,pip 不会创建任何 provenance_url.json 文件。建议消费者在这些情况下使用更新版本的 pip 重新构建 wheel。
uv 的开发者 提出了对 provenance_url.json 文件中要求至少一个哈希的担忧,因为 uv 除非明确要求,否则不计算分发哈希。然而,要求至少一个哈希有助于对分发进行完整性检查。这在涉及锁文件或将分发标识为 SBOM 的一部分的场景中很重要。provenance_url.json 文件强制包含下载分发的至少一个哈希。作为安装过程的一部分不计算分发哈希的安装程序(例如,出于性能原因)可以省略创建 provenance_url.json 文件。
使哈希键可选
PEP 610 及其 相应规范 PyPA 规范 建议在 direct_url.json 文件中的 archive_info 中包含 hashes 键,但这不是必需的(根据 RFC 2119 语言)
应该存在一个哈希键,其值为一个字典,将哈希名称映射到文件的十六进制编码摘要。
本 PEP 要求,如果创建 provenance_url.json 文件,则 archive_info 中必须包含 hashes 键;根据本 PEP
archive_info的值必须是一个字典,其中包含一个键hashes。
通过这样做,provenance_url.json 的消费者可以在安装程序创建 provenance_url.json 文件时检查工件摘要。
存储索引 URL
曾提出一种可能性,将索引 URL 作为文件内容的一部分存储。此索引 URL 将表示 pip 配置中配置的索引,或使用 --index-url 或 --extra-index-url 选项指定的索引。存储此信息被认为会引起混淆,尤其是在使用其他安装选项(如 --find-links)时。由于实际的索引 URL 与下载 wheel 文件的位置并非严格绑定,我们决定不在 provenance_url.json 文件中存储索引 URL。
未解决的问题
Conda 中 provenance_url.json 文件的可用性
我们希望从 Conda 维护者那里获得关于 provenance_url.json 文件的反馈。目前尚不清楚 Conda 是否愿意采用 provenance_url.json 文件。Conda 已经将来源相关信息(类似于本 PEP 中提议的来源信息)存储在 conda-meta 目录中的 JSON 文件中,遵循其安装过程中的操作。
在下游安装程序中使用 provenance_url.json
提议的 provenance_url.json 文件主要旨在由 Python 安装程序采用。其他安装程序,如 APT 或 DNF,可能会以其自身特定于下游包管理的方式记录已安装的下游 Python 分发包的来源。预计这些下游包安装程序不会创建提议的文件,因此它们被有意地排除在本 PEP 之外。然而,这些安装程序的开发人员或维护者的任何输入对于可能丰富 provenance_url.json 文件并以某种方式提供帮助都很有价值。
附录:安装程序和库调查
pip
pip 内部 API 中负责安装 wheel 的函数,名为 _install_wheel,不会在 .dist-info 目录中存储任何 provenance_url.json 文件。此外,在 pypa/pip#11865 中向 pip 引入上述文件的原型演示了在 pip 源代码中整合处理 provenance_url.json 文件的逻辑。
由于 pip 被下面提到的一些工具用于安装 Python 包分发,因此 pip 的发现适用于这些工具,因为 pip 不允许在其内部 API 中参数化在 .dist-info 目录中创建文件。下面提到的大多数使用 pip 的工具都将 pip 作为子进程调用,这不会对 provenance_url.json 文件最终是否存在于 .dist-info 目录中产生影响。
distlib
distlib 实现了操纵 dist-info 目录的低级功能。根据 distlib 的源代码,已安装分发数据库不使用任何名为 provenance_url.json 的文件。
Pipenv
Pipenv 使用 pip 来安装 Python 包分发。没有发现任何额外的逻辑会导致在 .dist-info 目录中引入 provenance_url.json 文件时出现向后兼容性问题。
installer
installer 不显式创建 provenance_url.json 文件。然而,根据 记录已安装项目 规范,installer 允许传递 additional_metadata 参数以在 .dist-info 目录中创建文件 - 请参阅 源代码。为避免任何向后兼容性问题,任何使用 installer 的库或工具不得使用上述 additional_metadata 参数请求创建 provenance_url.json 文件。
Poetry
Poetry 中的安装逻辑取决于 installer.modern-installer 配置选项(参见文档)。
在 installer.modern-installer 配置选项设置为 false 的情况下,Poetry 使用 pip 来安装 Python 包分发。
另一方面,当 installer.modern-installer 配置选项设置为 true 时,Poetry 使用 安装程序来安装 Python 包分发。从链接的源可以看出,没有传递任何名为 provenance_url.json 的额外元数据文件,这会导致与本 PEP 的兼容性问题。
Conda
Hatch
micropipenv
由于 micropipenv 是 pip 的一个封装,它使用 pip 来安装 Python 分发,既用于 锁文件,也用于 需求文件。
Thamos
Thamos 使用 micropipenv 来安装 Python 包分发,因此 micropipenv 的任何发现都适用于 Thamos。
PDM
PDM 使用安装程序 安装二进制分发版。它最终在 .dist-info 目录中创建的唯一附加元数据文件是 REFER_TO 文件。
uv
uv 用 Rust 编写,并在安装 wheels 时使用其自己的安装逻辑。它不会在 .dist-info 目录中创建任何与 provenance_url.json 文件命名冲突的附加文件。
致谢
感谢 Dustin Ingram、Brett Cannon 和 Paul Moore 的初步讨论,本提案由此产生。
感谢 Donald Stufft、Ofek Lev 和 Trishank Kuppusamy 的早期反馈和支持,使我得以开展这项 PEP 的工作。
感谢 Gregory P. Smith、Stéphane Bidoul、C.A.M. Gerlach 和 Adam Turner 审阅本 PEP 并提供了宝贵的建议。
感谢 Seth Michael Larson 的支持,提供了宝贵的建议以及提出的 pip-sbom 原型。
感谢 Stéphane Bidoul 和 Chris Jerdonek 的 PEP 610 以及相关的 记录已安装分发版的直接 URL 来源 和 直接 URL 数据结构 规范。
感谢 Frost Ming 提出了关于将索引 URL 存储在 provenance_url.json 文件中的可能担忧,以及 PDM 对 PEP 710 的初步支持。
感谢 Charlie Marsh 和 Zanie Blue 对 uv 安装程序提出的意见。
最后,但同样重要的是,感谢 Donald Stufft 赞助本 PEP。
版权
本文档置于公共领域或 CC0-1.0-Universal 许可证下,以更宽松者为准。
来源: https://github.com/python/peps/blob/main/peps/pep-0710.rst
最后修改: 2025-07-06 09:23:40 GMT