AI 最流行框架之一中被遗忘的遗留 API,如何悄然让数百万应用暴露于任意文件读取风险之下。
当你写的概念验证代码第一次运行就成功时,会有一种特别的感觉。不完全是兴奋——更像是一种缓慢而不安的醒悟:刚刚真的发生了什么。当我用测试配置运行 load_prompt_from_config(),看着一个本不该被读取的文件内容直接打印到终端时,就是这种感觉。
这就是 CVE-2026-34070 的故事:一个存在于 langchain-core 中的路径遍历漏洞,现已在 1.2.22 版本中修复。
如果你在过去几年里用 AI 构建过任何东西,你几乎肯定接触过 LangChain。它是现代 AI 技术栈的粘合剂——这个框架将语言模型、向量存储、工具和提示词管理串联成完整的应用。在 GitHub 上拥有超过 13 万颗星,从临时起意的周末项目到企业级部署无处不在,这里的漏洞不会只停留在局部。
我一直在对提示词子系统做代码审查,这时有个东西引起了我的注意:一个名为 langchain_core/prompts/loading.py 的模块。它根据直接从反序列化配置字典中取出的值来从磁盘加载文件。没有验证。没有路径清理。只有 open(path)。
我继续读了下去。
三个内部函数是问题的核心:
_load_template() — 读取由 template_path、suffix_path 和 prefix_path 引用的文件_load_examples() — 当 examples 键为字符串时,读取其引用的文件_load_few_shot_prompt() — 读取由 example_prompt_path 引用的文件文件扩展名检查是存在的。模板是 .txt。示例是 .json、.yaml、.yml。但没有任何机制阻止这些路径是绝对路径(/etc/passwd)或基于遍历的路径(../../../../home/user/.ssh/)。扩展名过滤器给人一种虚假的安全感——它只是意味着攻击者必须选对扩展名,而不是攻击被阻止了。
这些函数都可以通过两个面向公众的 API 触达:load_prompt() 和 load_prompt_from_config()。
最简单的版本是这样的:
from langchain_core.prompts.loading import load_prompt_from_config
config = {
"_type": "prompt",
"template_path": "/tmp/secret.txt",
"input_variables": [],
}
prompt = load_prompt_from_config(config)
print(prompt.template) # /tmp/secret.txt 的内容,干净地打印出来
就是这样。传入一个绝对路径,就能以 PromptTemplate 的形式拿回文件内容。无需认证。无需特殊权限。如果你能影响配置字典,你就能读取文件。
目录遍历同样干净利落:
config = {
"_type": "prompt",
"template_path": "../../etc/secret.txt",
"input_variables": [],
}
JSON/YAML 变体可以说更危险,因为它能触及的文件类型更多:
config = {
"_type": "few_shot",
"examples": "../../../../.docker/config.json",
"example_prompt": {
"_type": "prompt",
"input_variables": ["input", "output"],
"template": "{input}: {output}",
},
"prefix": "",
"suffix": "{query}",
"input_variables": ["query"],
}
prompt = load_prompt_from_config(config)
.docker/config.json 包含你的 Docker Hub 凭证。~/.azure/accessTokens.json 里有你的 Azure 令牌。Kubernetes 清单、CI/CD 配置、内部应用设置——文件系统上任何带有正确扩展名的文件都是攻击目标。
CVSS 评分为 7.5 高危,攻击向量为 AV:N/AC:L/PR:N/UI:N——可远程访问、低复杂度、无需权限、无需用户交互。
评分被限制在 9 分以下,是因为文件扩展名检查确实限制了可读取的文件范围。但在某些部署模式下,7.5 仍然低估了现实世界的影响。
想想这在生产环境中会落在哪里:
load_prompt_from_config(),每个用户都是潜在的攻击者。在这些环境中,"受文件扩展名限制"的局限性就变得不那么重要了。攻击者只需针对他们知道存在且带有正确扩展名的文件即可。在典型的云实例上:requirements.txt、config.yaml、.env.yaml、带有 .json 扩展名的挂载密钥文件——这个清单很长。
受影响的函数在公告中被描述为"未记录的遗留 API"。它们早于当前的 langchain_core.load 序列化系统(dumpd/dumps/load/loads),后者基于允许列表模型,不执行文件系统读取。
新 API 是存在的。它们更好。但旧代码从未被清理——它就那样躺在那里,可被触达,没有任何验证,静静等待。
这是一个值得关注的模式。在快速发展的开源项目中,尤其是像 LangChain 这样增长迅猛的项目,技术债务会堆积在角落里。那些"从未真正打算给用户用"的遗留代码,不会像主要 API 表面那样受到同样的审查。但它仍然可以被调用。它仍然在包里。如果它从磁盘读取文件,那就是一个潜在的漏洞。
补丁已随 langchain-core 1.2.22 发布。修复在打开任何文件之前增加了路径验证,拒绝绝对路径和 .. 遍历序列。同时提供了一个逃生舱——allow_dangerous_paths=True——供确实需要从可信路径读取文件的应用使用,并明确承认调用者是在主动选择承担风险。
遗留 API 也已在此版本中正式弃用。它们将在 2.0.0 中被完全移除。如果你在任何地方使用了 load_prompt() 或 load_prompt_from_config(),请立即迁移到 langchain_core.load 的对应功能,而不是等到破坏性变更到来。
立即更新:
pip install --upgrade langchain-core
确认你已升级到 1.2.22 或更高版本:
python -c "import langchain_core; print(langchain_core.__version__)"
如果你基于 LangChain 构建,值得快速做一次审计:
load_prompt 和 load_prompt_from_config。如果它们出现,检查传入的是什么。.txt、.json 或 .yaml 扩展名的文件,以及它们包含什么内容。随着这些系统处理越来越敏感的工作负载,AI 基础设施中的安全漏洞只会变得更加重要,而不是更不重要。LangChain 团队应对得很好——修复干净利落,弃用路径清晰,公告文档也很详尽。
但这提醒我们,AI 应用的攻击面不仅仅是模型本身。它是技术栈中的每一个库,每一个从未被清理的遗留函数,每一个"用户大概不会在这里传入不可信输入"最终被证明只是假设而非保证的地方。
去读代码吧。尤其是那些旧的部分。
CVE-2026-34070 被分配给了这个漏洞。完整公告可在 LangChain GitHub 安全公告 中查看。