FlawOpen

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

● CVE-2026-104873 · CVSS v4 7.6[1] · 高
セキュリティ研究 · FlawOpen

LangGraph CVE-2026-104873:認可バイパス

LangGraph SDK 0.1.45~0.4.3は3つのPythonデコレーターで操作フィルターを無視しました。0.4.4へ更新してください。[1]

CVSS ベクトルCVSS: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]
💡 わかりやすい解説 (ELI5)

建物の受付は荷物だけを担当するはずなのに、案内システムが全来訪者を受付へ送る状況を想像してください。入館だけを確認すると、別の窓口なら施錠した部屋にも入れてしまいます。SDKはリソースハンドラー登録時に actions= を無視し、読み取り、更新、削除も届く可能性がありました。ハンドラー自身が権限を確認しない場合に限りリスクがあります。[1]

主要な概念と専門用語

リソース認可デコレーター
リソース操作の認可ハンドラーを登録するPythonデコレーター。[1], [2]
操作フィルター
actions= で処理対象の操作を選びます。[1], [2]
フォールバック
個別の一致がない場合に使う汎用処理です。[1], [2]
CWE-863
不適切な認可:確認が意図した権限を適用しません。[1]

フレームワーク対応

根本原因の分析 (Root Cause)

0.1.45~0.4.3は actions= を受け取りながら、ハンドラーをリソースの * に登録していました。LangGraphは汎用フォールバックより先に選ぶことがありました。関数が想定操作だけを認可すると、別操作もフォールバック確認なしで届く可能性があります。他者のリソースへアクセスできるのは、アプリが省略された確認に依存し、ハンドラーも操作と所有者を検証しない場合です。0.4.4はリストを検証し、操作ごとに登録します。actions= 省略時はワイルドカードを意図的に維持します。アドバイザリーはCWE-863、CVSS 4.0 7.6 Highです。設定に依存する認可不備であり、未認証バイパスでも全環境の問題でもありません。 対象となるデコレーターは次のとおりです:@auth.on.threads / @auth.on.assistants / @auth.on.crons. 修正の重要な限界:SDKの変更は宣言された操作に登録を限定しますが、アプリケーションがグローバルなデフォルト拒否ハンドラーを設定しない場合、未対応のカスタム認可パスは引き続き許可されます。0.4.4の変更履歴によると、langgraph-api 0.10以降は未カバーのパスを起動時に警告します。この警告は検出用であり、アクセスを拒否する仕組みではありません。[1], [2], [5]

ステップ・バイ・ステップの攻撃フロー

ステップ 1

設定

SDK 0.1.45~0.4.3で対象デコレーターに actions= を指定。[1], [4]

ステップ 2

登録

ハンドラーは特定操作用の想定です。[1]

ステップ 3

ワイルドカード

SDKが全リソース操作に関連付けます。[1], [2]

ステップ 4

確認

操作と所有者が検証されない可能性があります。[1]

ステップ 5

修正

0.4.4へ更新し操作、フォールバック、所有者をテスト。[1], [2], [3], [5]

ソースコード比較:脆弱 vs 堅牢化

脆弱な抜粋は旧動作を示します。_ResourceOn.__call__ は actions= に関係なく * を使っていました。修正はリストを検証して個別登録します。全モジュールではなく該当箇所の抜粋です。更新し、ハンドラーでも操作と所有者を確認してください。 ソースコードの場所: libs/sdk-py/langgraph_sdk/auth/__init__.py, method _ResourceOn.__call__.[1], [2]

ソース抜粋: 操作別のハンドラー登録

脆弱な実装
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

エンジニアリング&システム堅牢化チェックリスト

参考資料