convex-security-audit について
Convexアプリのバックエンドを包括的に監査するスキルです。リリース前やセキュリティインシデント後の開発者向けに、公開関数、データアクセス、ルート、ストレージ、スケジュール処理、秘密情報を調べ、深刻度別の指摘と修正案をまとめます。
主な機能
- 01公開関数の棚卸し
- 02認証とテーブルアクセスの整理
- 03HTTPルートとストレージの確認
- 04スケジュール処理と内部呼び出しの追跡
- 05環境変数の秘密情報確認
- 06深刻度別の監査報告書作成
活用シーン
- 01本番公開前のバックエンド監査
- 02不審なデータアクセスの調査
- 03マルチテナントや課金機能の確認
- 04セキュリティ監査報告書の作成
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ waynesutton/ builder-skills --skill convex-security-auditインストール後、Claude Code で/convex-security-auditを入力して読み込まれたか確認します。
詳細
Convex セキュリティ監査
すべての公開関数、すべてのテーブル、すべてのエントリポイント(HTTP ルート、ファイルストレージ、スケジューラー)を網羅した、調査結果のレポートを作成します。ルールはひとつです。エクスポートされたすべての query、mutation、action は、インターネット上の誰もが任意の引数で呼び出せる公開エンドポイントです。そのため、それぞれについて、呼び出し元が誰なのか、何にアクセスできるのかを確認する必要があります。
10 分で済ませるなら、代わりに convex-security-check を使用してください。このスキルは、時間をかけて完全に監査するバージョンです。
このスキルを使う場面
- 本番公開や大規模リリースの前
- インシデント、キーの漏えい、不審なデータアクセスの後
- コードベースが大きくなり、どの公開関数を誰が呼び出せるかを把握できていない場合
- マルチテナント、管理者向け、または課金機能を追加する場合
- ユーザーから「完全な監査」「セキュリティレビュー」「バックエンド全体の確認」を依頼された場合
参考ファイル
- references/authorization-patterns.md:調査結果の修正時に開いてください。認証済み、管理者、テナントのスコープに応じたアクセスのための
getCurrentUserヘルパー、customQueryとcustomMutationのラッパー、インデックスを使った所有権チェック、ロールチェックについて説明しています。 - references/attack-surface.md:手順 4〜7 で開いてください。HTTP アクション、ストレージ URL の漏えい、スケジューラーへの信頼、
@convex-dev/rate-limiterを使ったレート制限、環境変数内のシークレット、クライアント提供の ID について説明しています。 - references/audit-report-template.md:手順 8 で開いてください。調査結果のテンプレートと、記入用のテーブルごとのデータアクセスマトリクスです。
監査手順
手順を順番に進めてください。調査結果は見つけ次第すべて記録してください。インベントリが完了するまでは、何も修正しないでください。先に修正すると、問題の全体像が見えにくくなります。
1. 公開関数をインベントリ化する
エクスポートされたすべての query、mutation、action を列挙してください。これらが攻撃対象領域です。
rg -n "export const \w+ = (query|mutation|action)\(" convex --glob '!**/_generated/**'
それぞれについて、ファイル、名前、種類、クライアントからの呼び出しが必要かどうか、コストがかかる処理(メール送信、LLM の呼び出し、レコードの作成など)がありレート制限が必要かどうかを記録してください。他の Convex 関数、cron、またはスケジューラーからしか呼び出されないものは、internalQuery、internalMutation、または internalAction にしてください。そのいずれも調査結果として記録してください。
2. 関数ごとに認証を確認する
手順 1 で見つかったすべての公開関数について、読み取りまたは書き込みの前に ctx.auth.getUserIdentity()(またはこれを呼び出すヘルパー)を呼び出しているか確認してください。意図的に匿名で公開している関数はどれですか?
rg -l "= (query|mutation|action)\(" convex --glob '!**/_generated/**' \
| xargs rg -L "getUserIdentity|getCurrentUser|authedQuery|authedMutation"
ここに出力されたファイルには、ファイル内のどこにも認証呼び出しがない公開関数が含まれています。それぞれを読んでください。公開ブログの一覧であれば匿名でも問題ありませんが、ユーザーデータを返したり書き込んだりする関数であれば、重大な問題です。
認証ヘルパーは、ID がない場合に例外をスローするか、早期に戻ることを確認してください。また、ロールチェックでは、クライアント引数ではなく users テーブルからロールを読み取ることを確認してください。
3. テーブルごとにデータアクセスを確認する
convex/schema.ts にあるすべてのテーブルについて、どの公開関数が読み取り、どの公開関数が書き込むのか、またアクセスのスコープを定める条件(所有者 ID、組織への所属、ロール、公開フラグ)を一覧にしてください。レポートテンプレートのデータアクセスマトリクスに記入してください。
次の点を確認してください。
- スコープが設定されていない読み取り:公開関数内の
ctx.db.query("table").collect() ctx.db.get(args.someId)の後に、所有権を比較せずに返却またはパッチを行っている- 所有権を
user._idではなく、偽装可能な値(メールアドレス、表示名)と照合している - スコープ設定に
.withIndex()ではなく.filter()を使用している。通常、これはスコープ設定が後付けされたことを意味します - フィールドが漏えいする戻り値バリデーター:
v.any()、またはpasswordHash、stripeCustomerId、内部フラグなどを含むドキュメント全体のスプレッド
rg -n "v\.any\(\)" convex --glob '!**/_generated/**'
rg -n "ctx\.db\.(get|patch|delete|replace)\(args\." convex --glob '!**/_generated/**'
4. http.ts をレビューする
convex/http.ts のすべてのルートは、Convex クライアントなしでアクセスできます。ルートごとに、呼び出し元の検証方法(Webhook の署名、ベアラートークン、ctx.auth.getUserIdentity())、使用前のリクエストボディの検証の有無、検証済みのデータのみを使って internal.* 関数だけを呼び出しているかを確認してください。
rg -n "http\.route|httpAction\(" convex/http.ts
署名検証を省略している Webhook ハンドラーは重大な問題です。httpAction 内から api.* 関数を呼び出すルートは、それらの関数が前提としている認証を回避している可能性があります。
5. ファイルストレージをレビューする
アップロード URL が生成される箇所と、ファイル URL が返される箇所を見つけてください。
rg -n "storage\.(generateUploadUrl|getUrl|delete)" convex --glob '!**/_generated/**'
認証のない公開 mutation で generateUploadUrl を使うと、誰でもストレージを埋められます。クライアントが渡した任意の ID に対して ctx.storage.getUrl(args.fileId) を返す query は、データ漏洩につながります。その URL を持つ人なら誰でも使用できます。
6. スケジュール実行と内部関数の境界をレビューする
内部関数は設計上、認証を省略します。問題は、未検証の入力を使って何かが内部関数をスケジュール実行していないかどうかです。
rg -n "(runAfter|runAt|runMutation|runAction|interval|cron)\([^)]*\bapi\." convex --glob '!**/_generated/**'
api.* 関数のスケジュール実行は指摘事項です。internal.* を使用してください。次に、各 internal.* の呼び出し元を、呼び出しを開始した公開関数またはルートまでたどり、スケジュール実行前に呼び出し元が所有権を検証したことを確認してください。
7. 環境変数とシークレットをレビューする
rg -n -i "(sk_live|sk_test|whsec_|AKIA[0-9A-Z]{16}|-----BEGIN|api[_-]?key\s*[:=]\s*['\"][A-Za-z0-9])" convex src --glob '!**/_generated/**'
rg -n "process\.env\." convex src --glob '!**/_generated/**'
シークレットは Convex の環境変数(npx convex env set NAME value)に保存し、外部呼び出しを行うアクションまたは HTTP アクション内で process.env を使って読み取ります。ソースコードにシークレットを直接記述している場合は重大な問題です。クライアントコード(src/)で process.env から読み取る値はすべて公開されます。そこに置くのは、シークレットではない VITE_ または NEXT_PUBLIC_ の値だけにしてください。開発環境と本番環境のデプロイで異なるキーを使用していることを確認してください。
8. レポートを作成する
references/audit-report-template.md に記入してください。指摘事項は問題ごとに1件ずつ、深刻度順に並べ、それぞれに場所、証拠、具体的な修正方法を記載してください。データアクセスマトリックスを含めてください。最後に、レビューの結果、正しく匿名アクセスが可能だと判断した公開関数の一覧を記載してください。そうすれば、次の監査担当者が同じ作業を繰り返さずに済みます。
深刻度の基準
| 深刻度 | 意味 | 例 |
|---|---|---|
| 重大 | 匿名の呼び出し元が他のユーザーのデータを読み取ったり変更したりできる、またはシークレットが漏洩している | 公開 mutation が所有権を確認せず、クライアント指定の ID でドキュメントを更新する。ソースコードに API キーがある。Webhook の署名検証がない |
| 高 | 認証済みユーザーが、自分の権限範囲外のデータやアクションにアクセスできる | マルチテナント query で組織のメンバーシップ確認がない。管理者関数がクライアント引数からロールを読み取る |
| 中 | 現時点で直接悪用できる問題はないが、多層防御に不備がある | 内部関数にすべき公開関数。コストのかかるアクションにレート制限がない。認証なしでアップロード URL を発行する |
| 低 | コード品質上の問題 | 機密性のない引数に v.any() を使用している。戻り値のバリデーターがない。インデックスがある箇所で .filter() を使っている |
指摘事項の例
Severity: Critical
Location: convex/tasks.ts, updateTask (mutation, public)
Evidence: handler calls ctx.db.patch(args.taskId, { title: args.title }) after
requireAuth(ctx) but never compares task.userId to the caller. Any signed in
user can rename any task by guessing or capturing an ID.
Fix: load the task, compare task.userId to user._id, throw on mismatch. See
references/authorization-patterns.md, "Ownership checks".
Verified: reproduced by calling updateTask with another user's task ID from the
dashboard function runner.
よくある間違い
| 間違い | 問題になる理由 | 代わりに行うこと |
|---|---|---|
呼び出し元をたどらずに internalMutation を安全とみなす | スケジュール実行する公開関数が未検証の ID を渡す可能性があります | 内部関数だけでなく、スケジュール実行の呼び出し箇所を監査する |
| UI だけで認証を確認する | 誰でもダッシュボードやスクリプトから関数を呼び出せます | すべての公開ハンドラーで、それぞれの関数自身が ID を確認する |
所有権を identity.email と比較する | メールアドレスは、プロバイダー間で再利用されたり、未検証だったりする可能性があります | tokenIdentifier を使って検索した user._id と比較する |
同じケースで return null と throw を混在させる | レスポンスの違いからレコードの存在が漏洩します | query では「見つからない」場合と「自分のものではない」場合の両方で null を返す |
| クライアントだけでレート制限する | 攻撃者はクライアントを経由しません | mutation またはアクション内で @convex-dev/rate-limiter を使用する |
| 修正後、grep を再実行せずに監査を完了とする | 修正によって問題の箇所が移動することがよくあります | 修正後に手順1から7を再実行する |
チェックリスト
- エクスポートされたすべての
query、mutation、actionに、想定される呼び出し元が記載されている - 内部向けにすべき関数はすべて
internal*になっている - すべての公開関数で本人確認を行っているか、意図的に匿名であることが文書化されている
- すべてのテーブルについて、データアクセスマトリックスの行が記入されている
- すべての
http.tsルートで呼び出し元を検証し、リクエスト本文を検証している - アップロード URL の生成とファイル URL の読み取りが、所有権によって制限されている
- スケジューラー、cron、
ctx.run*呼び出し内にapi.*への参照がない - ソースコードにシークレットのリテラルがなく、開発環境と本番環境で異なるキーを使用している
- コストが高い、または悪用される可能性のあるミューテーションとアクションに、レート制限が適用されている
- 各指摘事項について、重大度、場所、根拠、修正方法を記載したレポートが作成されている