
兄弟ルートよりも弱い認可を持つAPIルートを検出します。ソースからCVE-2026-45316を復元しました。否定的な結果も含みます。
兄弟ルートよりも弱い認可制御を持つAPIルートを検出します。
アドバイザリの知識なしに、ソースコードのみからOpen WebUIのCVE-2026-45316を検出しました:
[MISMATCH] POST /notes/{id}/pin (notes.py:pin_note_by_id)
3/3 comparable write operations on note require has_access(write);
this route requires only has_access(read)
POST /notes/{id}/update has_access(write)
POST /notes/{id}/access/update has_access(write)
DELETE /notes/{id}/delete has_access(write)
5つの本番コードベースの2,568ルート全体で4件の検出結果を出しました。2件は本物でした。このREADMEは、その両方の数字を説明します。
認可バグの大きなクラスは、壊れたチェックではありません。それは、兄弟ルートがすべて正しくチェックしているのに、あるルートで欠落しているか弱められているチェックなのです。
Portainerは、4つの兄弟テンプレートエンドポイントに認可を設定しましたが、5つ目には設定しませんでした。Signal KはHTTPログインをレート制限しましたが、WebSocketログインはレート制限しませんでした。Open WebUIのノート固定ルートは、他のすべてのノート変更操作が書き込み権限をチェックしていたのに、読み取り権限をチェックするだけでノートを変更していました。
常に同じ形です:
✓ ✓ ✓ ✓ ✗
リポジトリはすでにそのルールを含んでいます。1つのルートがそれを破っています。そこで、コードからルールを再構築し、例外を報告します。ポリシーファイルも、設定も、アノテーションもありません。証明の集合は、兄弟ルートそのものです。
python3 enforcement_coverage.py /path/to/repo
python3 enforcement_coverage.py /path/to/repo --density
python3 enforcement_coverage.py /path/to/repo --json
Python 3.10以上、依存関係なし。FastAPIのみ。
判定結果はMISSING、MISMATCH、PRESERVED、UNKNOWNです。UNKNOWNは棄権し、決して報告されません。
抽出。 シグネチャのDepends()、デコレータのdependencies=[]、ルーターレベルの依存関係、関数本体、ラッパー関数、権限クラスのリストから制御を解決します。
関数本体からの抽出は不可欠です。Open WebUIでは、608個のハンドラのうち216個が決定的なチェックを関数内に持っています:
if user.role != 'admin' and not await AccessGrants.has_access(
user_id=user.id, resource_type='note',
resource_id=note.id, permission='write', db=db,
):
raise HTTPException(status_code=403)
操作クラスは、HTTPメソッドではなく、ハンドラがリソースに対して行うことから決まります。POST /notes/{id}/chatは、ノートを読み取るPOSTです。
語彙の分類。 1つの制御ファミリ内の2つの値が、必ずしも強度の尺度を形成するとは限りません:
has_access {read, write} LEVEL
ensure_flow_permission {create,delete,execute,read,write} ACTION
has_permission {features.notes, workspace.tools} SCOPE
強度の比較をサポートするのはLEVEL語彙だけです。FlowAction.CREATEと優勢なWRITEを比較すると、修正される前はLangflowで6件の偽陽性が発生しました。
方向フィルタ。 より制限の緩い制御への逸脱のみが報告されます。先例より厳しい場合は脆弱性ではありません。
2件の真陽性。
Open WebUIのPOST /notes/{id}/pin — CVE-2026-45316。
もう1つの検出結果は、Netflix Dispatchの権限の非対称性です。POST /{incident_id}/resourcesはIncidentViewPermissionを使用していますが、6つの兄弟書き込み操作はIncidentEditPermissionを使用しています。IncidentViewPermissionは制限なしのインシデントであればTrueを返します。IncidentEditPermissionは管理者、コマンダー、またはレポーターを要求します。このハンドラは、追加のチェックなしにチケットとグループの作成をキューに投入します。
報告する場所がないため、報告されていません:リポジトリは2025年9月3日にNetflixによってアーカイブされ読み取り専用であり、SECURITY.mdはなく、アーカイブされたリポジトリでは非公開の脆弱性報告が無効になっており、DispatchはNetflixのバウンティプログラムで明示的に適用範囲外としてリストされています。ここで公開することが、残された唯一の開示チャネルです。重大度は低く — 認証済みの組織メンバーが必要で、制限なしのインシデントのみに影響します — プロジェクトはもはやメンテナンスされていません。
2件の偽陽性。 POST /tools/{id}/valves/user/updateはユーザー自身のバルブ設定を書き込むもので、ツールに対しては読み取り権限だけで正当です — 変更対象は認可の主体とは異なるエンティティだからです。もう1件は、兄弟ガードが不要なLangflowのナレッジベースルートです。
ここが有益な部分です。
Danswerは95.9%の制御カバレッジを持ち、80.7%のUNKNOWNで検出結果はゼロでした。その語彙はrequire_permission('basic_access')、('manage_connectors') — つまり能力の名前空間であり、強度の尺度ではありません。manage_connectorsがread_connectorsより弱いとは言えません。
検出結果を予測するのは、has_access(resource, read|write)のような順序付けられた強度引数を伴うリソーススコープの権限呼び出しです。5つのうち1つのコードベースがそれを持っていました。
5つのリポジトリにわたって5つのイディオムがありました。分析を実行できるようにする前に、それぞれに抽出器の作業が必要でした。
どのリポジトリでも、どの構成でもです。ほとんどのルートは、一貫した制御を持つ3つ以上の兄弟ファミリに属していません。
これらはいずれも事前に想定されていませんでした。
Depends()ではなく関数本体にある欠陥13が興味深いものです。Dispatchのタグ推奨ルートには、兄弟ルートが持っているCaseViewPermissionがありません。しかし、その権限は制限なしのケースであればTrueを返し、サービスはすでにvisibility == restrictedをインラインでチェックしています。パスは同等です。それを検出するには、構造ではなく意味的等価性の分析が必要です。
このメカニズムは機能します。公開済みのCVEと1件の未報告の認可ギャップを、監査可能な証明集合とともに、脆弱なコミットがマージされる前に利用可能だった情報だけを使って、ソースから復元しました。
成果はおよそ1,000ルートあたり1件の検出結果であり、5つの実質的なコードベースのうち1つが持っていたアーキテクチャが必要です。
リソーススコープの権限語彙を持つコードベースの監査ツールとしては有用です。この証拠に基づけば、汎用スキャナではありません。
暗黙の前提を推論することによるアクセス制御脆弱性の静的検出は、USENIX Security 2011にまで遡ります。ACMinerはAndroidミドルウェアの認可チェックをマイニングしました。Semgrepは、欠落した認可を対象としたAI駆動の検出を提供しており、ある顧客評価では61%の適合率を報告しています。OWASPはAuthorization Regression Testingチートシートを公開しており、その推奨アプローチはActor × Resource × Actionマトリクスを手作業で維持することです。
これは狭く、決定的なアプローチです。兄弟ルートが証明するものだけを復元し、それ以外は棄権します。
MITライセンスです。
| リポジトリ | ルート数 | 制御率 | 検出結果 |
|---|
| LiteLLM | 808 | 87.1% | 0 |
| Danswer / Onyx | 653 | 95.9% | 0 |
| Open WebUI | 529 | 97.2% | 2 |
| Netflix Dispatch | 291 | 36.1% | 1 |
| Langflow | 287 | 47.4% | 1 |
| リポジトリ | イディオム |
|---|
| Open WebUI | AccessGrants.has_access(resource_type=, permission=) |
| Langflow | ensure_<resource>_permission(user, Action.X) |
| LiteLLM | user_api_key_dict.user_roleのロール比較 |
| Netflix Dispatch | Depends(PermissionsDependency([CaseEditPermission])) |
| Danswer | require_permission('basic_access') |