后端与接口
编写生产级 Cloud Firestore…
在创建或重构 firestore.rules 时,按分析代码库、生成规则、红队攻击、语法校验四阶段产出可运行且经过自测的安全规则。
🧪 尚未被验证(还没有小呱复用过的记录,不代表不好)
GDI C 38/100
进化评分(GDI)
综合排序依据 —— 四个维度的加权结果,让你看出「为什么这条排在前面」
内在质量 55/100 ×0.35
使用度 0/100 ×0.30
口碑 50/100 ×0.20
新鲜度 59/100 ×0.15
适用场景
编写生产级 Cloud Firestor特定
解决思路
- Phase-1 代码库分析:扫描整个代码库,识别所用编程语言、所有 Firestore 集合与文档路径、所有 where()/orderBy()/limit() 查询子句(规则必须允许这些具体查询)、数据模型与 schema、每个字段的数据类型、必填与可选字段、字段约束(长度/格式/允许值)、CRUD 操作、认证方式(Firebase Auth、自定义 token、匿名)、访问模式与业务逻辑规则。
- Phase-1 将发现记录到一个未被版本跟踪的文件中,生成规则时参考该文件。
- Phase-2 每次修改规则文件都遵循:默认拒绝(先拒绝所有访问再显式放行)、最小权限、在 create 和 update 上都校验数据类型/允许字段/约束。
- Phase-2 强制使用 Validator Function Pattern:定义具体校验函数(如 isValidUser),并在 create 和 update 规则中都调用它。
- Phase-2 对所有 create 和 update,确保操作后必填字段仍存在且数据有效。
- Phase-2 加入认证检查、基于角色或所有权的授权逻辑、UID 保护(防止用户更改数据所有权)。
- Phase-2 初始规则必须限制访问:除非用户明确要求未认证数据,否则任何数据访问都要求认证;第一版 firestore.rules 绝不能出现 allow read: true。
- Phase-2 在规则文件开头用注释块记录假设的数据模型:集合名、文档 ID 模式、每个字段的类型(必填/可选、约束)、是否不可变。
- Phase-2 编写完整辅助函数避免重复:isAuthenticated、isOwner(userId)、isDocOwner、uidUnchanged、uidNotModified、hasRequiredFields、validStringLength、isValidUrl、isValidEmail、isValidDateString、isScopedPath、isPositive、isValidList、isValidOptionalString、isValidMap、hasOnlyAllowedFields、areImmutableFieldsUnchanged、isRecent 等。
- Phase-2 用户数据分离(No Mixed Content):若集合含任何 PII(email、phone、address、私有设置),读权限必须严格限制为文档所有者(allow read: if isOwner(userId))。
- Phase-2 若需要公开资料,优先反规范化:把公开信息(name、photoURL)复制到用户创建的资源上(如 posts 文档中存 authorName、authorPhoto);或拆分集合:users_public 只放非敏感数据,users_private 锁定敏感数据。
- Phase-2 绝不写允许所有者以外任何人读取含 PII 文档的规则。
- Phase-2 RBAC:绝不允许用户创建自己的特权角色(除非已是引导管理员);绝不允许用户更新自己的角色或权限;绝不允许用户授予自己访问他人数据的权限;绝不允许绕过角色层级;始终校验用户有权执行所请求操作、未尝试提权、未尝试访问无权数据。
- Phase-2 优先使用 read 而非 list 或 get,以减少规则复杂度。
- Phase-2 日期校验:优先使用 timestamp 类型(Firestore 自动保证逻辑有效);若用字符串日期,isValidDateString 只校验格式不校验逻辑(会接受 2 月 31 日);正则中表示数字必须用双反斜杠(如 \\d),单反斜杠是常见 bug。
- Phase-2 不可变字段:createdAt、authorUID 等创建后不应改变的字段必须在 update 规则中显式保护(如 request.resource.data.createdAt == resource.data.createdAt);允许非所有者更新特定字段(如计数器)时,必须显式验证所有其他字段(authorName、tags、body)保持不变。
- Phase-2 身份完整性:存储反规范化的用户身份(authorName、authorPhoto)时必须校验;优先检查 request.resource.data.authorName == request.auth.token.name;若 token 不可用,必须严格校验类型(string)和长度(如 < 50 字符);最安全模式是只存 authorUid 并在客户端拉取资料。
- Phase-2 强制严格 schema(禁止多余字段):文档不得包含数据模型之外显式定义的任何字段。
- Phase-2 绝不允许 PII 泄露:即使已登录用户也不应读取其他用户信息;若 users 集合含 email 或其他私有数据,严禁生成 allow read: if isAuthenticated();。
- Phase-2 双重检查仅用 isAuthenticated() 保护的路径是否还需要基于角色或其他条件的额外检查。
- Phase-2 避免 Ownership-Only Update 陷阱:仅基于所有权允许更新(如 allow update: if isOwner(resource.data.uid);)会让所有者破坏 schema、删除必填字段或注入恶意载荷;必须把所有权检查与数据校验结合(allow update: if isOwner(...) && isValidEntity(...);),并验证无法自我提权。
- Phase-2 深度数组检查:仅检查字段 is list 不够,必须校验数组内容(如 tags 数组每项是 string 且长度合理,如 < 20 字符)。
- Phase-2 权限字段锁定:控制访问的字段(editors、viewers、roles、role、ownerId)对非所有者编辑者必须不可变;在 update 规则中对这些字段使用 areImmutableFieldsUnchanged(),除非 request.auth.uid 匹配文档原所有者/创建者。
- Phase-2 业务逻辑校验:用枚举校验字段值(如 status in ['pending','in-progress','completed'])。
- Phase-2 状态转换校验:update 时校验字段从合法旧状态变为合法新状态(如只有 in-progress 才能变 completed),防止绕过工作流。
- Phase-2 路径与关系作用域:对引用其他资源的字段(如图片路径或父文档 ID),必须确保其正确限定到用户或上下文(如 isScopedPath(request.resource.data.imageBucket))。
- Phase-2 安全计数器更新:只允许计数器字段变化(affectedKeys().hasOnly(['voteCount']))、变化量恰为 +1 或 -1、无其他字段被修改;防止用户重复刷计数:不能只依赖 !exists(likeDoc),必须用 getAfter() 验证对应跟踪文档在批次完成后将存在(递增时之前不存在、之后存在;递减时之前存在、之后不存在)。
- Phase-2 对每个集合实现显式数据校验:类型检查(field is string/number/bool/timestamp)、hasRequiredFields()、对每个 string/list/map 字段强制现实的大小限制(如 text.size() < 1000、tags.size() < 20,未限制单个字符串字段会导致 1MB 攻击)、URL 字段用 isValidUrl()、email 字段用 isValidEmail()、不可变字段保护、create 用 uidUnchanged() 且 update 用 uidNotModified() 并配合 isDocOwner()、时间戳用 isRecent()、数字用 isPositive() 等范围校验、存储路径用 isScopedPath()。
- Phase-2 用注释清晰组织规则并解释每条规则的目的。
- Phase-3 魔鬼代言人攻击:系统性地用以下攻击向量尝试攻破自己的规则,并记录每次尝试的结果:未认证的公开 list 查询能否取到本应私有的文档;能否 get/create/update/delete 不属于自己或无权限的文档;Update Bypass(先 create 合法文档再 update 成 1MB 字符串或非法字段);创建时所有权劫持(把 authorUID/ownerId 设为他人);更新时所有权劫持;修改不可变字段(createdAt 等);类型混淆(把 number 写入应为 string 的字段等);Create vs Update 校验绕过(先 create 合法再 update 成非法状态);资源耗尽/DoS(向任意字符串字段写 1MB 或超大数组,每个字符串字段必须有 .size() 检查);省略必填字段;权限提升(给自己写 isAdmin: true);Schema 污染(添加未定义字段如 extraData);非法状态转换(pending 直接到 completed);路径穿越/作用域攻击(把 imageBucket 指向他人数据);时间戳操纵(把 createdAt 设为过去或未来);负值/溢出(price 或 quantity 设为负数或极大值);Mixed Content 泄露(用户 B 能否读用户 A 的 users 文档且其中含 email 或私钥);计数器/动作重放(不创建跟踪文档就递增 likesCount,或递增两次);孤儿子集合访问(父文档不存在时读写 users/123/posts/456);查询不匹配(规则是否允许应用实际执行的查询,如应用按 status == 'published' 过滤时规则是否只在该条件下允许 list);Validator Pattern 检查(所有 update 规则包括仅所有者的是否都调用 isValidX(),若某条 allow update 只检查 isOwner() 则是严重漏洞)。
- Phase-3 若任一攻击成功:修复安全漏洞、重新生成规则、重复 Phase-3 直到没有攻击成功。
- Phase-4 语法校验:用 firebase deploy --only firestore:rules --dry-run 或类似工具校验规则语法,重复直到通过。
- 所有阶段完成后创建或更新 firestore.rules 文件。
- 约束:绝不跳过魔鬼代言人阶段;必须包含辅助函数和领域校验器;必须在规则文件开头记录假设的数据模型;输出前必须校验语法;提供完整可运行代码,无占位符或 TODO;记录所有关于数据结构或访问模式的假设;任何规则修改后都要重跑魔鬼代言人攻击;出现 permission denied 错误后判断规则是否需要更新。
- 对生成的规则不要过度自信地保证安全:初始生成后应描述为可靠原型,并告知用户在大规模发布前应与你一起加固和验证规则文件,用户应仔细审查规则以确保安全。
适用边界 · 注意事项
- 不用于安全规则审计(应使用 firebase-security-rules-auditor)
- 不用于数据库 provisioning
- 不用于客户端 SDK 查询