authz-security について
authz-securityは、ルート、コントローラー、リゾルバー、データアクセスにおける認可チェックの欠落を見つけるモデル支援型コードレビュースキルです。リソースの所有関係と本人性を整理し、深刻度別に問題とフレームワークに即した修正案を報告します。アプリの実行や認証情報は不要です。
主な機能
- 01リソース所有関係とテナント構造の把握
- 02ルートとオブジェクトアクセスのレビュー
- 03オブジェクト単位・機能単位の認可漏れ検出
- 04一括代入とクライアント指定の本人情報の確認
- 05エンドポイント間の認可適用状況の比較
- 06深刻度とコード修正案の報告
活用シーン
- 01APIハンドラーのIDORやBOLAの確認
- 02管理エンドポイントのロール検証の確認
- 03一覧クエリのテナント範囲制限の評価
- 04コード差分における認可変更のレビュー
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ superagent-ai/ skills --skill authz-securityインストール後、Claude Code で/authz-securityを入力して読み込まれたか確認します。
同梱ファイル
SKILL.md と一緒にインストールされる参考資料やスクリプト。
references/checklist.mdframeworks.mdpatterns.md詳細
認可セキュリティスキャナー
このスキルは、モデルを認可レビュー担当者にします。ルート、コントローラー、リゾルバー、データモデルを読み、検出パスをたどり、アクセス制御チェックの欠落をそれぞれ重大度と、フレームワークに即した具体的な修正方法とともに報告します。インストールするツールも、実行するアプリも、認証情報も不要です。コードを読むモデルが分析します。
アクセス制御の不備は、OWASP Top 10 の Web リスク第 1 位(A01)であり、API リスクでも上位 2 つ(API1 BOLA、API5 BFLA)に入っています。自動ツールが見落とすからこそ、常に上位に挙げられています。grep で探せる危険な関数呼び出しはありません。バグはチェックの欠落であり、確認するにはアプリケーションの所有権モデルを理解する必要があります。これは、コードを読むモデルにはできますが、シグネチャスキャナーにはできません。
考え方
認証が答えるのは、あなたは誰ですか? です。認可が答えるのは、この特定のオブジェクトに対して、これを実行することが許可されていますか? です。このスキルが扱うのは、後者の質問だけです。ハンドラーが完全に認証されていても、重大な脆弱性を抱えている可能性があります。
すべての特権操作は、次の 2 つの軸で悪用される可能性があります。
- 水平 — 同じ権限レベルの別のユーザーのオブジェクトを操作します。オブジェクト識別子(
/orders/1042→/orders/1043)を変更すると、他人のデータを読み取れてしまいます。これは BOLA / IDOR(OWASP API1)です。 - 垂直 — 自分の権限レベルを超える機能にアクセスします。一般ユーザーが管理者用エンドポイントにアクセスするケースです。これは BFLA(OWASP API5)です。
問題はほぼ常に、誤ったコードではなく、コードの欠落です。オブジェクト ID では絞り込むものの所有者では絞り込まないクエリ、「ログイン済み」では保護しているものの「管理者であること」では保護していない管理者用ルート、モデルに直接バインドされるリクエストボディなどです。そのため、探すのはパターンではなく欠落です。
チェックの有無を判断できない場合は、チェックがないものと仮定し、確認事項として報告してください。 誤検知のコストはレビューコメント 1 件です。見逃しのコストは、すべてのユーザーのデータです。この非対称性を、以下のすべての判断の指針にしてください。
スキャン手順
以下のパスを順番に進めます。パス 0 では後続のパスに必要なコンテキストを整理するため、ここを飛ばさないでください。
パス 0:所有権とテナントモデルを把握する
ここでの「所有」とは何を意味するのかを知らなければ、チェックの欠落は判断できません。エンドポイントを 1 つでも読む前に、データ層(ORM モデル、スキーマ、マイグレーション)を読み、次の点を確認します。
- 各リソースの所有者は誰ですか? 所有者やスコープを示すカラムを探します。例:
user_id、account_id、org_id、tenant_id、team_id、workspace_id。このようなカラムを持つリソースは、スコープを絞り込まずに返したり変更したりしてはいけません。 - プリンシパルは何ですか? 認証済みの ID はどこから取得されますか?セッションの
request.user、認証ミドルウェアが設定するreq.user、検証済み JWT のsubクレームなどです。これが信頼できる唯一の ID です。記録してください。 - テナント戦略は何ですか? 行レベル(
tenant_idカラムを持つ共有テーブル)は、マルチテナント環境で BOLA がほとんど発生する箇所です。分離はすべてのクエリでフィルターを忘れずに適用することに依存するためです。テナントごとにスキーマまたはデータベースを分ける方式では境界がより低い層に置かれ、誤りが起きにくくなります。
このマップを(形式にこだわらず)書き留めます。後続のすべての指摘は、「プリンシパル P が、P にアクセス権があることを確認せずにオブジェクト O を操作した」という内容です。判断するには O の所有者を把握しておく必要があります。
パス 1:ハンドラーと、ハンドラーが操作するオブジェクトを列挙する
すべてのエントリーポイントを列挙します。REST ルート、コントローラーのアクション、GraphQL リゾルバー、RPC/gRPC メソッド、サーバーアクションなどです。それぞれについて、次の項目を記録します。
- HTTP メソッドとパス(またはリゾルバー名)。
- 読み取りまたは変更の対象となるオブジェクト。
- オブジェクト識別子の取得元:パスパラメーター、クエリ文字列、リクエストボディ、セッションまたはトークン。
クライアントから渡される識別子を使ってオブジェクトを読み込む、または変更するすべてのハンドラーに印を付けます。これらがパス 2 で確認する BOLA 候補です。セッションから取得したプリンシパル自身の ID だけを使うハンドラー(GET /me、current_user.orders など)はリスクが低いため、優先度を下げられます。
Pass 2: オブジェクトレベルの認可(BOLA / IDOR) — OWASP API1
Pass 1 で該当したすべてのハンドラーについて、次の点を確認します。主体がこの特定のオブジェクトを所有している、またはアクセスを許可されていることを確認する処理はありますか? 適切な確認方法は、次の 3 つのいずれかです。
- 所有者でスコープを絞ったクエリ — 検索自体に所有者を含め、所有していない ID は見つからないようにします。
invoice = Invoice.objects.get(id=id, org=request.user.org) # 404 on someone else's - 読み込み後の明示的な確認 — 読み込んだ後、主体と照合し、一致しなければ拒否します。
invoice = Invoice.objects.get(id=id) if invoice.org_id != request.user.org_id: raise PermissionDenied - 一元化されたポリシー/権限 — ルールを定義するガードです。
authorize! :read, invoice(Pundit/CanCanCan)、@PreAuthorize("hasPermission(#id,...)")、OPA/Cedar/OpenFGA による判定、ポリシーゲートなどが該当します。
オブジェクトをクライアントが指定した ID で読み込み、上記のいずれも行っていない場合(「ここにアクセスするにはログインが必要だったはず」という前提のもと、ID だけを使っている場合)、それが典型的な IDOR です。認証は認可ではありません。
// [P0] BOLA — Express: looks up by id, never checks ownership
app.get("/api/orders/:id", requireAuth, async (req, res) => {
const order = await Order.findByPk(req.params.id);
res.json(order);
});
// Fixed — scope to the authenticated principal
app.get("/api/orders/:id", requireAuth, async (req, res) => {
const order = await Order.findOne({
where: { id: req.params.id, userId: req.user.id },
});
if (!order) return res.sendStatus(404);
res.json(order);
});
重大度:機微なデータの読み取り、または認証済みのユーザーなら誰でも実行できる書き込み/削除は P0 です。ID の空間が大きく、推測が困難で、かつデータの機密性が低い場合は P1 です(それでも指摘事項です。推測困難な ID も漏えいする可能性があります)。
Pass 3: 機能レベルの認可(BFLA/垂直権限昇格) — OWASP API5
ユーザー管理、ロール変更、管理者ダッシュボード、データのエクスポート、課金、内部ツール、機能フラグなど、特権を要する操作を実行するエンドポイントでは、認証済みであることだけでなく、主体のロールまたは権限を確認する必要があります。
次の点を指摘します。
/admin/*ルートや特権操作が、認証ミドルウェアだけで保護されており、ハンドラーまたはミドルウェアチェーンにロール/権限の確認がない。- ロールの確認がフロントエンド(ボタンを非表示にするなど)でしか行われず、API エンドポイントが保護されていない。
- 保護処理がなく、難読化に頼っている「隠し」管理者エンドポイント(文書化されていないパスなど)。
# [P1] BFLA — authenticated, but any user can promote anyone to admin
@router.post("/users/{id}/role")
def set_role(id: int, body: RoleIn, user=Depends(current_user)):
User.objects.filter(id=id).update(role=body.role)
# Fixed — assert the privilege
@router.post("/users/{id}/role")
def set_role(id: int, body: RoleIn, user=Depends(current_user)):
if not user.is_admin:
raise HTTPException(403)
User.objects.filter(id=id).update(role=body.role)
Pass 4: マスアサインメント/過剰なプロパティのバインド — OWASP API3
ハンドラーがリクエストボディ全体をモデルにバインドする場合(User.update(req.body)、Model(**request.data)、user.update_attributes(params[:user])、Object.assign(entity, req.body))、クライアントが制御してはならないフィールドを確認します。role、is_admin、is_verified、email_verified、owner_id、org_id、tenant_id、balance、price、status などです。いずれかのフィールドを操作できる場合、クライアントはフィールドを追加することで権限を昇格したり、オブジェクトを別のテナントに移したりできます。
修正方法:書き込み可能なフィールドを明示的に許可リストにします。strong params、フィールド名を指定したシリアライザー/DTO、入力に限定した Pydantic モデルなどが使えます。生のボディを永続化モデルに直接バインドしてはいけません。
Pass 5: クライアントが指定した ID を信頼しない
主体とテナントは、セッションまたは検証済みトークンからサーバー側で導出する必要があります。クライアントが制御する値から導出してはいけません。次の値を認可の判定またはクエリのスコープ設定に使っている場合は、指摘します。
- リクエストボディ、クエリ文字列、パス、カスタムヘッダー(
X-User-Id、X-Tenant)から読み取ったuser_id/account_id/org_id。 - 署名、発行者、対象者、有効期限を検証せずに信頼している JWT クレーム。
// [P0] — scopes by a query param the caller controls
app.get("/api/invoices", requireAuth, async (req, res) => {
res.json(await Invoice.findAll({ where: { userId: req.query.userId } }));
});
// Fixed: scope by the session principal, ignore client-supplied identity
// where: { userId: req.user.id }
Pass 6: 一貫性と見落とされやすい箇所
認可のバグは、カバレッジにばらつきがある箇所に集中します。それぞれを確認してください。
- 同じリソースに対する別の HTTP メソッド。
GET /orders/:idでは所有権を確認しているのに、同じリソースへのPUT/PATCH/DELETEでは確認していない(またはその逆)。読み取りだけでなく、すべてのメソッドを確認してください。 - ネストされたリソース。
/orgs/:orgId/projects/:projectId— 親から子への関連付けは検証されていますか(プロジェクトは実際にその組織に属しているか、ユーザーはその組織に属しているか)。よくあるバグは、末端のリソースだけを確認し、パスのプレフィックスを信頼するものです。 - バッチ/一括エンドポイント。 ID の配列を受け取る場合、チェックは最初の要素だけでなく、すべての要素に対して実行する必要があります。GraphQL のエイリアス付きクエリも同様です。
- 一覧/コレクションエンドポイント。
GET /invoicesには差し替え可能な ID がないため、テナントの範囲に限定する必要があることを見落としがちです。範囲が限定されていない一覧はテーブル全体を漏えいさせます。単一オブジェクトの IDOR よりも深刻な P0 になることもよくあります。 - GraphQL。
node(id:)/グローバルオブジェクト識別と、関連オブジェクトを読み込むすべてのフィールドリゾルバーで、リゾルバーごとに認可を適用する必要があります。クエリのルートだけを確認しても不十分です。 - 差分で新たに追加されたエンドポイント。
v2ルート、新しいコントローラーアクション、新しいフィールドリゾルバーは、まさにガードが忘れられた箇所です。差分に新しいオブジェクト ID 指定のハンドラーがあれば、必ず Pass 2 のレビュー対象として扱ってください。
Pass 7: 認可に見えるが、認可制御ではないもの
認可のように見えて、実際にはそうではないものです。これらのいずれかがユーザー間の唯一の防御になっている場合、代替されるべきチェックの欠落と同じ深刻度になります。
- ミドルウェア/ルートの登録順序。 ガードが先に実行されるよう登録順序に依存すること。後から上にルートが追加されたり、フレームワークがより具体的なルートを先に照合したりするだけで、簡単に破綻します。
- フロントエンドのみの強制。 ボタンを非表示にし、SPA のルートにガードを設けていても、その背後の API が公開されている。
- 秘密の識別子。 UUID やスラッグだけを唯一の制御として使うこと。ID はログ、
Refererヘッダー、エラーメッセージ、API レスポンスから漏えいします。また、UUIDv1 は一部が予測可能です。推測困難な ID は多層防御の一部であり、チェックの代わりにはなりません(認可がほかの点では正しい場合は P3)。 - デフォルト許可の設定。 どのルールにも一致しない場合に許可するフレームワークのデフォルト設定、欠落した
permission_classes、Spring のpermitAll()、全件に一致するルート、またはポリシーエンジンに問い合わせているものの、デフォルトの判定が許可になっている場合などです。
指摘事項の形式
各指摘事項はこの形式で報告してください。重要度の高い順に並べ、P0 を最初にします。
[P0] bola in app/controllers/orders_controller.rb:42
#show loads Order by params[:id] with no ownership check. Any authenticated
user can read any order by changing the id (sequential integers).
Principal: current_user. Order belongs to a user via user_id.
Vulnerable:
def show
@order = Order.find(params[:id])
end
Fix (scope the lookup to the owner; 404s on non-owned ids):
def show
@order = current_user.orders.find(params[:id])
end
ユーザーがパスなしでコード片を貼り付けた場合は「ハンドラー」と記載し、コード片内の行番号を使用してください。チェックが欠落していることの根拠として、オブジェクト、その所有者、プリンシパルを必ず明記してください。
重要度の基準
- P0 — 認証済みユーザー(多くの場合、未認証ユーザーも)が、連鎖攻撃なしで今すぐ悪用できるもの。機密データの読み取りや書き込み/削除に対するオブジェクトレベルチェックの欠落、通常ユーザーが利用できる管理者機能、範囲が限定されていないコレクションエンドポイント、またはクライアント提供の ID に基づくスコープ設定などです。マージをブロックします。
- P1 — 機密性の低い操作に対するロールチェックの欠落、権限またはテナントのフィールドへのマスアサインメント、または推測困難な ID 空間の広さだけを防御としている BOLA。ユーザーデータに影響する場合はマージをブロックします。
- P2 — カバレッジにばらつきがあるものの、まだ悪用可能ではない状態。一方の HTTP メソッドにはスコープ設定があるが、同じリソースに対する別のメソッドでは不明確、テナントのスコープ設定が存在するものの脆弱、または静的解析では確認できないミドルウェアに依存したチェックなどです。
- P3 — 多層防御。認可がほかの点では正しい場合の予測可能な ID、認可に関するリグレッションテストの欠落、または第二の防御層として ID の秘匿性に依存している状態です。
コードに問題がないように見える場合
すべてのパスを確認して何も見つからなかった場合は、次のようにします。
- 問題が見つからなかったことを明確に伝え、確認した内容を述べます。Pass 0 の所有権モデル、列挙したハンドラー、そしてすべての動詞で、すべてのオブジェクト ID 経由のパスと特権関数にチェックがあることを確認したと伝えてください。
- 静的レビューでは確認できないことを記載します。実行時のミドルウェアの配線と順序、確認していない一元化されたポリシーの実際の内容、フレームワークのデフォルト設定、デプロイ済みの設定がコードと一致しているかどうかです。
- 実行中のインスタンスに対して、動的な BOLA テスト(2 つのアカウントを使い、ID を入れ替える)を実行時に行い、
references/checklist.mdを確認することを推奨します。これは、このスキルでは実施しない、補完的な攻撃側のチェックです。
静的レビューで問題が見つからなくても、デプロイの安全性が証明されたわけではありません。
参考ファイル
references/frameworks.md— 主要な各スタック(Express/NestJS、Django/DRF、Rails + Pundit/CanCanCan、Spring Security、Laravel、Go)で、適切な所有権チェックとロールチェックがどのようなものかを説明しています。慣用的なコードでチェックが存在するかどうかを見極め、プロジェクト独自の規約に沿って修正を記述するために読んでください。references/patterns.md— 「X を安全に認可したい」という場合のレシピを紹介しています。所有者でスコープを絞ったクエリ、一元化されたポリシーレイヤー、デフォルトで拒否する設定、サーバー側で取得した ID、安全なマルチテナントのスコープ設定などです。問題を指摘するだけでなく、修正を提案する際に読んでください。references/checklist.md— エンドポイント、リソース、アプリケーションごとにまとめた、トリアージの順序付きチェックリストです。完全な監査、多数のエンドポイントの一括確認、大規模な調査結果の優先順位付けに使用してください。
このスキルで対応しないこと
- アプリを実行したり、リクエストを送信したり、認証情報を必要としたりすることはありません。ソースを読み取ります。実行時の確認には、動的な BOLA テスター(2 つのアカウントを使い、オブジェクト ID を入れ替える)と組み合わせてください。これは、この防御側のレビューを補完する攻撃側のチェックです。
- 認可エンジン(OPA/Rego、AWS Cedar、OpenFGA、Oso)に取って代わることはありません。既存の仕組みが、昨日追加されたパスも含め、すべてのパスで実際に適用されているかを確認します。
- 悪用可能性が証明されていないことを理由に、チェックの欠落を見逃すことはありません。チェックの存在を確認できない場合は、その点を指摘します。誤検知のコストはコメントですが、見逃しは侵害につながります。
- 認証を認可として扱うことはありません。「ログインが必要です」は、「このユーザーがこのオブジェクトにアクセスしてよいか」という問いへの答えにはなりません。