flawopen.com/Teardowns/cve-2026-71433-langgraph-store-namespace-boundary
LangGraph CVE-2026-71433:命名空间越界
LangGraph 的 Postgres 和 SQLite 范围读取可能越过命名空间边界,暴露相邻租户数据。请将两个 checkpoint 包升级到 3.1.1。
CVSS 向量
CVSS:3.1/AV:N/AC:H/PR:L/UI:N/S:U/C:H/I:N/A:N想象图书管理员要找名称以“团队”开头的文件夹。如果目录只比较字符串开头,也会把“团队2”一起找出来。LangGraph 用点号拼接命名空间标签,却使用了不要求点号分隔符的前缀匹配。修复后必须完全匹配命名空间,或确认后面确实有一个新段,因此不同租户的文件夹不会再混在一起。
核心概念与专有名词
Namespace(命名空间)- LangGraph Store 用来分组条目的标签元组。应用可能用它划分用户或租户边界。
前缀范围- 读取某个命名空间及其后代的请求。正确匹配必须尊重段边界:
foo包含foo.child,但不包含相邻命名空间foobar。 SQL `LIKE` 模式- 文本模式中
%匹配任意序列,_匹配单个字符。如果命名空间标签应按字面处理,就必须转义这些字符。 CWE-863- 授权不正确:Store 返回了调用者无权读取的命名空间记录。
框架映射
- 本指南所述弱点的 CWE 参考: CWE-863
根本原因剖析 (Root Cause)
在 3.1.1 之前,Postgres 和 SQLite checkpoint store 会用点号拼接命名空间元组,并使用 LIKE '<namespace>%' 限制搜索范围。LIKE 不理解命名空间分隔符,因此查询 foo 时也可能匹配相邻命名空间 foobar;合法标签中的 % 和 _ 还可能被当作通配符。list_namespaces 的前缀和后缀匹配也存在类似错误。普通范围读取即可触发,无需构造异常输入。问题仅影响 Postgres/SQLite Store 的 search 和命名空间列表读取路径,并且仅在应用依赖命名空间隔离租户时产生影响。get、put 和 delete 使用精确相等匹配,不受影响;InMemoryStore 按命名空间元素逐项比较,也不受影响。修复要求精确相等,或在后代段前出现点号;同时转义 LIKE 元字符,并在命名空间列表中采用段感知匹配。SQLite 搜索还避免了 LIKE 对 ASCII 大小写不敏感的行为。公告称暂无在野利用证据。受影响的包已在 3.1.1 修复。
CVE-2026-71433 部署可达性矩阵
| 部署与 namespace 形式 | 已记录的行为 | 排查措施 |
|---|---|---|
| PostgresStore / AsyncPostgresStore 或 SqliteStore / AsyncSqliteStore 低于 3.1.1;租户隔离依赖 namespace。 | 带范围的 search 或 list_namespaces 在标签共享文本前缀(foo / foobar、1 / 12)或包含 % / _ 时,可能匹配到相邻 namespace。SQLite 搜索还会把仅 ASCII 大小写不同的名称视为相同。 | 将对应 checkpoint 包升级到 3.1.1 或更高版本。针对生产后端测试相邻前缀、通配符、后缀匹配和大小写差异。 |
| 低于 3.1.1 的 SQLite Store,且 namespace 标签仅有 ASCII 大小写差异。 | 有缺陷的后代搜索使用不区分 ASCII 大小写的 LIKE,而精确操作会区分大小写。 | 升级;如果标签可能仅大小写不同,添加大小写回归测试。 |
不含 % 或 _ 的定长 UUID 标签。 | 公告说明此类标签不受影响:定长标签不可能互为前缀。 | 确认每个 namespace 段都满足这一约束。如果标签长度可变或包含通配符,应按受影响处理。 |
| InMemoryStore。 | 不受影响:它逐个比较 namespace 元素。 | 此 CVE 无需升级;仍应在应用边界检查租户所有权。 |
| LangSmith 托管部署。 | 默认的 LANGGRAPH_STORE_BACKEND=python 使用 AsyncPostgresStore,在依赖受影响版本时属于影响范围。独立的 grpc 后端已获得等效修复。 | 检查已配置的 backend 和实际部署版本;确认托管环境已更新,不要仅凭产品名称推断是否受影响。 |
攻击执行流程分解
确认存储后端与标签
部署必须使用低于 3.1.1 的 langgraph-checkpoint-postgres 或 langgraph-checkpoint-sqlite,并依赖命名空间隔离用户或租户。共享前缀以及含 %、_ 的标签会使边界相关。
发起普通范围读取
经过身份验证的应用请求搜索 foo 等命名空间。调用者无需提交特殊载荷;普通命名空间就是错误匹配逻辑的输入。
后端拼接标签并匹配模式
Store 用点号连接标签,并使用以 % 结尾的 LIKE 前缀。由于没有要求分隔符,foo 可能匹配 foobar;标签中的模式字符也会扩大匹配范围。
可能返回相邻命名空间条目
如果应用将命名空间当作租户边界,搜索或列表可能返回其他用户存储的条目。这是读取路径上的机密性问题,不是 SQL 注入,也不会授予写入权限。
升级并重新测试隔离
将适用的两个包升级到 3.1.1 或更高版本。使用独立测试租户检查相邻前缀、字面 % 和 _ 标签、后缀匹配,以及 SQLite 大小写行为。
源代码对比:漏洞与安全实现
易受影响的条件把任何以展平后命名空间开头的字符串都视为后代。修复增加精确相等分支,并要求后代段之前必须有字面点号;同时在 LIKE 模式中转义反斜杠、% 和 _。上游提交还将 list_namespaces 的前缀/后缀模式替换为段感知匹配,并调整 SQLite 搜索。此处为源代码摘录,并非完整模块。
源码摘录: libs/checkpoint-postgres/langgraph/store/postgres/base.py
def _prepare_batch_search_queries(op):
ns_condition = "TRUE"
ns_param = None
if op.namespace_prefix:
ns_condition = "store.prefix LIKE %s"
ns_param = (f"{_namespace_to_text(op.namespace_prefix)}%",)
return ns_condition, ns_param
def _escape_like_literal(text: str) -> str:
return text.replace("\\", "\\\\").replace("%", "\\%").replace("_", "\\_")
def _namespace_prefix_condition(namespace_prefix: tuple[str, ...]) -> tuple[str, tuple]:
path = _namespace_to_text(namespace_prefix)
condition = r"(store.prefix = %s OR store.prefix LIKE %s ESCAPE '\\')"
return condition, (path, f"{_escape_like_literal(path)}.%")
if op.namespace_prefix:
ns_condition, ns_param = _namespace_prefix_condition(op.namespace_prefix)
工程与系统安全加固清单
- ✓在所有安装位置将
langgraph-checkpoint-postgres和langgraph-checkpoint-sqlite升级到 3.1.1 或更高版本。 - ✓添加租户标签前缀重叠的回归用例,例如
foo和foobar,并确认每次搜索仅返回请求的命名空间及其后代。 - ✓使用生产环境的确切数据库后端测试含
%、_、反斜杠以及仅大小写不同的标签。 - ✓测试
list_namespaces的前缀、后缀、通配符和空路径行为;要求*按文档仅匹配一个段。 - ✓在应用边界保留授权检查;不透明或固定格式的命名空间 ID 不能替代租户所有权校验。