摘要
本PEP改变了包影响Python启动过程的方式。之前的.pth文件由site.py在解释器启动期间解析和执行,用于扩展sys.path以及在将控制权交给用户代码第一行之前执行包初始化代码。由于其任意代码执行功能的晦涩特性,历史形式的.pth文件长期被提议移除。最近的供应链攻击已将.pth文件中的任意代码执行作为攻击向量。本PEP并未完全关闭此向量,但通过缩小攻击面并启用未来的策略机制(用于控制允许或阻止哪些包扩展路径和执行启动代码),提出了重要且有用的改进。
动机
Python的.pth文件支持两种功能:
- 扩展
sys.path:文件中非注释且不以import开头的行指定要追加到sys.path的目录。相对路径会隐式锚定在site-packages目录。 - 执行任意代码:以
import开头的行立即通过将源字符串传递给exec()来执行。
尽管两者都有有效用例,但import行功能问题最大,因为:
- 代码执行是实现带来的副作用。
import行在解释器启动期间使用exec()执行,这打开了广泛的攻击面。- 没有明确入口点的概念,而这在Python打包中已是既定模式。
规范
本PEP提出以下内容:
- 保留
<name>.pth文件格式,但弃用import行处理三年,之后将禁止此类行。 - 保持当前
<name>.pth文件对sys.path的扩展功能不变。 - 添加名为
<name>.start的新文件格式,其命名符合pkgutil.resolve_name()参数的“冒号形式”入口点。 - 在弃用期内,如果存在与
<name>.pth匹配的<name>.start文件,则禁用前者中import行的执行,转而使用后者的入口点。这为同时支持本PEP的Python版本和不支持的老版本提供了迁移路径。 - 弃用期结束后,
<name>.pth文件中的import行将被忽略并发出警告。
处理阶段
site.py启动代码分为以下明确阶段:
- 查找
<name>.pth文件,按文件名排序。 - 解析
.pth文件,收集所有路径扩展(去重)。 - 可选:应用全局策略过滤器。
- 将路径扩展追加到
sys.path。 - 列出所有
<name>.start文件,排序。 - 对于与已扫描
.pth文件匹配的.start文件,丢弃匹配的.pth文件中的所有import行。 - 解析
.start文件,收集所有入口点。 - 可选:应用全局策略过滤器。
- 对于每个入口点,使用
pkgutil.resolve_name()解析为可调用对象,无参数调用之。
入口点语法
本PEP仅允许pkg.mod:callable形式的入口点,并且要求指定可调用对象。
文件命名与发现
- 包可选地安装零个或多个
<name>.pth和<name>.start文件。 <name>前缀任意,但建议与包名匹配以清晰。- 文件按字母顺序处理。
.start文件位于与.pth文件相同的site-packages目录中。- 以
.开头的文件名和具有操作系统级隐藏属性的文件被排除。
错误处理
- 解析错误通常被跳过,仅在给Python传递
-v(详细)标志时报告。 - 如果无法打开或读取文件,则跳过并继续处理下一个文件。
- 无效的入口点规范被跳过。
- 执行期间,错误打印到
sys.stderr,处理继续。
编码
<name>.start文件必须使用utf-8-sig编码。<name>.pth文件也应使用utf-8-sig编码。当前回退到当前区域设置的解码支持被弃用(5年后强制要求utf-8-sig)。
原理
- 两文件方法:简单演化式改进。弃用通过
exec()执行import行,替换为更窄的模块内入口点函数调用,减少攻击面。 - 关注点分离:将
sys.path扩展与入口点规范拆分到两个文件中,处理规则清晰。 - 两阶段处理:解析和执行分离,为未来全局策略机制提供挂钩。
向后兼容性
本PEP提出对.pth文件中import行处理进行3年弃用期。.pth文件中的sys.path扩展保持不变。使用当前任意代码执行功能的包可以轻易迁移:将代码移到可导入模块内的可调用对象中,然后在.start文件中命名该入口点。
安全影响
本PEP改进了解释器启动的安全状态:
- 移除
exec()的任意代码执行,替换为更受约束和可审计的入口点执行。 - 将路径扩展与代码执行拆分到两个独立文件中,以便准确了解任意代码执行发生的位置。
- Python的导入系统用于访问和运行入口点,因此标准审计钩子(PEP 578)可以提供监控。
- 两阶段处理模型为未来策略机制创建了自然挂钩,以检查和限制执行内容。
包.模块:可调用语法将执行限制在可导入模块内的可调用对象。
如何教授
site模块文档将更新以描述.pth和.start文件的操作和最佳实践。包作者的迁移指南包括:
- 若包当前提供
.pth文件,分析是否用于sys.path扩展或启动代码执行。 - 若使用代码执行功能,在包内可导入模块中创建零参数可调用对象,并在匹配的
.start文件中命名这些入口点。 - 若需同时支持新旧Python,在
.pth中使用import pkg.mod; pkg.mod.callable()形式,旧版本执行这些行,新版本忽略它们而使用.start文件。 - 迁移期后,从
.pth文件中移除所有import行。
参考实现
参考实现支持本PEP的当前版本。
拒绝的想法
- 仅在
.pth文件中添加入口点而保留import行(拒绝理由:意图混淆) <name>.site.toml文件(认为结构过于复杂)- 单一站点范围配置文件(需要独立安装的包之间协调)
- 优先级或权重字段(包独立安装,无优先级仲裁者)
- 向可调用对象传递参数(无明确有用的参数)
待解决问题
- 入口点语法的严格限制:本PEP建议使用
pkg.mod:callable的窄定义,不接受非冒号形式,并要求冒号后必须指定可调用对象。 site.addpackage()非文档化函数的弃用问题。- 弃用期内警告的隐藏策略:建议将所有
.pth文件中import行的警告隐藏在-v详细标志后面。
变更历史
- 2026-04-19:添加编码要求,更新迁移指南。
- 2026-04-15:警告仅在
-v时发出;明确匹配.start文件时忽略.pth中的import行。 - 2026-04-13:更改PEP标题,提出
.pth格式演进和.start文件添加,提议三年弃用期。
致谢
感谢Paul Moore、Emma Smith和Brett Cannon的反馈与鼓励。FINISHED