FlawOpen

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

● CVE-2026-104873 · CVSS v4 7.6[1] · Tinggi
Riset Keamanan · FlawOpen

LangGraph CVE-2026-104873: bypass otorisasi

LangGraph SDK 0.1.45–0.4.3 mengabaikan filter tindakan pada tiga dekorator Python. Perbarui ke 0.4.4.[1]

Vektor 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]
💡 Penjelasan Sederhana (ELI5)

Bayangkan resepsionis gedung yang hanya menerima paket, tetapi sistem mengarahkan semua pengunjung ke mejanya. Jika resepsionis hanya memeriksa bahwa seseorang sudah masuk, pengunjung dapat mencapai ruangan yang seharusnya dikunci meja lain. SDK mengabaikan actions= saat mendaftarkan handler resource, sehingga operasi baca, ubah, atau hapus juga dapat diterima. Risiko muncul jika handler tidak memeriksa izin.[1]

Konsep Kunci & Istilah

Dekorator otorisasi resource
Dekorator Python untuk mendaftarkan otorisasi operasi resource.[1], [2]
Filter tindakan
actions= memilih operasi handler.[1], [2]
Handler fallback
Handler umum saat tidak ada kecocokan khusus.[1], [2]
CWE-863
Otorisasi tidak benar: pemeriksaan tidak menerapkan izin yang dimaksud.[1]

Pemetaan kerangka kerja

Analisis Akar Masalah (Root Cause)

Versi 0.1.45–0.4.3 menerima actions=, tetapi mendaftarkan handler pada wildcard resource *. LangGraph dapat memilihnya sebelum fallback umum. Jika fungsi hanya mengizinkan tindakan yang dimaksud, tindakan lain dapat diterima tanpa pemeriksaan fallback. Akses ke resource orang lain memerlukan aplikasi yang bergantung pada pemeriksaan terlewat dan handler yang tidak memeriksa tindakan serta kepemilikan sendiri. Versi 0.4.4 memvalidasi daftar dan mendaftarkan tiap tindakan secara terpisah; tanpa actions=, wildcard tetap dipertahankan dengan sengaja. Advisory menetapkan CWE-863 dan CVSS 4.0 7.6 High. Ini kelemahan otorisasi bergantung konfigurasi, bukan bypass tanpa autentikasi atau masalah semua deployment LangGraph. Dekorator yang terdampak adalah: @auth.on.threads / @auth.on.assistants / @auth.on.crons. Batas penting perbaikan: perubahan SDK membatasi pendaftaran pada tindakan yang dideklarasikan, tetapi jalur custom-auth yang tidak tercakup tetap diizinkan jika aplikasi tidak mengatur handler global dengan default-deny. Changelog 0.4.4 menyebut langgraph-api 0.10+ memperingatkan jalur yang tidak tercakup saat startup; perlakukan peringatan itu sebagai deteksi, bukan penegakan akses.[1], [2], [5]

Alur Serangan Langkah demi Langkah

Langkah 1

Konfigurasi

SDK 0.1.45–0.4.3 dengan dekorator terdampak dan actions=.[1], [4]

Langkah 2

Pendaftaran

Handler mungkin hanya ditujukan untuk satu tindakan.[1]

Langkah 3

Wildcard

SDK mengaitkannya dengan semua tindakan resource.[1], [2]

Langkah 4

Pemeriksaan

Handler mungkin tidak memeriksa tindakan dan kepemilikan.[1]

Langkah 5

Perbaikan

Perbarui ke 0.4.4; uji tindakan, fallback, dan kepemilikan.[1], [2], [3], [5]

Kode Sumber: Rentan vs Aman

Cuplikan rentan menunjukkan perilaku lama: _ResourceOn.__call__ memakai * tanpa memedulikan actions=. Perbaikan memvalidasi daftar dan mendaftarkan tindakan satu per satu. Ini cuplikan, bukan pengganti modul lengkap. Perbarui SDK dan periksa tindakan serta kepemilikan di handler. Lokasi kode sumber: libs/sdk-py/langgraph_sdk/auth/__init__.py, method _ResourceOn.__call__.[1], [2]

Cuplikan sumber: Pendaftaran handler berdasarkan tindakan

IMPLEMENTASI RENTAN
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
PERBAIKAN AMAN & KUAT
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

Daftar Periksa Penguatan Sistem Rekayasa

Sumber