yaml 的 load() 函数默认不安全,会执行任意 python 对象构造;应始终使用 yaml.safe_load(),仅解析基础类型;必要时用 csafeloader 或 fullloader 并确保来源可信;避免超大文件,注意锚点与深拷贝风险。

YAML 的 load() 函数默认不安全
Python 的 PyYAML 库里,yaml.load() 会执行任意 Python 对象构造(比如 !python/object/apply),只要配置文件里写了恶意标签,就能触发代码执行。这不是“可能”,而是只要用了旧版默认调用方式,就等于在入口处开了个 eval() 后门。
实操建议:
立即学习“Python免费学习笔记(深入)”;
- 永远用
yaml.safe_load()替代yaml.load()—— 它只支持基础 YAML 类型(字符串、数字、列表、字典等),拒绝所有自定义标签和对象构造 - 如果必须解析带标签的 YAML(比如 Kubernetes 配置里的
!!str),确认来源绝对可信,并显式传入Loader=yaml.CSafeLoader(C 加速版)或yaml.FullLoader(仅限已知可控场景) - 检查项目依赖:运行
pip show pyyaml,若版本 safe_load 仍是默认行为但文档不明显;5.1+ 才真正把load()标为弃用
类型自动转换导致数据“悄悄变样”
YAML 解析器会按规则把某些字符串转成布尔、时间、数字甚至 null,比如 yes → True、2023-01-01 → datetime.date 对象、0123 → 83(八进制)。这些不是 bug,是 YAML 规范本身的设计,但和 Python 开发者直觉冲突。
实操建议:
立即学习“Python免费学习笔记(深入)”;
- 所有需要保持原始字符串语义的字段(如 ID、密码、带前导零的编号),加单引号包裹:
'0123'、'yes' - 避免用 YAML 表示“弱类型”配置,比如开关字段统一用
enabled: true而不是enabled: on,减少歧义 - 如果配置项需强类型校验,别靠 YAML 自动转换,改用
pydantic或dataclasses做反序列化后验证
注释和锚点在运行时不可见
YAML 支持注释(#)和锚点(&anchor + *anchor),但 yaml.safe_load() 返回的是纯 Python 原生结构(dict/list),所有注释被丢弃,锚点也被展开成重复值 —— 你没法在运行时知道某段配置原本有没有注释,也无法追溯某个值是否来自锚点引用。
实操建议:
立即学习“Python免费学习笔记(深入)”;
- 别把业务逻辑依赖注释(例如用注释标记“此字段已废弃”),这类信息应写进字段名或额外元数据字段
- 锚点适合简化编写,但会增加调试难度:两个看似独立的配置项实际指向同一内存对象,修改其中一个会影响另一个;如需深拷贝行为,加载后手动
copy.deepcopy() - 若需保留注释结构(如配置编辑工具),换用
ruamel.yaml库,它提供CommentedMap等类型,但代价是更重、API 更复杂
大文件或嵌套过深时性能和栈溢出风险
PyYAML 默认使用递归解析,当 YAML 文件超过几千行,或嵌套层级超过 100 层(比如意外生成的深度缩进模板),容易触发 RecursionError 或显著拖慢加载速度。这在 CI/CD 加载大型 Helm values 文件或微服务配置时很常见。
实操建议:
立即学习“Python免费学习笔记(深入)”;
- 用
yaml.CSafeLoader替代yaml.SafeLoader(纯 Python 实现),C 扩展版快 3–5 倍且递归控制更稳 - 限制嵌套深度:加载前先用正则粗筛缩进层数,或设置
yaml.Loader的yaml.load(stream, Loader=MyLimitedLoader)自定义限制 - 超大配置(>1MB)考虑拆分:按模块分文件,用 Python 逻辑合并,而不是堆一个巨无霸 YAML
最麻烦的其实是组合风险——比如用了 safe_load 却没防住类型转换,又或者加了锚点却忘了深拷贝,问题要等运行一段时间才暴露。配置不是写完就完的事,它得经得起 reload、diff、merge 和多人协作的反复折腾。









