ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

Civitai 绿域拍卖的 Green Buzz 支持:PR 审查视角下的跨域出价安全与数据一致性

Civitai 绿域拍卖的 Green Buzz 支持:PR 审查视角下的跨域出价安全与数据一致性 Civitai 绿域拍卖的 Green Buzz 支持PR 审查视角下的跨域出价安全与数据一致性【免费下载链接】civitaiA repository of models, textual inversions, and more项目地址: https://gitcode.com/GitHub_Trending/ci/civitai这篇 PR 审查笔记记录了 Civitai 为拍卖Auction功能增加绿域.red 域名Green Buzz 出价支持时的完整代码审查过程它梳理了客户端无法伪造账户类型的信任边界、绿域内容安全校验的前后端双重拦截以及两个真实的跨域数据一致性隐患——Bid/BidRecurring表在唯一约束中未区分 Buzz 类型导致的跨域出价合并与退款歧义以及孤儿orphaned绿色循环出价在定时任务中无限重试的问题。读完本文你将掌握在引入多域/多币种账户体系时如何审查唯一约束是否覆盖了业务维度这一类隐蔽但后果严重的数据模型缺陷以及如何用源码逐行验证安全结论。一、PR 背景与功能范围该 PR 的核心目标是让绿域用户可以消耗 Green Buzz 参与拍卖竞价并为绿域引入专门的安全检查。审查范围包括三个层面竞价服务层createBid等出价逻辑如何根据请求来源域名决定从哪个 Buzz 账户扣款循环出价任务层每日拍卖任务handle-auctionsjob如何为循环出价recurring bid重新扣款以及绿色循环出价的再校验逻辑UI 层环境切换environment-swap相关的界面改动包括绿域下 NSFW 出价按钮的禁用与错误提示。拍卖系统的基本模型是AuctionBase是长期存在的拍卖基项按日生成具体的Auction实例用户对某个实体entityId如模型版本出价产生Bid行若设置了recurringUntil则额外产生一条BidRecurring行由每日任务在次日新拍卖实例上自动复投。这一结构在 schema 定义 中可以直接确认。二、隐患一不同域名的出价被静默合并这是本次审查发现的两个核心问题中更严重的一个根源在于唯一约束没有把Buzz 账户类型纳入业务主键。2.1Bid表accountType不在唯一约束中PR 提交时的Bid表定义为unique([auctionId, userId, entityId])。当同一个用户在同一天对同一个实体分别从 .comYellow Buzz和 .redGreen Buzz出价时第二次出价的 upsert 会命中第一条记录并执行amount累加而不是新建一行——Buzz 交易本身确实从正确的账户类型扣款了但Bid行只留下一个混合总额无法区分其中多少来自 Yellow、多少来自 Green。后果直接而严重退款逻辑被破坏——如果这笔出价在拍卖结束后未中标需要退还系统无从得知应按什么比例退回 Yellow 与 Green 账户。唯一的逃生通道是transactionIds数组同时记录了两笔交易 ID理论上 Buzz 服务可以逐笔反向冲正但出价逻辑层并没有处理这种混合拆分的能力。修复方案文档给出的两条路径给Bid增加accountType列并纳入唯一约束使 .com 与 .red 的出价成为独立行若跨域对同一实体出价属于极少见场景则直接对第二次出价抛出错误拒绝避免静默合并。当前仓库状态方案 1 已经落地。迁移文件 显示了完整的三步操作-- AlterTable: Add accountType to Bid ALTER TABLE Bid ADD COLUMN accountType TEXT NOT NULL DEFAULT yellow; -- Update unique index on Bid to include accountType DROP INDEX Bid_auctionId_userId_entityId_key; CREATE UNIQUE INDEX Bid_auctionId_userId_entityId_accountType_key ON Bid(auctionId, userId, entityId, accountType);修复后的 schema 定义 中Bid为unique([auctionId, userId, entityId, accountType])。同时可以确认 createBid 实现 中查找既有出价的查询也已带上账户类型维度const auctionData await dbWrite.auction.findFirst({ where: { id: auctionId }, select: { ...auctionSelect, bids: { where: { userId, entityId, accountType: accountTypes[0] ?? yellow, }, // ... }, }, });这样 .com 与 .red 的出价各自独立成行退款时可按行精确回溯扣款账户。2.2BidRecurring表列存在但约束缺位BidRecurring的处境略有不同它已有accountType列但唯一约束仍是unique([auctionBaseId, userId, entityId])。这导致 upsert 按旧约束匹配时来自不同域名的第二次循环出价只会累加amount而不会更新accountType字段——循环出价永远停留在先创建者的账户类型上。后果是后续每日循环扣款会扣错 Buzz 账户类型如果首条是 Yellow 创建Green 的循环安全再校验见第三节将永远不会触发。修复方案把accountType加入唯一约束改为unique([auctionBaseId, userId, entityId, accountType])并同步更新 upsert 的where子句。文档特别指出循环出价任务本来就遍历所有行因此天然兼容同一用户/同一实体存在多条不同账户类型的循环出价这一新形态无需改动任务逻辑。当前仓库状态同样已经修复。add_account_type_to_bid 迁移 的后半段重建了BidRecurring的唯一索引DROP INDEX BidRecurring_auctionBaseId_userId_entityId_key; CREATE UNIQUE INDEX BidRecurring_auctionBaseId_userId_entityId_accountType_key ON BidRecurring(auctionBaseId, userId, entityId, accountType);且 createBid 中的循环出价 upsert 的where子句已匹配四元组await dbWrite.bidRecurring.upsert({ where: { auctionBaseId_userId_entityId_accountType: { auctionBaseId: auctionData.auctionBase.id, entityId, userId, accountType: accountTypes[0] ?? yellow, }, }, // ... });通用教训引入新业务维度这里是 Buzz 类型时必须逐张排查所有按用户实体唯一的表确认新维度是否应进入唯一约束。静默合并比报错更危险因为它不产生任何用户可见的失败信号只在退款、对账、安全校验等下游环节以诡异的形式暴露。三、隐患二孤儿绿色循环出价无限重试每日任务 handle-auctions.ts 中的createRecurringBids会捞出所有未暂停且在有效期内的循环出价逐条执行。对于绿色循环出价任务在扣款前会重新校验目标模型版本是否仍满足绿域安全要求。问题出在校验失败的分支当模型版本被删除时mv为null代码正确地跳过了本次扣款但既没有暂停也没有删除这条循环出价——于是每一天的任务运行都会再次跳过它每天产生一条日志无限循环下去。处理建议在模型版本找不到时自动暂停该循环出价isPaused: true一次性终止无意义的重试if (!mv) { await dbWrite.bidRecurring.update({ where: { id: recurringBid.id }, data: { isPaused: true }, }); log(Paused recurring bid ${recurringBid.id}: model version not found); continue; }对照当前 任务实现绿色再校验的完整判断为if (!mv || mv.model.nsfw || mv.model.poi || mv.model.minor)时跳过并记日志——跳过逻辑本身正确但对永久找不回的实体缺少终态处理。这是一个典型的定时任务健壮性问题任何跳过分支都应区分临时性失败明天可能恢复继续跳过即可与永久性失败应终止或转人工处理否则孤儿记录会永久占用任务循环。四、隐患三循环出价的再校验窄于首次出价校验首次createBid对AuctionType.Model类拍卖执行的校验链条相当完整见 auction.service.ts模型版本存在、availability非 Private、版本status为 Published所属模型status为 Published、meta.cannotPromote不为 true、非poi模型类型在拍卖允许的modelTypes内生态ecosystem/baseModel匹配绿域专属校验accountTypes包含green时若mv.model.nsfw || mv.model.poi || mv.model.minor则抛出Cannot bid on this content from this domain.对应代码。而循环出价的每日再校验handle-auctions.ts对绿色出价只检查了nsfw、poi、minor三项。缺失项包括cannotPromotemeta 标志模型status出价创建后可能被下架模型availability可能被设为 Private模型类型 / 生态匹配。此外文档指出Yellow 循环出价的再校验为零——完全不做任何检查。审查结论将其定为低优先级因为首次出价时所有规则都已被强制校验再校验的缺口只在出价创建之后模型属性发生变化时才有意义建议至少补上 Published 状态的重查。这一判断体现了务实的风险分级对循环任务而言最坏情况是多扣了一天的钱买一个已经不可推广的实体而非资金损失或安全违规——但 NSFW/poi/minor 三项作为绿域的安全底线被保留在每日校验中说明安全校验与商业校验在再校验场景下被区别对待这是合理的分层。五、审查中确认安全的五个结论Verified Safe一份有价值的 PR 审查不仅列出问题也要明确记录已验证无风险的结论避免后续审查者重复劳动。本文档的 Verified Safe 清单包含五项其中三项可以直接在源码中复核客户端无法操纵accountType——账户类型在服务端由getAllowedAccountTypesbuzz-helpers.ts根据请求上下文ctx.features派生不接受用户输入。这意味着出价请求中即便夹带账户类型字段也会被服务端重新推导覆盖跨域冒用账户在协议层面即被阻断。绿域出价正确扣取 Green Buzz——getAllowedAccountTypes对绿域返回[green]而 createBid 的扣款调用createMultiAccountBuzzTransaction以fromAccountTypes: accountTypes显式指定扣款账户类型扣款链路闭合。绿域 NSFW 出价被双向拦截——客户端层面是禁用的出价按钮加错误提示服务端层面是createBid中的绿色专属校验第四节所列即使绕过前端也无法成交。无 SQL 注入风险——所有数据库访问均通过参数化的 Prisma 查询。前端实现干净——null 检查完备、Buzz 类型展示正确、无状态管理问题。六、从审查笔记到已落地修复一个完整的审查闭环将文档结论与当前仓库状态对照可以看到这次 PR 审查形成了一个干净的闭环审查发现文档建议当前仓库状态证据Bid无accountType维度跨域出价合并加列并纳入唯一约束Bid 模型 已含accountType且约束为四元组BidRecurring约束缺accountType更新唯一约束与 upsert迁移 加列、后续迁移 重建索引upsert where 子句 已匹配四元组绿色再校验范围偏窄低优先级至少重查 Published当前实现仅重查 nsfw/poi/minor缺口仍存在孤儿循环出价无限重试找不到实体时自动暂停当前 任务代码 仍为跳过日志未自动暂停值得注意的是修复是分两个迁移阶段完成的先由 20260407 迁移 给BidRecurring单独加列再由此前 PR 中已存在的Bid.accountType列补齐——而最终让两个约束一致的是 20260410 迁移。这种先加列、后重建索引的渐进式迁移方式避免了在单条 DDL 中同时改动两张表索引带来的锁表风险也是大表 schema 演进的常见做法。从源码结构看这套设计的信任模型可以概括为三层信任边界在服务端accountType永远由服务端从域名上下文派生getAllowedAccountTypes→ctx.features客户端提交的内容只影响出价多少不影响用什么钱出扣款与记账分离但可对账createMultiAccountBuzzTransaction负责资金侧返回的transactionIds落进Bid.transactionIdsString[] 列即使账户维度历史上被合并资金流水仍可逐笔追溯——这正是文档中理论上 Buzz 服务可以逐笔反向冲正这一判断的依据安全校验前置且不可跳过绿域的 NSFW/poi/minor 校验同时存在于首次出价服务层抛错拒绝与每日循环任务扣款前再校验两个入口前端禁用按钮只是 UX 层面的第一道提示。七、可复用的审查方法总结这篇 PR 审查笔记的价值不仅在于 Civitai 拍卖本身更在于它示范了一套可迁移的多账户体系审查方法约束即业务语义逐一核对所有unique约束是否覆盖了全部业务维度。本次两个隐患本质是同一个根因——唯一约束漏掉accountType——分别表现为合并行Bid和保留旧值BidRecurring两种形态区分永久失败与临时失败定时任务的每个continue/跳过分支都应问一句这条记录明天还会成功吗不会成功的记录需要终态暂停、删除或告警首次校验与再校验的差集分析把两条校验路径并列成表缺失项一目了然并按最坏后果是资金损失还是合规风险定级安全类检查保留、商业类检查可放宽Verified Safe 清单同样是交付物把客户端不可伪造账户类型无注入风险这类否定性结论显式写出附上推导依据能显著降低后续审查与回归测试的成本。对于正在为现有系统引入多域名、多币种或多账户类型维度的开发者本文给出的检查顺序是先改数据模型约束再改所有 upsert 的where子句与查询过滤最后才谈 UI 与任务逻辑——顺序颠倒就会出现扣款正确但记账合并这类最难排查的中间状态。【免费下载链接】civitaiA repository of models, textual inversions, and more项目地址: https://gitcode.com/GitHub_Trending/ci/civitai创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表