FlawOpen

flawopen.com/Teardowns/cve-2026-71433-langgraph-store-namespace-boundary

● CVE-2026-71433 · CVSS 5.3 · Moyenne
Recherche · FlawOpen

LangGraph CVE-2026-71433 : fuite de namespace

Les lectures ciblées des stores Postgres et SQLite de LangGraph pouvaient franchir les limites de namespace et exposer les données d’un tenant voisin. Mettez les deux paquets à niveau 3.1.1.

Vecteur CVSSCVSS:3.1/AV:N/AC:H/PR:L/UI:N/S:U/C:H/I:N/A:N
💡 Explication en Langage Simple (ELI5)

Imaginez qu’un bibliothécaire cherche des dossiers dont le nom commence par « équipe ». Si le catalogue compare seulement le début du texte, il peut renvoyer « équipe2 » avec « équipe ». LangGraph concaténait les étiquettes du namespace avec des points, puis utilisait un motif de préfixe sans exiger ce point comme frontière. Le correctif exige le namespace exact ou un véritable segment suivant, afin que le dossier d’un tenant ne soit plus confondu avec celui d’un autre.

Concepts Clés et Termes

Namespace
Un tuple d’étiquettes utilisé par le Store LangGraph pour regrouper des éléments. Une application peut s’en servir comme frontière entre utilisateurs ou tenants.
Filtrage par préfixe
Une demande de lecture d’un namespace et de ses descendants. La correspondance correcte respecte la frontière du segment : foo inclut foo.child, mais pas le namespace voisin foobar.
Motif SQL `LIKE`
Un motif textuel où % correspond à une suite quelconque et _ à un caractère. Il faut échapper ces caractères lorsque les étiquettes du namespace doivent rester littérales.
CWE-863
Autorisation incorrecte : le Store a renvoyé des enregistrements hors du namespace que l’appelant était autorisé à lire.

Correspondances de référentiels

  • Référence CWE pour la faiblesse décrite dans ce guide : CWE-863

Analyse de Cause Racine

Avant la version 3.1.1, les stores de checkpoint Postgres et SQLite aplatissaient les tuples de namespace avec des points et limitaient les recherches avec LIKE '<namespace>%'. LIKE ne comprend pas le séparateur du namespace : une recherche sur foo pouvait donc aussi correspondre au namespace voisin foobar. Les étiquettes valides % et _ pouvaient également servir de jokers. list_namespaces comportait des erreurs proches pour les préfixes et suffixes. Une lecture ciblée ordinaire suffisait ; aucune entrée malformée n’était nécessaire. Le problème concerne uniquement les chemins de lecture search et de liste des namespaces des stores Postgres/SQLite lorsque l’application s’appuie sur les namespaces pour séparer les tenants. get, put et delete utilisent l’égalité exacte et n’étaient pas concernés ; InMemoryStore compare les éléments du namespace et n’était pas concerné non plus. Le correctif exige une égalité exacte ou un point avant les descendants, échappe les métacaractères de LIKE et utilise des motifs par segment pour la liste des namespaces. Sous SQLite, la recherche évite aussi le comportement de LIKE qui ignore la casse ASCII. L’avis ne signale aucune exploitation connue. Les paquets concernés sont corrigés en 3.1.1.

Matrice d’exposition au CVE-2026-71433 selon le déploiement

