flawopen.com/Teardowns/cve-2026-104873-langgraph-sdk-auth-action-bypass
LangGraph CVE-2026-104873:認可バイパス
LangGraph SDK 0.1.45~0.4.3は3つのPythonデコレーターで操作フィルターを無視しました。0.4.4へ更新してください。[1]
建物の受付は荷物だけを担当するはずなのに、案内システムが全来訪者を受付へ送る状況を想像してください。入館だけを確認すると、別の窓口なら施錠した部屋にも入れてしまいます。SDKはリソースハンドラー登録時に actions= を無視し、読み取り、更新、削除も届く可能性がありました。ハンドラー自身が権限を確認しない場合に限りリスクがあります。[1]
主要な概念と専門用語
CWE-863- 不適切な認可:確認が意図した権限を適用しません。[1]
フレームワーク対応
- このガイドで説明する弱点の CWE 参照: CWE-863
- OWASP API Security Top 10 — API5:2023 機能レベルの認可不備
根本原因の分析 (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]
ステップ・バイ・ステップの攻撃フロー
ソースコード比較:脆弱 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
エンジニアリング&システム堅牢化チェックリスト
- ✓SDK 0.1.45~0.4.3を0.4.4以降へ更新します。[1], [3]
- ✓
actions=を使う対象3デコレーターを検索します。[1], [2] - ✓操作と正確なリソースIDへのアクセス権を確認します。[1]
- ✓宣言済み・未宣言の操作と拒否経路をテストします。[2]
- ✓thread、assistant、cronのユーザー間アクセスをテストします。[1]
- ✓起動時に重複、空、不明なリストを拒否します。[2]
- ✓ログインを明示的な認可と混同しません。
- ✓操作ごとのハンドラーでカバーされないすべてのカスタム認可パスに、グローバルなデフォルト拒否ハンドラーを設定します。langgraph-api 0.10以降は未カバーのパスを警告しますが、起動時の警告だけではリクエストを拒否しません。[5], [1]