flawopen.com/Teardowns/cve-2026-104873-langgraph-sdk-auth-action-bypass
LangGraph CVE-2026-104873: bypass de autorización
LangGraph SDK 0.1.45–0.4.3 ignoraba filtros de acción en tres decoradores Python. Actualice a 0.4.4.[1]
Imagina la recepción de un edificio destinada solo a entregas, pero el sistema envía allí a todos. Si solo comprueba quién entró, alguien podría llegar a habitaciones que otro mostrador mantendría cerradas. El SDK afectado ignoraba actions= al registrar un handler de recurso, que podía recibir lecturas, cambios o eliminaciones. El riesgo depende de que el handler no compruebe los permisos necesarios.[1]
Conceptos Clave y Términos
CWE-863- Autorización incorrecta: la comprobación no aplica el permiso previsto.[1]
Mapeos de marcos
- Referencia CWE para la debilidad descrita en esta guía: CWE-863
- OWASP API Security Top 10 — API5:2023 Autorización Rota a Nivel de Función
Análisis de Causa Raíz
En 0.1.45–0.4.3, los tres decoradores afectados aceptaban actions=, pero registraban el handler con el comodín * del recurso. LangGraph podía elegirlo antes que el respaldo general. Si el handler solo autorizaba la acción prevista, podía recibir otras sin esas comprobaciones. El acceso a recursos ajenos requería que la aplicación dependiera de ellas y que el handler no verificara acción y propiedad. 0.4.4 valida la lista y registra cada acción por separado; sin actions=, mantiene el comodín intencionadamente. El aviso asigna CWE-863 y CVSS 4.0 7.6 High. Es un fallo condicionado por la configuración, no un bypass sin autenticación ni un defecto de todas las implementaciones LangGraph. Los decoradores afectados son: @auth.on.threads / @auth.on.assistants / @auth.on.crons. Límite importante de la corrección: el cambio del SDK restringe los registros a las acciones declaradas, pero las rutas de autorización personalizada sin coincidencia siguen permitidas si la aplicación no configura un controlador global de denegación por defecto. El changelog de 0.4.4 indica que langgraph-api 0.10+ advierte al iniciar sobre rutas sin cobertura; considere esa advertencia una señal de detección, no un control de acceso.[1], [2], [5]
Flujo de Ataque Paso a Paso
Código Fuente: Vulnerable vs. Seguro
El fragmento vulnerable muestra el comportamiento anterior: _ResourceOn.__call__ usaba * sin importar actions=. La corrección valida y registra cada acción por separado. Es un extracto, no un reemplazo del módulo. Actualice y haga que los handlers comprueben acción y propiedad. Ubicación del código fuente: libs/sdk-py/langgraph_sdk/auth/__init__.py, method _ResourceOn.__call__.[1], [2]
Fragmento de origen: Registro del handler según la acción
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
Lista de Verificación de Seguridad para Ingeniería
- ✓Actualice SDK 0.1.45–0.4.3 a 0.4.4+.[1], [3]
- ✓Busque los tres decoradores afectados con
actions=.[1], [2] - ✓Compruebe acción y acceso al ID exacto del recurso.[1]
- ✓Pruebe acciones declaradas y no declaradas, incluidas las denegadas.[2]
- ✓Pruebe acceso entre usuarios a threads, assistants y crons.[1]
- ✓Rechace listas duplicadas, vacías o desconocidas al iniciar.[2]
- ✓No confunda inicio de sesión con autorización explícita.
- ✓Configure un controlador global de denegación por defecto para toda ruta de autorización personalizada que no cubran los controladores limitados por acción. langgraph-api 0.10+ avisa de las rutas sin cobertura, pero una advertencia al iniciar no deniega solicitudes.[5], [1]