FlawOpen

flawopen.com/Teardowns/cve-2026-104873-langgraph-sdk-auth-action-bypass

● CVE-2026-104873 · CVSS v4 7.6[1] · Alta
Pesquisa · FlawOpen

LangGraph CVE-2026-104873: falha de autorização

LangGraph SDK 0.1.45–0.4.3 ignorava filtros de ação em três decorators Python. Atualize para 0.4.4.[1]

Vetor CVSSCVSS:4.0/AV:N/AC:L/AT:P/PR:L/UI:N/VC:H/VI:H/VA:N/SC:N/SI:N/SA:N[1]
💡 Explicação em Linguagem Simples (ELI5)

Imagine uma recepção instruída a receber só entregas, mas o sistema envia todos os visitantes para lá. Se ela só verifica quem entrou no prédio, alguém pode chegar a salas que outro balcão manteria trancadas. O SDK afetado ignorava actions= ao registrar um handler de recurso, que podia receber leituras, alterações ou exclusões. O risco depende de o handler não verificar as permissões necessárias.[1]

Conceitos Centrais e Termos

Decorator por recurso
Decorator Python para registrar autorização de um tipo de recurso.[1], [2]
Filtro de ação
actions= seleciona operações permitidas ao handler.[1], [2]
Fallback
Handler geral usado quando nenhum específico corresponde.[1], [2]
CWE-863
Autorização incorreta: a verificação não aplica a permissão esperada.[1]

Mapeamentos de frameworks

Análise de Causa Raiz

Nas versões 0.1.45–0.4.3, os três decorators afetados aceitavam actions=, mas registravam o handler no wildcard * do recurso. LangGraph podia escolhê-lo antes do fallback geral. Se o handler só autorizasse a ação prevista, podia receber outras ações sem as verificações do fallback. Acesso a recursos alheios exigia que a aplicação dependesse dessas verificações e que o handler não validasse ação e propriedade. A versão 0.4.4 valida a lista e registra cada ação separadamente; sem actions=, mantém o wildcard intencionalmente. O advisory atribui CWE-863 e CVSS 4.0 7.6 High. É uma falha condicional à configuração, não um bypass sem autenticação nem um defeito de toda implantação LangGraph. Os decorators afetados são: @auth.on.threads / @auth.on.assistants / @auth.on.crons. Limite importante da correção: a mudança do SDK restringe os registros às ações declaradas, mas caminhos de autorização personalizada sem correspondência continuam permitidos se a aplicação não configurar um handler global de negação por padrão. O changelog do 0.4.4 informa que o langgraph-api 0.10+ alerta na inicialização sobre caminhos sem cobertura; trate o alerta como detecção, não como bloqueio.[1], [2], [5]

Fluxo de Ataque Passo a Paso

Passo 1

Configuração

SDK 0.1.45–0.4.3 e decorator afetado com actions=.[1], [4]

Passo 2

Registro

Um handler pode ser destinado a uma única ação.[1]

Passo 3

Wildcard

O SDK associa o handler a todas as ações do recurso.[1], [2]

Passo 4

Checagem

A operação chega ao handler que pode não validar ação e propriedade.[1]

Passo 5

Correção

Atualize para 0.4.4 e teste ação, fallback e propriedade.[1], [2], [3], [5]

Código-Fonte: Vulnerável vs. Seguro

O trecho vulnerável mostra o comportamento anterior: _ResourceOn.__call__ usava * independentemente de actions=. A correção registra cada ação separadamente após validação. É um recorte, não uma substituição do módulo. Atualize e faça handlers verificarem ação e propriedade. Local do código-fonte: libs/sdk-py/langgraph_sdk/auth/__init__.py, method _ResourceOn.__call__.[1], [2]

Trecho de origem: Registro do handler conforme a ação

IMPLEMENTAÇÃO VULNERÁVEL
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
PATCH SEGURO E ROBUSTO
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 Verificação de Segurança para Engenharia

Fontes