flawopen.com/Teardowns/cve-2026-104873-langgraph-sdk-auth-action-bypass
LangGraph CVE-2026-104873:授权绕过
LangGraph SDK 0.1.45–0.4.3 忽略三个 Python 装饰器的操作筛选。请升级到 0.4.4。 具体影响取决于处理器是否检查权限。[1]
想象一栋楼的前台只负责收件,但路由系统把所有访客都送到前台。如果前台只确认访客进过大楼,他们就可能进入其他柜台本应锁住的房间。受影响 SDK 注册资源处理器时忽略 actions=,因此处理器也可能收到读取、更新或删除请求。只有处理器本身没有执行权限检查时才有风险。[1]
核心概念与专有名词
CWE-863- 授权不正确:检查未落实预期权限。[1]
框架映射
- 本指南所述弱点的 CWE 参考: CWE-863
- OWASP API Security Top 10 — API5:2023 功能级授权失效
根本原因剖析 (Root Cause)
0.1.45–0.4.3 接受 actions=,却将处理器注册到资源通配符 * 下。LangGraph 可能先选择它而不是通用回退处理器。如果函数只授权预期操作,其他操作就可能绕过回退检查。只有应用依赖被跳过的检查且处理器未自行验证操作和所有权时,已认证用户才可能访问他人资源。0.4.4 校验列表并逐个注册操作;未提供 actions= 时仍有意保留通配行为。公告列为 CWE-863、CVSS 4.0 7.6 High。这是依赖配置的授权缺陷,不是未认证绕过,也不影响所有 LangGraph 部署。 受影响的装饰器为:@auth.on.threads / @auth.on.assistants / @auth.on.crons. 修复的重要边界:SDK 改动会按声明的操作限定处理器注册;但如果应用未配置全局默认拒绝处理器,未匹配的自定义授权路径仍会被允许。0.4.4 更新日志说明 langgraph-api 0.10+ 会在启动时警告未覆盖路径;该警告用于发现问题,并不执行访问控制。[1], [2], [5]
攻击执行流程分解
源代码对比:漏洞与安全实现
易受影响片段展示旧行为:_ResourceOn.__call__ 无论 actions= 如何都使用 *。修复会校验列表并逐个注册操作。这是源码片段,不是完整模块替换。请升级,并要求处理器检查操作和资源所有权。 源代码位置: libs/sdk-py/langgraph_sdk/auth/__init__.py, method _ResourceOn.__call__.[1], [2]
源码摘录: 按操作注册处理器
def __call__(self, fn=None, *, resources=None, actions=None):
if fn is not None:
_validate_handler(fn)
return typing.cast("_ActionHandler", _register_handler(
self.auth, self.resource, "*", fn
))
def decorator(handler):
_validate_handler(handler)
return typing.cast("_ActionHandler", _register_handler(
self.auth, self.resource, "*", handler
))
return decorator
def __call__(self, fn=None, *, resources=None, actions=None):
def decorator(handler):
_validate_handler(handler)
if resources is None:
resource_list = [self.resource]
elif isinstance(resources, str):
resource_list = [resources]
elif isinstance(resources, Sequence):
resource_list = list(resources)
else:
raise TypeError("resources must be a string or sequence of strings")
if resource_list != [self.resource]:
raise ValueError("use @auth.on(...) for other resources")
if actions is None:
action_list = ["*"]
elif isinstance(actions, str):
action_list = [actions]
elif isinstance(actions, Sequence):
action_list = list(actions)
else:
raise TypeError("actions must be a string or sequence of strings")
if not action_list:
raise ValueError("actions must not be empty")
if not all(isinstance(action, str) for action in action_list):
raise TypeError("actions must be strings")
valid_actions = {
value.action for value in vars(self).values()
if isinstance(value, _ResourceActionOn)
}
invalid_actions = (
sorted(set(action_list) - valid_actions) if actions is not None else []
)
if invalid_actions:
raise ValueError("invalid action for this resource")
if len(action_list) != len(set(action_list)):
raise ValueError("actions must not contain duplicates")
for action in action_list:
if (self.resource, action) in self.auth._handlers:
raise ValueError("handler already registered for action")
for action in action_list:
_register_handler(self.auth, self.resource, action, handler)
return handler
if fn is not None:
return decorator(typing.cast("_ActionHandler", fn))
return decorator
工程与系统安全加固清单
- ✓将 SDK 0.1.45–0.4.3 升级到 0.4.4 或更高版本。[1], [3]
- ✓搜索使用
actions=的三个受影响装饰器。[1], [2] - ✓检查操作权限及对确切资源 ID 的访问权。[1]
- ✓测试已声明和未声明的操作,包括拒绝路径。[2]
- ✓测试 thread、assistant、cron 的跨用户访问。[1]
- ✓启动时拒绝重复、空或未知列表。[2]
- ✓不要将登录等同于明确授权。
- ✓为未被按操作限定的处理器覆盖的所有自定义授权路径配置全局默认拒绝处理器。langgraph-api 0.10+ 会警告未覆盖路径,但启动警告不会拒绝请求。[5], [1]