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 Нарушение авторизации на уровне функций
Анализ первопричины
В версиях 0.1.45–0.4.3 декораторы принимали actions=, но регистрировали обработчик по маске ресурса *. LangGraph мог выбрать его раньше общего fallback. Если функция проверяла только ожидаемое действие, другие могли пройти без этих проверок. Доступ к чужим ресурсам требовал зависимости приложения от пропущенных проверок и отсутствия собственной проверки действия и владельца. Версия 0.4.4 проверяет список и регистрирует действия отдельно; без actions= маска намеренно сохраняется. Advisory указывает CWE-863 и CVSS 4.0 7.6 High. Ошибка зависит от конфигурации и не является обходом без аутентификации или дефектом каждой установки LangGraph. Затронуты декораторы: @auth.on.threads / @auth.on.assistants / @auth.on.crons. Важное ограничение исправления: изменение SDK ограничивает регистрацию указанными действиями, но непокрытые пути пользовательской авторизации остаются разрешёнными, если приложение не настроило глобальный обработчик с запретом по умолчанию. В changelog 0.4.4 сказано, что langgraph-api 0.10+ предупреждает при запуске о непокрытых путях; это предупреждение помогает обнаружению, но не обеспечивает принудительный запрет.[1], [2], [5]
Пошаговый сценарий атаки
Исходный код: Уязвимый vs Защищённый вариант
Уязвимый фрагмент показывает прежнее поведение: _ResourceOn.__call__ использовал * независимо от actions=. Исправление проверяет список и регистрирует каждое действие отдельно. Это фрагмент, не замена модуля. Обновите SDK и проверяйте действие и владельца в обработчиках. Путь к исходному коду: 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]