Déploiement et forme des namespacesComportement documentéAction de triage
PostgresStore / AsyncPostgresStore ou SqliteStore / AsyncSqliteStore avant la version 3.1.1 ; l’isolation des tenants repose sur les namespaces.Une recherche search ou list_namespaces limitée peut atteindre un namespace frère si les libellés partagent un préfixe textuel (foo / foobar, 1 / 12) ou contiennent % / _. Sous SQLite, la recherche assimilait aussi les différences de casse ASCII.Mettez à niveau le paquet checkpoint concerné vers la version 3.1.1 ou ultérieure. Testez les préfixes frères, les caractères génériques, les suffixes et les différences de casse sur le moteur de production.
Store SQLite antérieur à 3.1.1 avec des libellés de namespace qui ne diffèrent que par la casse ASCII.La recherche vulnérable des descendants utilisait LIKE, insensible à la casse, alors que les opérations exactes distinguaient la casse.Mettez à niveau et ajoutez un test de non-régression sur la casse si les libellés peuvent varier ainsi.
Libellés UUID de longueur fixe sans % ni _.L’avis indique que ce format n’est pas concerné : des libellés de longueur fixe ne peuvent pas être préfixes les uns des autres.Vérifiez que chaque segment respecte cette invariance. Si les libellés peuvent varier ou contenir des caractères génériques, considérez le déploiement comme concerné.
InMemoryStore.Non concerné : les éléments du namespace sont comparés individuellement.Aucune mise à niveau requise pour ce CVE ; conservez les contrôles de propriété des tenants à la frontière applicative.
Déploiement hébergé LangSmith.La valeur par défaut LANGGRAPH_STORE_BACKEND=python utilise AsyncPostgresStore et est concernée avec un paquet vulnérable. Le backend distinct grpc a reçu une correction équivalente.Vérifiez le backend configuré et la version déployée ; confirmez la version ou le déploiement du correctif au lieu de déduire l’exposition du seul nom du produit.

Déroulement de l'Attaque Étape par Étape

Étape 1

Vérifiez le backend et les étiquettes

Le déploiement doit utiliser langgraph-checkpoint-postgres ou langgraph-checkpoint-sqlite avant 3.1.1 et s’appuyer sur le namespace pour séparer utilisateurs ou tenants. Des étiquettes partageant un préfixe ou contenant % et _ rendent cette frontière pertinente.

Étape 2

Une lecture ciblée ordinaire est lancée

Une requête authentifiée de l’application recherche un namespace tel que foo. L’appelant n’a pas besoin d’envoyer de payload spécial : le namespace ordinaire est l’entrée du comparateur défectueux.

Étape 3

Le backend aplatit et compare un motif

Le Store joint les étiquettes avec des points et utilise un préfixe LIKE terminé par %. Comme aucun séparateur n’est exigé, foo peut correspondre à foobar ; les caractères de motif des étiquettes peuvent aussi élargir la correspondance.

Étape 4

Des éléments d’un namespace voisin peuvent être renvoyés

Lorsque l’application utilise les namespaces comme frontières entre tenants, la recherche ou la liste peut renvoyer les éléments stockés d’un autre utilisateur. Il s’agit d’un problème de confidentialité en lecture, pas d’une injection SQL ni d’un accès en écriture.

Étape 5

Mettez à niveau et retestez l’isolation

Mettez les deux paquets concernés à niveau vers 3.1.1 ou une version ultérieure. Testez les préfixes voisins, les étiquettes littérales % et _, les suffixes et la sensibilité à la casse de SQLite avec des tenants de test séparés.

Code Source : Vulnérable vs Sécurisé

Le prédicat vulnérable considère toute chaîne commençant par le namespace aplati comme un descendant. Le correctif ajoute une comparaison exacte et exige un point littéral avant les segments descendants ; il échappe aussi la barre oblique inverse, % et _ dans le motif LIKE. Le commit upstream remplace également les motifs de préfixe/suffixe de list_namespaces par une correspondance par segments et ajuste la recherche SQLite. Il s’agit d’un extrait source, pas d’un module de remplacement.

Extrait source: libs/checkpoint-postgres/langgraph/store/postgres/base.py

IMPLÉMENTATION VULNÉRABLE
def _prepare_batch_search_queries(op):
    ns_condition = "TRUE"
    ns_param = None
    if op.namespace_prefix:
        ns_condition = "store.prefix LIKE %s"
        ns_param = (f"{_namespace_to_text(op.namespace_prefix)}%",)
    return ns_condition, ns_param
PATCH SÉCURISÉ ET ROBUSTE
def _escape_like_literal(text: str) -> str:
    return text.replace("\\", "\\\\").replace("%", "\\%").replace("_", "\\_")

def _namespace_prefix_condition(namespace_prefix: tuple[str, ...]) -> tuple[str, tuple]:
    path = _namespace_to_text(namespace_prefix)
    condition = r"(store.prefix = %s OR store.prefix LIKE %s ESCAPE '\\')"
    return condition, (path, f"{_escape_like_literal(path)}.%")

if op.namespace_prefix:
    ns_condition, ns_param = _namespace_prefix_condition(op.namespace_prefix)

Liste de Contrôle de Sécurité pour l'Ingénierie

Sources