Python包启动配置与安全改进

3 阅读6分钟

摘要

本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提出以下内容:

  1. 保留<name>.pth文件格式,但弃用import行处理三年,之后将禁止此类行。
  2. 保持当前<name>.pth文件对sys.path的扩展功能不变。
  3. 添加名为<name>.start的新文件格式,其命名符合pkgutil.resolve_name()参数的“冒号形式”入口点。
  4. 在弃用期内,如果存在与<name>.pth匹配的<name>.start文件,则禁用前者中import行的执行,转而使用后者的入口点。这为同时支持本PEP的Python版本和不支持的老版本提供了迁移路径。
  5. 弃用期结束后,<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文件的操作和最佳实践。包作者的迁移指南包括:

  1. 若包当前提供.pth文件,分析是否用于sys.path扩展或启动代码执行。
  2. 若使用代码执行功能,在包内可导入模块中创建零参数可调用对象,并在匹配的.start文件中命名这些入口点。
  3. 若需同时支持新旧Python,在.pth中使用import pkg.mod; pkg.mod.callable()形式,旧版本执行这些行,新版本忽略它们而使用.start文件。
  4. 迁移期后,从.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