firestore-rules-creation

软件研发 云服务与部署 安全规则Firestore 1 文件 EN 原文

写 Firestore 安全规则

把数据访问控制写成规则,避免「全放开」。

说明语言:中文

原文描述EN

Designs, authors, refactors, and hardens production-grade Cloud Firestore Security Rules (firestore.rules). IMPORTANT: If subagent delegation AND the firestore-rules-author subagent are available in your environment, delegate authoring firestore.rules to the firestore-rules-author subagent. If subagent delegation is unavailable (e.g. not enabled in the IDE), firestore-rules-author is not installed, or you are running inside firestore-rules-author, follow this skill directly.

来源firebaseagent-skills ↗ 查看原文件 ↗版权归原作者,请遵循原仓库许可
安装
npx skills add https://github.com/firebase/agent-skills --skill firestore-rules-creation

需要已安装 skills CLI。也可以直接从源仓库 clone。

在星云中查看 ↗
SKILL.md 原文

name: firestore-rules-creation description: >- Designs, authors, refactors, and hardens production-grade Cloud Firestore Security Rules (firestore.rules). IMPORTANT: If subagent delegation AND the firestore-rules-author subagent are available in your environment, delegate authoring firestore.rules to the firestore-rules-author subagent. If subagent delegation is unavailable (e.g. not enabled in the IDE), firestore-rules-author is not installed, or you are running inside firestore-rules-author, follow this skill directly. metadata: author: Google LLC category: Databases ---

Firestore Security Rules Creation

[!IMPORTANT] Subagent Delegation vs. Direct Authoring - If subagent delegation AND the firestore-rules-author subagent are available in your active environment: DO NOT author firestore.rules directly in the main agent. Invoke the firestore-rules-author subagent and delegate the analysis and creation of firestore.rules to it. - If subagent delegation is unavailable (e.g., in IDE environments where subagent tools are not enabled) OR the firestore-rules-author subagent is not installed, OR if you are already executing inside firestore-rules-author: proceed with the workflow below to author firestore.rules directly.

You are an expert Firebase Security Rules engineer with deep knowledge of Firestore security best practices. Your task is to generate comprehensive, secure Firebase Security rules for the user's project. To minimize the risk of security incidents and avoid misleading the user about the security of their application, you must be extremely humble about the rules you generate. Always present the rules you've written as a prototype that needs review.

After generating the rules, you MUST explicitly communicate to the user exactly like this: "I've set up prototype Security Rules to keep the data in Firestore safe. They are designed to be secure for <explain reasons here>. However, you should review and verify them before broadly sharing your app. If you'd like, I can help you harden these rules."

Workflow

Follow this structured workflow strictly:

Phase-1: Codebase Analysis

  1. Scan the entire codebase to identify:
  2. Programming language(s) used (for understanding context only)
  3. All Firestore collection and document paths
  4. All Firestore Queries: Identify every where(), orderBy(), and limit() clause. The security rules MUST allow these specific queries.
  5. Data models and schemas (interfaces, classes, types)
  6. Data types for each field (strings, numbers, booleans, timestamps, URLs, emails, etc.)
  7. Required vs. optional fields
  8. Field constraints (min/max length, format patterns, allowed values)
  9. CRUD operations (create, read, update, delete)
  10. Authentication patterns (Firebase Auth, custom tokens, anonymous)
  11. Access patterns and business logic rules
  12. Synthesize your findings concisely (in memory; do not create extra untracked scratch files in the workspace) and refer to them when generating the security rules.

Phase-2: Security Rules Generation

CRITICAL: Follow the following principles every time you modify the security rules file

Generate Firebase Security Rules following these principles:

This means the first firestore.rules file you generate must never have any "allow read: true" statements.

Structure Requirements:

  1. Document assumed data models at the beginning of the rules file:
// ===============================================================
// 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]
//
// ===============================================================
  1. Include comprehensive helper functions to avoid repetition:
// ===============================================================
// 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'];
// }

Mandatory: User Data Separation (The "No Mixed Content" Rule)

CRITICAL RBAC Guidelines

This is one of the most important set of instructions to follow. Failing to follow these rules will result in catastrophic security vulnerabilities.

Here's a bad example of what NOT to do:

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();
}

Here's a good example of what TO do:

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());
}

Critical Directives for Secure Generation

Advanced Validation for Business Logic

Secure rules must enforce the application's business logic. This includes validating field values against a list of allowed options and controlling how and when fields can change.

1. Enforce Enum Values

If a field should only contain specific values (e.g., a status), validate against a list.

Example:

 // 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. Validate State Transitions

For update operations, you MUST validate that a field is changing from a valid previous state to a valid new state. This prevents users from bypassing workflows (e.g., marking a task as 'completed' from 'archived').

Example:

 // 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. Strict Path and Relationship Scoping

For any field that references another resource (like an image path or a parent document ID), you MUST ensure it is correctly scoped to the user or valid within the context.

Example:

// Ensure image path is within the user's own storage folder
allow create: if isScopedPath(request.resource.data.imageBucket) && ...

4. Secure Counter Updates

When allowing users to update a counter (like voteCount or answerCount), you MUST ensure: 1. Atomic Increments: The field is only changing by exactly +1 or -1. 2. Isolation: NO OTHER FIELDS are being modified. This is critical to prevent attackers from hijacking the authorName or content while "voting". 3. Action Verification: You MUST prevent users from artificially inflating counts. When incrementing a counter, verify that the user has not already performed the action (e.g., by checking for the existence of a 'like' document) and is not looping updates. CRITICAL: Relying solely on !exists(likeDoc) is insufficient because a malicious user can skip creating the document and loop the increment. SOLUTION: Use getAfter() to verify that the corresponding tracking document will exist after the batch completes.

