FlawOpen

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

● CVE-2026-104873 · CVSS v4 7.6[1] · Hoch
Sicherheitsforschung · FlawOpen

LangGraph CVE-2026-104873: Autorisierungsumgehung

LangGraph SDK 0.1.45–0.4.3 ignorierte Aktionsfilter in drei Python-Dekoratoren. Aktualisieren Sie auf 0.4.4.[1]

CVSS-VektorCVSS: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]
💡 Einfache Erklärung (ELI5)

Stellen Sie sich einen Empfang vor, der nur Lieferungen annehmen soll, während das Leitsystem alle Besucher dorthin schickt. Prüft er nur den Zutritt, kann jemand Räume erreichen, die ein anderer Schalter verschlossen hätte. Das SDK ignorierte actions= beim Registrieren eines Ressourcen-Handlers; dadurch erhielt er auch Lese-, Änderungs- oder Löschvorgänge. Ein Risiko entsteht nur, wenn der Handler nötige Berechtigungen nicht selbst prüft.[1]

Kernkonzepte & Begriffe

Ressourcendekorator
Python-Dekorator zur Autorisierung eines Ressourcentyps.[1], [2]
Aktionsfilter
actions= wählt die Aktionen des Handlers.[1], [2]
Fallback
Allgemeiner Handler ohne spezifische Übereinstimmung.[1], [2]
CWE-863
Fehlerhafte Autorisierung: Die Prüfung setzt die Berechtigung nicht durch.[1]

Framework-Zuordnungen

Ursachenanalyse

Versionen 0.1.45–0.4.3 akzeptierten actions=, registrierten den Handler aber unter dem Ressourcen-Wildcard *. LangGraph konnte ihn vor einem allgemeinen Fallback auswählen. Prüfte die Funktion nur die vorgesehene Aktion, konnte sie andere ohne Fallback-Prüfung erhalten. Zugriff auf fremde Ressourcen setzte voraus, dass die Anwendung von ausgelassenen Prüfungen abhing und der Handler Aktion sowie Eigentümer nicht selbst kontrollierte. 0.4.4 validiert die Liste und registriert jede Aktion einzeln; ohne actions= bleibt der Wildcard absichtlich bestehen. Der Hinweis nennt CWE-863 und CVSS 4.0 7.6 High. Der Fehler hängt von der Konfiguration ab, ist weder unauthentifiziert noch in jeder LangGraph-Installation vorhanden. Betroffen sind die Dekoratoren: @auth.on.threads / @auth.on.assistants / @auth.on.crons. Wichtige Grenze des Fixes: Die SDK-Änderung beschränkt Registrierungen auf deklarierte Aktionen. Nicht abgedeckte Pfade der benutzerdefinierten Autorisierung bleiben jedoch erlaubt, sofern die Anwendung keinen globalen Deny-by-default-Handler einrichtet. Laut Changelog 0.4.4 warnt langgraph-api 0.10+ beim Start vor nicht abgedeckten Pfaden; diese Warnung dient der Erkennung, nicht der Durchsetzung.[1], [2], [5]

Schritt-für-Schritt Angriffsablauf

Schritt 1

Konfiguration

SDK 0.1.45–0.4.3 mit betroffenem Dekorator und actions=.[1], [4]

Schritt 2

Registrierung

Ein Handler kann nur eine Aktion bearbeiten sollen.[1]

Schritt 3

Wildcard

Das SDK ordnet ihn allen Ressourcenaktionen zu.[1], [2]

Schritt 4

Prüfung

Der Handler kann Aktion und Eigentümer ungeprüft lassen.[1]

Schritt 5

Abhilfe

Auf 0.4.4 aktualisieren und Aktion, Fallback, Eigentümer testen.[1], [2], [3], [5]

Quellcode: Verwundbar vs. Sicher

Der verwundbare Ausschnitt zeigt das frühere Verhalten: _ResourceOn.__call__ verwendete unabhängig von actions= den Schlüssel *. Die Korrektur validiert und registriert jede Aktion einzeln. Dies ist ein Ausschnitt, kein Ersatz des Moduls. Aktualisieren Sie und lassen Sie Handler Aktion und Eigentümer prüfen. Quellcodepfad: libs/sdk-py/langgraph_sdk/auth/__init__.py, method _ResourceOn.__call__.[1], [2]

Quelltextauszug: Handler-Registrierung nach Aktion

VERWUNDBARE IMPLEMENTIERUNG
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
GEHÄRTETER SICHERHEITS-PATCH
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

Checkliste für Engineering & Systemsicherheit

Quellen