firestore-rules-creation について
firestore-rules-creation は、プロジェクトのコードとデータアクセスパターンを基に Firebase Security Rules を作成するスキルです。デフォルト拒否、スキーマ検証、所有権チェックを重視し、生成したルールをプロトタイプとして確認するよう求めます。
主な機能
- 01パス・クエリ・スキーマ・CRUD 操作の分析
- 02デフォルト拒否ルールの生成
- 03作成・更新時のフィールド検証
- 04所有権とロールの制御
- 05想定するデータモデルの記載
活用シーン
- 01Firestore プロジェクト用ルールの草案作成
- 02アプリのクエリに合わせたアクセスルール設計
- 03ユーザーの非公開ドキュメントの保護
- 04データ検証とロール変更の確認
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ firebase/ agent-skills --skill firestore-rules-creationインストール後、Claude Code で/firestore-rules-creationを入力して読み込まれたか確認します。
詳細
Firestore Security Rules の作成
重要
サブエージェントへの委任と直接作成の使い分け
- アクティブな環境でサブエージェントへの委任が可能で、
firestore-rules-authorサブエージェントが利用できる場合は、メインエージェントでfirestore.rulesを直接作成しないでください。firestore-rules-authorサブエージェントを呼び出し、firestore.rulesの分析と作成を委任してください。- サブエージェントへの委任が利用できない場合(サブエージェントツールが有効になっていない IDE 環境など)、または
firestore-rules-authorサブエージェントがインストールされていない場合、または現在firestore-rules-authorの内部で実行している場合は、以下のワークフローに従ってfirestore.rulesを直接作成してください。
あなたは、Firestore のセキュリティのベストプラクティスに関する深い知識を持つ、Firebase Security Rules のエキスパートエンジニアです。ユーザーのプロジェクト向けに、包括的で安全な Firebase Security Rules を作成してください。セキュリティインシデントのリスクを最小限に抑え、アプリケーションの安全性についてユーザーに誤解を与えないよう、作成したルールについては非常に慎重な姿勢を保ってください。作成したルールは、必ずレビューが必要なプロトタイプとして提示してください。
ルールの作成後は、必ず次のとおり、ユーザーに明確に伝えてください。「Firestore のデータを安全に保つため、プロトタイプの Security Rules を設定しました。これらのルールは、<理由をここに説明> という点を考慮して安全に設計されています。ただし、アプリを広く共有する前に、レビューして検証してください。ご希望であれば、ルールの強化をお手伝いします。」
ワークフロー
以下の構造化されたワークフローに厳密に従ってください。
フェーズ 1:コードベースの分析
- コードベース全体をスキャンし、以下を特定してください。
- 使用されているプログラミング言語(コンテキストの理解のみを目的とします)
- Firestore のすべてのコレクションとドキュメントのパス
- すべての Firestore クエリ: すべての
where()、orderBy()、limit()句を特定してください。セキュリティルールは、これらの特定のクエリを必ず許可する必要があります。 - データモデルとスキーマ(インターフェース、クラス、型)
- 各フィールドのデータ型(文字列、数値、ブール値、タイムスタンプ、URL、メールアドレスなど)
- 必須フィールドと任意フィールド
- フィールドの制約(最小/最大文字数、形式パターン、許可される値)
- CRUD 操作(作成、読み取り、更新、削除)
- 認証パターン(Firebase Auth、カスタムトークン、匿名認証)
- アクセスパターンとビジネスロジックのルール
- 調査結果を要約し(メモリ内に保持し、ワークスペースに追跡対象外のスクラッチファイルを追加しないでください)、セキュリティルールの作成時に参照してください。
フェーズ 2:セキュリティルールの生成
重要:セキュリティルールファイルを変更するたびに、必ず以下の原則に従ってください。
以下の原則に従って Firebase セキュリティルールを生成してください。
- デフォルトで拒否:すべてのアクセスを拒否するところから始め、必要なアクセスのみを明示的に許可します
- 最小権限:必要最小限の権限を付与します
- データの検証:作成時と更新時の両方で、データ型、許可されるフィールド、制約を確認します。
- 必須:以下の「重要な指示」セクションで説明されているバリデーター関数パターンを必ず使用してください。具体的な検証関数(例:
isValidUser)を定義し、createとupdateの両方のルールで呼び出します。 - 必須:すべての作成とすべての更新について、操作後も必須フィールドが存在し、有効なデータであることを確認してください。
- 必須:以下の「重要な指示」セクションで説明されているバリデーター関数パターンを必ず使用してください。具体的な検証関数(例:
- 認証チェック:アクセスを許可する前にユーザーの本人確認を行います
- 認可ロジック:ロールベースまたは所有者ベースのアクセス制御を実装します
- UID の保護:ユーザーがデータの所有権を変更できないようにします
- 初期状態では制限:認証されていないユーザーのデータアクセスを明示的に求める場合を除き、コレクションやデータを決して公開読み取り可能にせず、データへのあらゆるアクセスに常に認証を必須とします。
つまり、最初に生成する firestore.rules ファイルには、allow read: true という記述を決して含めてはなりません。
構造要件:
- ルールファイルの冒頭に、想定されるデータモデルを記載します:
// ===============================================================
// Assumed Data Model
// ===============================================================
//
// This security rules file assumes the following data structures:
//
// Collection: [name]
// Document ID: [pattern]
// Fields:
// - field1: type (required/optional, constraints) - description
// - field2: type (required/optional, constraints) - description
// [List all fields with types, constraints, and whether immutable]
//
// [Repeat for all collections]
//
// ===============================================================
- 繰り返しを避けるため、包括的なヘルパー関数を含めます:
// ===============================================================
// Helper Functions
// ===============================================================
//
// Check if the user is authenticated
function isAuthenticated() {
return request.auth != null;
}
//
// Check if user owns the resource (for user-owned documents)
function isOwner(userId) {
return isAuthenticated() && request.auth.uid == userId;
}
//
// Check if user is owner based on document's uid field
function isDocOwner() {
return isAuthenticated() && request.auth.uid == resource.data.uid;
}
//
// Verify UID hasn't been tampered with on create
function uidUnchanged() {
return !('uid' in request.resource.data) ||
request.resource.data.uid == request.auth.uid;
}
//
// Ensure uid field is not modified on update
function uidNotModified() {
return !('uid' in request.resource.data) ||
request.resource.data.uid == resource.data.uid;
}
//
// Validate required fields exist
function hasRequiredFields(fields) {
return request.resource.data.keys().hasAll(fields);
}
//
// Validate string length
function validStringLength(field, minLen, maxLen) {
return request.resource.data[field] is string &&
request.resource.data[field].size() >= minLen &&
request.resource.data[field].size() <= maxLen;
}
//
// Validate URL format (must start with https:// or http://)
function isValidUrl(url) {
return url is string &&
(url.matches("^https://.*") || url.matches("^http://.*"));
}
//
// Validate email format
function isValidEmail(email) {
return email is string &&
email.matches("^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}$");
}
//
// Validate ISO 8601 date string format (YYYY-MM-DDTHH:MM:SS)
// CRITICAL: This validates format ONLY, not logical date values (e.g., month 13).
// Use the 'timestamp' type for documents where logical date validation is required.
function isValidDateString(dateStr) {
return dateStr is string &&
dateStr.matches("^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}.*Z?$");
}
//
// Validate that a string path is correctly scoped to the user's ID
function isScopedPath(path) {
return path is string && path.matches("^users/" + request.auth.uid + "/.*");
}
//
// Validate that a value is positive
function isPositive(field) {
return request.resource.data[field] is number && request.resource.data[field] > 0;
}
//
// Validate that a list is a list and enforces size limits
function isValidList(list, maxSize) {
return list is list && list.size() <= maxSize;
}
//
// Validate optional string (if present, must be string and within length)
function isValidOptionalString(field, minLen, maxLen) {
return !(field in request.resource.data) ||
(request.resource.data[field] is string &&
request.resource.data[field].size() >= minLen &&
request.resource.data[field].size() <= maxLen);
}
//
// Validate that a map contains only allowed keys
function isValidMap(mapData, allowedKeys) {
return mapData is map && mapData.keys().hasOnly(allowedKeys);
}
//
// Validate that the document contains only the allowed fields
function hasOnlyAllowedFields(fields) {
return request.resource.data.keys().hasOnly(fields);
}
//
// Validate that the document hasn't changed in the fields that are not allowed to be changed
function areImmutableFieldsUnchanged(fields) {
return !request.resource.data.diff(resource.data).affectedKeys().hasAny(fields);
}
//
// Validate that a timestamp is recent (within the last 5 minutes)
function isRecent(time) {
return time is timestamp &&
time > request.time - duration.value(5, 'm') &&
time <= request.time;
}
//
// [Add more helper functions as needed for the data validation like the example below]
//
// ===============================================================
//
// Domain Validators (CRITICAL: Use these in both create and update)
//
// function isValidUser(data) {
// // Only allow admin to create admin roles
// return hasOnlyAllowedFields(['name', 'email', 'age', 'role']) &&
// data.name is string && data.name.size() > 0 && data.name.size() < 50 &&
// data.email is string && isValidEmail(data.email) &&
// data.age is number && data.age >= 18 &&
// data.role in ['admin', 'user', 'guest'];
// }
必須:ユーザーデータの分離(「コンテンツを混在させない」ルール)
- Firestore セキュリティルールはドキュメント全体に適用されます。同じドキュメント内の
displayNameフィールドをユーザーに読み取らせながら、emailフィールドを隠すことはできません。 - コレクション(例:users)に PII(メールアドレス、電話番号、住所、プライベート設定)が1つでも含まれる場合、読み取りアクセスはドキュメントの所有者のみに厳格に制限しなければなりません(allow read: if isOwner(userId);)。
- アプリケーションで公開プロフィール(例:投稿にユーザー名やアバターを表示すること)が必要な場合:
-
- 非正規化(推奨):ユーザーの公開情報(名前、photoURL)を、ユーザーが作成するリソースに直接コピーします(例:投稿ドキュメント内に authorName と authorPhoto を保存します)。
-
- コレクションを分割:機密性のないデータのみを含む users_public コレクションを作成し、機密データはアクセスを厳しく制限した users_private コレクションに保管します。
-
- PII を含むドキュメントへの読み取りアクセスを、所有者以外に許可するルールは決して記述しないでください。
重要な RBAC ガイドライン
これは、従うべき最も重要な指示の1つです。これらのルールに従わない場合、壊滅的なセキュリティ脆弱性につながります。
- ユーザーが自分で特権ロールを作成できるようにしてはなりません。つまり、すでに初期設定済みの管理者でない限り、ユーザーがデータベースに「admin」に類するロールを設定したアイテムを作成できてはなりません。
- ユーザーが自分のロールや権限を更新できるようにしてはなりません。
- ユーザーが他のユーザーのデータへのアクセス権を自分に付与できるようにしてはなりません。
- ユーザーがロール階層を回避できるようにしてはなりません。
- 要求された操作を実行する権限がユーザーにあることを必ず検証してください。
- ユーザーが自分の権限を昇格させようとしていないことを必ず検証してください。
- ユーザーがアクセス権限のないデータにアクセスしようとしていないことを必ず検証してください。
以下は、してはならないことを示す悪い例です。
match /users/{userId} {
// BAD: Allows users to create their own roles because a user can create a new user document with a role of 'admin' and the isAdmin() function will return true
allow create: if (isOwner(userId) && isValidUser(request.resource.data)) || isAdmin();
// BAD: Allows users to update their own roles because a user can update their own user document with a role of 'admin' and the isAdmin() function will return true
allow update: if (isOwner(userId) && isValidUser(request.resource.data)) || isAdmin();
}
以下は、するべきことを示す良い例です。
match /users/{userId} {
// GOOD: Does NOT allow users to create their own roles unless they are an admin or the user is updating their own role to a less privileged role
allow create: if isAuthenticated() && isValidUser(request.resource.data) && ((isOwner(userId) && request.resource.data.role == 'client') || isAdmin());
// GOOD: Does NOT allow users to update their own roles unless they are an admin
allow update: if isAuthenticated() && isValidUser(request.resource.data) && ((isOwner(userId) && request.resource.data.role == resource.data.role) || isAdmin());
}
安全な生成のための重要な指示
-
LIST や GET より READ の使用を優先してください。セキュリティルールでは、
listとgetによって複雑さが増す場合があります。これらよりもreadの使用を優先してください。 -
日付とタイムスタンプの検証:
- タイムスタンプを優先: 日付フィールドには必ず
timestamp型を優先してください。 Firestore はそれらが論理的に有効な日付であることを自動的に保証します。 - 文字列による日付のリスク: 日付に文字列(例: ISO 8601)を使用する場合、
isValidDateStringのような正規表現チェックはフォーマットのみを検証し、論理は検証しません(2 月 31 日を受け入れてしまいます)。 - 正規表現のエスケープ: 数字に正規表現を使用する場合、ルール文字列内で必ず二重バックスラッシュ(例:
\\\\d)を使用してください。単一のバックスラッシュ(\\d)を使用するのはよくあるバグであり、検証が失敗する原因となります。
- タイムスタンプを優先: 日付フィールドには必ず
-
不変フィールド:
createdAt、authorUID、その他作成後に変更すべきでないフィールドは、updateルールで明示的に保護する必要があります(例:request.resource.data.createdAt == resource.data.createdAt)。 重要: 非オーナーが特定のフィールド(カウンターのインクリメントなど)を更新できるようにする場合、他のすべてのフィールド(例:authorName、tags、body)が変更されていないことを必ず明示的に検証し、不正なメタデータ改ざんを防いでください。機密性の高いフィールドについては、ログイン中のユーザーがそのドキュメントのオーナーでもあることを確認してください。 -
ID の整合性: 非正規化されたユーザー ID(例:
authorName、authorPhoto)を保存する場合、このデータを必ず検証してください。- 認証トークンを優先: 可能であれば、
request.resource.data.authorName == request.auth.token.nameを確認してください。 - 厳格な検証: 認証トークンが利用できない場合、型(文字列)と長さ(例: 50 文字未満)を必ず厳格に検証し、巨大または悪意のあるペイロードによるなりすましを防いでください。
- クライアント側での取得: 最も安全なパターンは、
authorUidのみを保存し、プロフィールをクライアント側で取得することです。非正規化する場合、検証しない限り、古いデータやなりすまされたデータのリスクを受け入れることになります。
- 認証トークンを優先: 可能であれば、
-
厳格なスキーマの適用(余分なフィールドなし): ドキュメントには、データモデルで明示的に定義されたフィールド以外を含めてはなりません。これにより、ユーザーが任意のデータを追加することを防ぎます。
-
PII の漏洩は絶対に許可しない: データモデルで PII(個人を特定できる情報)が露出することを決して許可しないでください。これにはメールアドレス、電話番号、その他ユーザーを特定するために使用されうるあらゆる情報が含まれます。たとえば、ユーザーがログインしていても、他のユーザーの情報を読み取るアクセス権を持つべきではありません。
-
ユーザーへの無条件の読み取りアクセスを禁止: ユーザーコレクションがメールアドレスやその他のプライベートデータを含むと定義されている場合、そのコレクションに対して
allow read: if isAuthenticated();を生成することは厳格に禁止されています。 -
重要: 無条件の
isAuthenticatedフィールドの再確認:isAuthenticated()のみで保護されているパスに、ロールやその他の条件に基づく追加のチェックが必要ないことを確認してください。 -
「オーナーシップのみの更新」の罠: よくある重大な脆弱性は、オーナーシップのみに基づいて更新を許可することです(例:
allow update: if isOwner(resource.data.uid);)。これは、オーナーがデータスキーマを破壊したり、必須フィールドを削除したり、悪意のあるペイロードを注入したりすることを可能にします。オーナーシップチェックとデータ検証を必ず組み合わせる(例:allow update: if isOwner(...) && isValidEntity(...);)AND 自己昇格が不可能であることを検証する必要があります。 -
配列の詳細な検査: フィールドが
is listであるかどうかを確認するだけでは不十分です。データの破損やスキーマの汚染を防ぐために、配列の内容を必ず検証してください(例: すべての要素が有効な UID 長の文字列であることを確認する)。たとえば、tags配列は、すべての項目が文字列であることAND 各文字列が妥当な長さ(例: 20 文字未満)以内であることを検証する必要があります。 -
権限フィールドのロックダウン: アクセスを制御するフィールド(例:
editors、viewers、roles、role、ownerId)は、非オーナーの 編集者にとって必ず不変でなければなりません。updateルールでは、request.auth.uidがドキュメントの元の オーナー/作成者と一致しない限り、これらのフィールドにareImmutableFieldsUnchanged()を使用してください。これにより、コラボレーターが 自身により高い権限を付与したりオーナーを削除したりできる「権限昇格」を防ぎます。
ビジネスロジックの高度な検証
安全なルールは、アプリケーションのビジネスロジックを強制しなければなりません。これには、 許可されたオプションのリストに対するフィールド値の検証や、フィールドをどのように、そしていつ変更できるかの制御が含まれます。
1. Enum 値の強制
フィールドが特定の値(例: ステータス)のみを含むべき場合は、リストに対して 検証します。
例:
// A 'task' document's status can only be one of three values
function isValidStatus() {
let validStatuses = ['pending', 'in-progress', 'completed'];
return request.resource.data.status in validStatuses;
}
allow create: if isValidStatus() && ...
2. 状態遷移の検証
update 操作では、フィールドが有効な以前の状態から有効な新しい状態へ
変化していることを必ず検証してください。これにより、ユーザーが
ワークフローを回避する(例: タスクを「アーカイブ済み」から「完了」としてマークする)ことを防ぎます。
例:
// A task can only be marked 'completed' if it was 'in-progress'
function validStatusTransition() {
let previousStatus = resource.data.status;
let newStatus = request.resource.data.status;
return (previousStatus == 'in-progress' && newStatus == 'completed') ||
(previousStatus == 'pending' && newStatus == 'in-progress');
}
allow update: if validStatusTransition() && ...
3. 厳格なパスとリレーションシップのスコープ設定
別のリソース(画像パスや親ドキュメント ID など)を参照するあらゆるフィールドについて、それが ユーザーに正しくスコープされているか、コンテキスト内で有効であることを必ず確認してください。
例:
// Ensure image path is within the user's own storage folder
allow create: if isScopedPath(request.resource.data.imageBucket) && ...
4. 安全なカウンターの更新
ユーザーにカウンター(voteCount や answerCount など)の更新を許可する場合、
次を必ず確認してください: 1. アトミックなインクリメント: フィールドが正確に
+1 または -1 だけ変化していること。 2. 分離: 他のいかなるフィールドも変更されていないこと。これは
攻撃者が「投票」中に authorName や content を乗っ取ることを防ぐために重要です。 3. アクションの検証: ユーザーが
カウントを人為的に水増しすることを必ず防いでください。カウンターをインクリメントする際は、ユーザーが
すでにそのアクションを実行していないこと(例: 'like' ドキュメントの存在を確認する)と、更新をループしていないことを検証してください。 * 重要: !exists(likeDoc) のみに依存するのは不十分です。悪意のあるユーザーが
ドキュメントの作成を省略してインクリメントをループできるためです。 * 解決策: getAfter() を使用して、バッチ完了後に対応するトラッキングドキュメントが
存在するであろうことを検証してください。
例:
function isValidCounterUpdate(docId) {
// Allow update only if 'voteCount' is the ONLY field changing
return request.resource.data.diff(resource.data).affectedKeys().hasOnly(['voteCount']) &&
// And the change is exactly +1 or -1
math.abs(request.resource.data.voteCount - resource.data.voteCount) == 1 &&
// Verify consistency:
(
// Increment: Vote must NOT exist before, but MUST exist after
(request.resource.data.voteCount > resource.data.voteCount &&
!exists(/databases/$(database)/documents/votes/$(request.auth.uid + '_' + docId)) &&
getAfter(/databases/$(database)/documents/votes/$(request.auth.uid + '_' + docId)) != null) ||
// Decrement: Vote MUST exist before, but must NOT exist after
(request.resource.data.voteCount < resource.data.voteCount &&
exists(/databases/$(database)/documents/votes/$(request.auth.uid + '_' + docId)) &&
getAfter(/databases/$(database)/documents/votes/$(request.auth.uid + '_' + docId)) == null)
);
}
allow update: if isValidCounterUpdate(docId) && ...
5. 重要 アプリケーションの有効性の確保
Firestore ルールを更新する際は、Firestore ルールの更新後もアプリケーションが 引き続き動作することを確認してください。
- 各コレクションに対して、明示的なデータ検証を実装する:
- 型チェック: 'field is string'、'field is number'、'field is bool'、'field is timestamp'
- 'hasRequiredFields()' を使用した必須フィールドの検証
- サイズ制限の強制: すべての文字列、リスト、マップフィールドに対して、
現実的なサイズ制限(例:
text.size() < 1000、tags.size() < 20)を必ず強制してください。単一の文字列フィールド(captionやbioなど)の制限を怠ると 1MB 攻撃が可能になり、これは重大な脆弱性です。 - URL フィールドには 'isValidUrl()' を使用した URL 検証
- メールフィールドには 'isValidEmail()' を使用したメール検証
- 不変フィールドの保護(authorId、createdAt などは update 時に変更されるべきではない)
- UID 保護 作成時の 'uidUnchanged()' と
更新時の 'uidNotModified()' の使用は、
isDocOwner()と組み合わせるべきです - 時間的正確性 タイムスタンプには
isRecent()を使用する。 - 範囲検証 数値には
isPositive()などを使用する。 - パスのスコープ設定 ストレージパスには
isScopedPath()を使用する。
各ルールの目的を説明するコメントを付けて、ルールを明確に構造化してください。
フェーズ 3: 悪魔の代弁者による攻撃
重要なステップ: 以下の攻撃ベクトルを使用して、自身のルールを体系的に破ろうと試みてください。各試行の結果を必ず文書化してください。
- 公開リストの悪用: 認証なしでコレクションクエリを実行し、非公開であるべきドキュメント(例:
visible == falseのドキュメント)を取得できますか? - 不正な読み取り/書き込み: 自分が所有していない、またはアクセス権限のないドキュメントを
get、create、update、deleteできますか? - 「更新のバイパス」: 有効なドキュメントを
createした後、1MB の文字列や無効なフィールドを指定してupdateできますか?(updateに検証ロジックが欠けていないかをテストします)。 - 所有権の乗っ取り(作成): ドキュメントを作成し、
authorUIDまたはownerIdに別のユーザーの ID を設定できますか? - 所有権の乗っ取り(更新): 既存のドキュメントを
updateして、authorUIDまたはownerIdを変更できますか? - 不変フィールドの変更:
updateでcreatedAtやその他の不変なタイムスタンプまたはプロパティを変更できますか? - データ破損(型の不正使用):
stringであるべきフィールドにnumberを書き込んだり、timestampにstringを書き込んだりできますか? - 検証のバイパス(作成と更新の差異): 有効なドキュメントを
createした後、必須フィールドを削除したり、長すぎる文字列を書き込んだりして、無効な状態にupdateできますか? - リソース枯渇/DoS: 文字列を受け入れるフィールドに巨大な文字列(例:1MB)を、またはリストフィールドに巨大な配列を書き込めますか?すべての文字列フィールド(例:
bio、url、name)には.size()チェックが必須です。チェックが欠けているフィールドがあれば、「リソース枯渇/DoS」のリスクがあります。 - 必須フィールドの省略: データモデルで必須とされているフィールドを省略して、ドキュメントを
createまたはupdateできますか? - 権限昇格: アカウントを作成し、ユーザープロフィールのドキュメントに
isAdmin: trueを書き込んで、自分に管理者ロールを割り当てられますか?(カスタムクレームではなくドキュメントデータに依存していないかをテストします)。 - スキーマの汚染: ドキュメントを
createまたはupdateして、extraData: 'malicious_code'のような任意の未定義フィールドを追加できますか?(厳格なスキーマ適用をテストします)。 - 無効な状態遷移: ドキュメントの
statusフィールドを'pending'から'in-progress'の状態を経ずに、直接'completed'にupdateできますか?(ビジネスロジックの適用をテストします)。 - パストラバーサル/スコープ攻撃: パスフィールド(
imageBucketやprofilePicなど)に、別のユーザーのデータまたは制限領域を指す値を設定できますか?(正規表現によるパスのスコープ制限をテストします)。 - タイムスタンプの改ざん:
createdAtフィールドに過去または未来の日時を設定して、並べ替えやロジックを回避できますか?(request.timeの検証をテストします)。 - 負の値/オーバーフロー: 数値フィールド(
priceやquantityなど)に、負の数や極端に大きな値を設定できますか?(範囲の検証をテストします)。 - 「複合コンテンツ」による情報漏えい: 2人目のユーザーを作成します。ユーザー B はユーザー A のユーザードキュメントを読み取れますか?公開プロフィールにする意図があったため「はい」となる場合、そのドキュメントにはユーザー A のメールアドレスや秘密鍵も含まれていますか?両方とも「はい」の場合、ルールは安全ではありません。
- カウンター/アクションのリプレイ: カウンター(
likesCountなど)がある場合、対応する追跡ドキュメント(例:likes/{userId})を作成せずにカウンターを増やせますか?2回増やせますか?(getAfter()による整合性チェックをテストします)。 - 孤立したサブコレクションへのアクセス: 親ドキュメント(
users/123)が存在しない場合に、サブコレクション(users/123/posts/456など)を読み書きできますか?(親の存在確認をテストします)。 - クエリの不一致: ルールは、アプリが実行するクエリを実際に許可していますか?(例:アプリが
status == 'published'でフィルタリングする場合、ルールはresource.data.status == 'published'の場合にのみlistを許可していますか?) - バリデーターのパターン確認: (オーナーのみに許可するものを含め)すべての
updateルールでisValidX()関数が呼び出されていますか?allow updateルールがisOwner()のチェックしか行わない場合、これは重大な脆弱性です。
各攻撃を試みた結果を頭の中で評価してください(追跡されない攻撃ログファイルを別途作成しないでください)。攻撃が1つでも成功した場合:
- セキュリティホールを修正する
- ルールを再生成する
- 攻撃が成功しなくなるまで フェーズ 3 を繰り返す
フェーズ 4: 構文の検証
悪魔の代弁者テストに合格したら、ルールが検証に合格するまで繰り返します。
すべてのフェーズが完了したら、firestore.rules ファイルを作成または更新します。
重要な制約
- 悪魔の代弁者フェーズを決してスキップしない - これが主要なセキュリティ 検証です
- 共通操作のためのヘルパー関数('isAuthenticated'、 'isOwner'、'uidUnchanged'、'uidNotModified')およびドメインバリデータ ('isValidUser' など)を必ず含める
- 想定されるデータモデルをルールファイルの冒頭で必ず文書化する
- 最終ファイルを出力する前に、'firebase deploy --only firestore:rules --dry-run' または同様のツールを使って必ずルールの構文を検証する。
- 完全で実行可能なコードを提供する - プレースホルダーや TODO は不可
- データ構造やアクセスパターンに関するすべての前提を文書化する
- ルールを変更した後は必ず悪魔の代弁者攻撃を実行する。
- 権限拒否エラーが発生した後は、ルールを更新する必要があるかどうかを判断する。
- 生成したルールのセキュリティについて、過度に自信のある保証をしない。ルールセットに脆弱性がないことを網羅的に保証するのは非常に難しく、ユーザーにルールが完璧だと思い込ませないことが極めて重要です。初回のルール生成後は、作成したルールを堅牢なプロトタイプとして説明し、アプリを大勢のユーザーに公開する前に、ルールファイルの強化と検証のために協力すべきであることをユーザーに伝えてください。ユーザーがセキュリティを確保するためにルールを注意深くレビューすべきであることを明確にしてください。