flawopen.com/Teardowns/cve-2026-104873-langgraph-sdk-auth-action-bypass
LangGraph CVE-2026-104873 : contournement d’autorisation
LangGraph SDK 0.1.45–0.4.3 ignorait les filtres d’action de trois décorateurs Python. Passez à 0.4.4.[1]
Imaginez l’accueil d’un immeuble chargé de recevoir uniquement les livraisons, alors que le système y envoie tous les visiteurs. S’il vérifie seulement l’entrée, une personne peut atteindre des pièces qu’un autre guichet aurait fermées. Le SDK ignorait actions= lors de l’enregistrement d’un gestionnaire de ressource, qui pouvait aussi recevoir lectures, modifications ou suppressions. Le risque dépend de l’absence de contrôle des permissions dans ce gestionnaire.[1]
Concepts Clés et Termes
Décorateur par ressource- Décorateur Python qui enregistre l’autorisation d’un type de ressource.[1], [2]
CWE-863- Autorisation incorrecte : le contrôle n’applique pas la permission prévue.[1]
Correspondances de référentiels
- Référence CWE pour la faiblesse décrite dans ce guide : CWE-863
- OWASP API Security Top 10 — API5:2023 Autorisation défaillante au niveau des fonctions
Analyse de Cause Racine
Dans les versions 0.1.45–0.4.3, les trois décorateurs concernés acceptaient actions=, mais enregistraient le gestionnaire sous le joker *. LangGraph pouvait le choisir avant le repli général. S’il n’autorisait que l’action prévue, il pouvait recevoir les autres sans les contrôles de repli. L’accès aux ressources d’autrui exigeait que l’application dépende de ces contrôles et que le gestionnaire ne vérifie pas lui-même action et propriété. La version 0.4.4 valide la liste et enregistre chaque action séparément ; sans actions=, le joker est conservé volontairement. L’avis attribue CWE-863 et CVSS 4.0 7.6 High. Cette faille dépend de la configuration, sans être un contournement sans authentification ni un défaut de toutes les installations LangGraph. Les décorateurs concernés sont : @auth.on.threads / @auth.on.assistants / @auth.on.crons. Limite importante du correctif : le changement du SDK limite les enregistrements aux actions déclarées, mais les chemins d’autorisation personnalisée non couverts restent autorisés si l’application ne configure pas de gestionnaire global de refus par défaut. Le changelog 0.4.4 indique que langgraph-api 0.10+ signale au démarrage les chemins non couverts ; considérez cet avertissement comme une détection, pas comme une mesure d’application.[1], [2], [5]
Déroulement de l'Attaque Étape par Étape
Code Source : Vulnérable vs Sécurisé
L’extrait vulnérable montre l’ancien comportement : _ResourceOn.__call__ utilisait * quelle que soit la valeur de actions=. Le correctif valide et enregistre chaque action séparément. C’est un extrait, pas le remplacement du module. Mettez à niveau et faites vérifier action et propriété par les gestionnaires. Emplacement dans le code source : libs/sdk-py/langgraph_sdk/auth/__init__.py, method _ResourceOn.__call__.[1], [2]
Extrait source: Enregistrement du gestionnaire selon l’action
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
Liste de Contrôle de Sécurité pour l'Ingénierie
- ✓Mettez le SDK 0.1.45–0.4.3 à niveau vers 0.4.4+.[1], [3]
- ✓Recherchez les trois décorateurs concernés avec
actions=.[1], [2] - ✓Vérifiez action et accès à l’identifiant exact de ressource.[1]
- ✓Testez actions déclarées et non déclarées, y compris les refus.[2]
- ✓Testez les accès entre utilisateurs aux threads, assistants et crons.[1]
- ✓Rejetez au démarrage les listes dupliquées, vides ou inconnues.[2]
- ✓Ne confondez pas connexion et autorisation explicite.
- ✓Configurez un gestionnaire global de refus par défaut pour chaque chemin d’autorisation personnalisée non couvert par les gestionnaires limités aux actions. langgraph-api 0.10+ signale les chemins non couverts, mais un avertissement au démarrage ne refuse pas les requêtes.[5], [1]