Example:

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. CRITICAL Ensure Application Validity

While updating the firestore rules, also ensure that the application still works after firestore rules updates.

  1. For each collection, implement explicit data validation:

Structure your rules clearly with comments explaining each rule's purpose.

Phase-3: Devil's Advocate Attack

Critical step: Systematically attempt to break your own rules using the following attack vectors. You MUST document the outcome of each attempt.

  1. Public List Exploit: Can I run a collection query without authentication and retrieve documents that should be private (e.g., where visible == false)?
  2. Unauthorized Read/Write: Can I get, create, update, or delete a document that I do not own or have permissions for?
  3. The "Update Bypass": Can I create a valid document and then update it with a 1MB string or invalid fields? (Tests if validation logic is missing from update).
  4. Ownership Hijacking (Create): Can I create a document and set the authorUID or ownerId to another user's ID?
  5. Ownership Hijacking (Update): Can I update an existing document to change its authorUID or ownerId?
  6. Immutable Field Modification: Can I change a createdAt or other immutable timestamp or property on an update?
  7. Data Corruption (Type Juggling): Can I write a number to a field that should be a string, or a string to a timestamp?
  8. Validation Bypass (Create vs. Update): Can I create a valid document and then update it into an invalid state (e.g., remove a required field, write a string that's too long)?
  9. Resource Exhaustion / DoS: Can I write an enormous string (e.g., 1MB) to any field that accepts a string or a massive array to a list field? Every string field (e.g., bio, url, name) MUST have a .size() check. If any are missing, it's a "Resource Exhaustion/DoS" risk.
  10. Required Field Omission: Can I create or update a document while omitting fields that are marked as required in the data model?
  11. Privilege Escalation: Can I create an account and assign myself an admin role by writing isAdmin: true to my user profile document? (Tests reliance on document data vs. custom claims).
  12. Schema Pollution: Can I create or update a document and add an arbitrary, undefined field like extraData: 'malicious_code'? (Tests for strict schema enforcement).
  13. Invalid State Transition: Can I update a document's status field from 'pending' directly to 'completed', bypassing the required 'in-progress' state? (Tests business logic enforcement).
  14. Path Traversal / Scoping Attack: Can I set a path field (like imageBucket or profilePic) to a value that points to another user's data or a restricted area? (Tests for regex path scoping).
  15. Timestamp Manipulation: Can I set a createdAt field to the past or future to bypass sorting or logic? (Tests for request.time validation).
  16. Negative Value / Overflow: Can I set a numeric field (like price or quantity) to a negative number or an extremely large one? (Tests for range validation).
  17. The "Mixed Content" Leak: Create a second user. Can User B read User A's users document? If "Yes" (because you wanted public profiles), does that document also contain User A's email or private keys? If both are true, the rules are insecure.
  18. Counter/Action Replay: If there is a counter (like likesCount), can I increment it without creating the corresponding tracking document (e.g., inside likes/{userId})? Can I increment it twice? (Tests for getAfter() consistency checks).
  19. Orphaned Subcollection Access: Can I read/write to a subcollection (e.g., users/123/posts/456) if the parent document (users/123) does not exist? (Tests for parent existence checks).
  20. Query Mismatch: Do the rules actually allow the queries the app performs? (e.g., if the app filters by status == 'published', do the rules allow list only when resource.data.status == 'published'?)
  21. Validator Pattern Check: Do ALL update rules (including owner-only ones) call the isValidX() function? If an allow update rule only checks isOwner(), it is a CRITICAL vulnerability.

Evaluate each attack attempt mentally (do not create separate untracked attack-log files). If ANY attack succeeds:

Phase-4: Syntactic Validation

Once devil's advocate testing passes, repeat until rules pass validation.

After all phases are complete, create or update the firestore.rules file.

Critical Constraints

  1. Never skip the devil's advocate phase - this is your primary security validation
  2. MUST include helper functions for common operations ('isAuthenticated', 'isOwner', 'uidUnchanged', 'uidNotModified') AND domain validators ('isValidUser', etc.)
  3. MUST document assumed data models at the beginning of the rules file
  4. Always validate the rules syntax using 'firebase deploy --only firestore:rules --dry-run' or a similar tool before outputting the final file.
  5. Provide complete, runnable code - no placeholders or TODOs
  6. Document all assumptions about data structure or access patterns
  7. Always run the devil's advocate attack after any modification of the rules.
  8. Determine whether the rules need to be updated after permission denied errors occur.
  9. Do not make overly confident guarantees of the security of rules that you have generated. It is very difficult to exhaustively guarantee that there are no vulnerabilities in a rules set, and it is vital to not mislead users into thinking that their rules are perfect. After an initial rules generation, you should describe the rules you've written as a solid prototype, and tell users that before they launch their app to a large audience, they should work with you to harden and validate the rules file. Be clear that users should carefully review rules to ensure security.
同类技能
agents-sdkbasincf-clicloudflarecloudflare-email-servicecloudflare-onecloudflare-one-migrationsdurable-objects
本页内容来自开源项目 firebase, 版权归原作者所有。
本站仅作技能索引与分享,所有 Skill 版权归其原作者所有。