FlawOpen

flawopen.com/Teardowns/cve-2026-71433-langgraph-store-namespace-boundary

● CVE-2026-71433 · CVSS 5.3 · 中危
安全研究 · FlawOpen

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
💡 通俗易懂的原理解析 (ELI5)

想象图书管理员要找名称以“团队”开头的文件夹。如果目录只比较字符串开头,也会把“团队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 和实际部署版本;确认托管环境已更新,不要仅凭产品名称推断是否受影响。

攻击执行流程分解

步骤 1

确认存储后端与标签

部署必须使用低于 3.1.1 的 langgraph-checkpoint-postgres 或 langgraph-checkpoint-sqlite,并依赖命名空间隔离用户或租户。共享前缀以及含 %、_ 的标签会使边界相关。

步骤 2

发起普通范围读取

经过身份验证的应用请求搜索 foo 等命名空间。调用者无需提交特殊载荷;普通命名空间就是错误匹配逻辑的输入。

步骤 3

后端拼接标签并匹配模式

Store 用点号连接标签,并使用以 % 结尾的 LIKE 前缀。由于没有要求分隔符,foo 可能匹配 foobar;标签中的模式字符也会扩大匹配范围。

步骤 4

可能返回相邻命名空间条目

如果应用将命名空间当作租户边界,搜索或列表可能返回其他用户存储的条目。这是读取路径上的机密性问题,不是 SQL 注入,也不会授予写入权限。

步骤 5

升级并重新测试隔离

将适用的两个包升级到 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)

工程与系统安全加固清单

参考来源