尧图网络科技YAOTU DIGITAL 获取报价
获取报价
首页 / 资讯中心 / 文章详情

Civitai App Blocks 暗启动与 GA 收尾实战:zip-bomb 上限、submit-version CSRF 与 per-user Buzz 上限的架构化落地

发布时间:2026/9/18 3:20:29

资讯中心
01
ARTICLE

Civitai App Blocks 暗启动与 GA 收尾实战:zip-bomb 上限、submit-version CSRF 与 per-user Buzz 上限的架构化落地

Civitai App Blocks 暗启动与 GA 收尾实战:zip-bomb 上限、submit-version CSRF 与 per-user Buzz 上限的架构化落地
Civitai App Blocks 暗启动与 GA 收尾实战zip-bomb 上限、submit-version CSRF 与 per-user Buzz 上限的架构化落地【免费下载链接】civitaiA repository of models, textual inversions, and more项目地址: https://gitcode.com/GitHub_Trending/ci/civitaiApp Blocks产品名Civitai Apps代码侧仍沿用block/app_block技术词汇是 civitai 仓库中一套第三方 iframe 应用系统block 以托管 iframe 形态渲染在模型页插槽与全页应用路由上通过绑定到单次安装的短时 RS256 JWT 鉴权。本文以交接文档 docs/features/app-blocks-ga-handoff.md 为主体结合审计文档 docs/features/app-blocks-merge-audit-2026-06.md 与仓库源码完整拆解这套暗启动dark launch→ GA 阻断项清零 → 上线决策的工程过程。读完本文你将掌握一次第三方代码托管系统上线前安全 GA 阻断项zip-bomb 上限、CSRF、每用户每日 Buzz 支出上限如何从威胁建模走到原子化实现以及多标志位Flipt门控、无信任推送no-trust-on-push等关键不变量如何落进生产代码。背景基础已合入、全线暗启动截至 2026-06-14 交接时刻App Blocks v1 基础已合入main并在生产环境全量部署处于完全暗启动状态feature 关闭仅审核员可见。2026-06-13 的 session 已清掉安全类 GA 阻断项2026-06-14 的 session 关闭了剩余三个代码类 GA 阻断项zip-bomb 上限#2528、submit-version CSRF#2529、per-user Buzz 上限#2530。已合入main的 PR 清单如下全部随release 5.0.1830发布PR内容#2319App Blocks v1 基础block-host 基座、CORS、JWT、publisher-install、model.sidebar_top插槽#2510build-callbackwebhookflag 门控 原子重放防护setNxKeepTtlWithEx#2517Showcase 图片按观看者浏览等级过滤匿名用户强制为publicPG等级#2519H2— 服务端/客户端 flag 门控上下文统一buildFliptContext#2521BLOCK_INIT载荷最小化为 allowlist移除viewer.status#2524git-push不再自动批准/部署 —— 改为审核员审核后触发#2528ZIP zip-bomb 上限—— 逐条目流式解压 累计总量上限#2529submit-version CSRF—— 共享同源 allowlist 应用于原始路由#2530per-user Buzz 上限—— 原子 reserve-and-refund 的每用户每日聚合#2532仅测试侧 typecheck 修复此前main上已存在的红态导致每个 PR 的 preview 失败三个 GA 阻断项 PR仅涉及代码与 Redis没有新增需要手工执行的 DB migration。Flag 状态与暗启动边界线上 Flipt 状态依据civitai/flipt-stateGitOps 源中civitai-app/default/features.yaml的配置app-blocks-enabled的结构是- key: app-blocks-enabled enabled: false # base OFF rollouts: - segment: { keys: [moderators] } # moderators segment: isModerator true即对所有人关闭仅当按用户求值且命中moderators分段时对审核员开启。审计文档明确记录了这一点并强调 H1 风险全局enabled: true会让所有人看到 App Blocks已被moderators分段 basefalse的组合规避。三段式 flag 模型源码级在 src/server/services/app-blocks-flag.ts 中App Blocks 由三个相互独立的 Flipt flag门控各自对应不同面app-blocks-enabled— 用户可见性basefalsemoderators分段仅在携带审核员上下文求值时返回true。管辖 UI 挂载、tRPC 门控、令牌签发mint、listForModel。app-blocks-pipeline-enabled— 构建/发布流水线对应开放决策 1机器 webhookbuild-callback、git-push、workflow-completed无用户上下文必须用全局 flag 求值。见isAppBlocksPipelineEnabled()。app-blocks-runtime-enabled— 运行时令牌验证对应开放决策 4JWKS 公钥端点与withBlockScope中间件验证已签发 block JWT 的面。它必须与流水线 flag 解耦——暂停构建不能杀死线上 block 的运行时令牌验证。该文件顶部有一段非常值得读的GLOBAL-EVAL SEMANTICS注释当无用户求值时Flipt 以entityIdglobal、空 context 命中 flag 的base 值不是分段必然不匹配所以关闭。也就是说flag 缺失或 Flipt 不可达 →isFlipt捕获异常返回false这是无条件的 fail-closed而分段不匹配是否等于关闭取决于 base 值——basetrue时全局求值就是true。 这解释了为什么isAppBlocksEnabled({ user })的 per-user 分支要把entityId设为用户 id、context 由共享的buildFliptContext(user)构建从而与客户端getFeatureFlags门保持同一形态、永不漂移也解释了为什么isAppBlocksAuthorEnabled的user参数是必填capability 没有主体就无法授权缺失主体是编译期类型错误而不是运行时静默false。关键不变量No-widening已验证用户面服务端门控通过buildFliptContext携带请求用户上下文求值只有服务端isModerator true才解析为 TRUE非审核员/匿名恒为 false求值缓存以身份为 key审核员的 TRUE 无法被重放给非审核员。机器/流水线门控留在全局求值上webhook、JWKS、withBlockScope它们没有用户上下文因此构建/发布流水线仍需全局开启——这是有意的等待专门的app-blocks-pipeline-enabledflag见开放决策。No-trust-on-pushgit-push永远不触发部署构建/部署只能由approveRequest审核员路径触发未审核的 push 进入pendingHTTP 202评审状态。Showcase 与BLOCK_INIT不再向第三方 iframe 泄露 NSFW 内容/提示词/PII/审核状态。GA 阻断项一ZIP zip-bomb 上限#2528威胁审计发现publish-request.service.ts原先对 ZIP 每个条目完整解压后才做大小检查jszip 的entry.async(nodebuffer)会先把解压后的全部字节物化到内存再跑 length 检查——一个膨胀到数 GiB 的单条目能在事后检查触发之前就打爆 Pod。数学上50 MiB 上传 × 2000 文件 × 10 MiB/文件 ≈ 20 GiB 的解压量一次上传即可导致 OOM。zip-slip 不可利用jszip 会规范化..文件进入 Forgejo content API。修复实现在 src/server/services/blocks/publish-request.service.ts 中extractBundleMetadata改为逐条目流式解压用entry.nodeStream读取任何时刻超过单文件上限10 MiB或运行累计上限MAX_TOTAL_DECOMPRESSED_BYTES 200 MiB就立即中止。驻留内存被约束在约一个单文件上限以内与压缩比无关。同一个 helper 同时守护 review-push 与 approve-fetch 循环。常量定义在 src/server/schema/blocks/publish-request.schema.tsexport const MAX_BUNDLE_SIZE_BYTES 50 * 1024 * 1024; // 50 MiB export const MAX_FILES_IN_BUNDLE 2000; export const MAX_FILE_SIZE_BYTES 10 * 1024 * 1024; // 10 MiB per file // Ceiling on TOTAL decompressed bytes across all entries in a bundle. // Defends against zip bombs: MAX_FILES_IN_BUNDLE * MAX_FILE_SIZE_BYTES is // ~20 GiB, far past what a pod can hold. 4x the 50 MiB upload cap is well // above any legitimate web bundles decompressed size yet bounds memory. export const MAX_TOTAL_DECOMPRESSED_BYTES 200 * 1024 * 1024; // 200 MiBremainingTotalBytes由调用方传入作为运行中的全局预算总量上限减去已消费字节从而让单文件超限与累计超限都走同一套流式中止路径。测试佐证src/server/services/blocks/tests/publish-request.service.test.ts 覆盖了extractBundleMetadata的行为正常 bundle 的文件清单与 manifest 解析、缺失block.manifest.json拒绝、空 bundle 拒绝、非法 JSON manifest 拒绝、sha256 确定性、路径排序以及单文件超过 10 MiB 上限即拒绝等用例。同类风险的后续项审计还点名了同类风险的一处src/server/services/wildcard-set-provisioning.service.ts:260对用户上传 ZIP 使用entry.async(uint8array)目前只靠声明未压缩大小的预求和上限兜底——而中央目录是攻击者可伪造的撒谎条目仍可 OOM。建议套用 #2528 的流式上限模式作为独立 feature 跟踪风险低于 App Blocks 本身。GA 阻断项二submit-version CSRF#2529威胁生产环境的会话 cookie 是sameSite:none见next-auth-options.ts而ModEndpoint原先不做 Origin/CSRF 检查Next.js 又能解析application/x-www-form-urlencoded于是攻击者可以构造一个跨站表单 POST借已登录审核员的 cookie 提交 bundle。这是全站ModEndpoint的既有姿态并非该路由独有只是当时被暗启动遮住了。修复实现submit-version是一个绕过 tRPC 管线的原始路由src/pages/api/blocks/submit-version.ts不会经过createContext的同源检查。修复方式是让该路由调用共享的isAllowedOriginRequest来自 src/server/utils/origin-helpers.ts 的共享同源 allowlistcreateContext与原始路由共用同一份if (isProd !isAllowedOriginRequest(req)) { res.status(403).json({ message: Cross-origin request blocked }); return; }!isProd豁免是为了保住本地开发与测试它们不发送 Origin与createContext行为一致。该路由随后执行两层门控isAppBlocksEnabled({ user })H2携带已认证审核员的上下文求值使moderators分段解析为 ON镜像enforceAppBlocksFlag以及 bundle 存储配置检查BUNDLE_S3_ENDPOINT/BUNDLE_S3_BUCKET缺失时返回 412。为什么单独建一条 72mb 路由bundle 是 base64 编码的 ZIP50 MiB 上限 → 约 67 MiB 编码后超过共享 tRPC body 限制。与其把全站唯一的/api/trpc/[trpc]路由上限从 17mb 提到 72mb等于为所有 tRPC 调用放开限制不如让上传走这条独立路由把 72 MiB 的 body 上限隔离在唯一需要它的端点上。这是审计文档中一条已 RESOLVED 的 MEDIUM 的落地形态。剩余同类风险跟踪项#2529 是刻意按路由收窄的。其他绕过createContext同源检查的 cookie 认证 POST/PUT 路由仍然存在包括HTTP 方法已验证mod/set-image-nsfw-level、mod/csam-upload、mod/clavata-image-process、mod/scanner-policies/export-dataset、mod/new-order/rate-limit-configPUT、admin/manage-sanity-checks、admin/temp/membership-buzz-backfill。文档给出的一处式修复方案在endpoint-helpers.ts的ModEndpoint/AuthedEndpoint里统一加if (isProd !isAllowedOriginRequest(req)) return 403并为 bearer/API-key 调用方提供镜像createContext的豁免。带副作用的 GET 路由如auth/impersonate是另一类问题不归 Origin allowlist 管。GA 阻断项三per-user Buzz 上限#2530威胁与目标语义原先的每日上限BLOCK_BUZZ_CAP_PER_DAY 50_000是按(user, app_block, day)维度计数的——一个用户装 N 个 block总支出可以放大 N 倍N-blocks × cap 乘法问题且旧的先读后记read→record模式在 Redis 抖动时会少计TOCTOU / under-count属于 fail-open。修复实现原子 reserve-and-refund新的上限是per-USER-per-day 聚合语义上不再区分user, app_block入口处用原子INCRBY预留reserve超上限或预解析抛错时用DECRBY退款refund由此同时关闭N-block 乘法放大与旧 read→record 的 TOCTOU/少计两个洞Redis 丢失时 fail closed拒绝支出丢失退款只会多计更严格安全退款钉死在预留时使用的 key上避免 UTC 午夜翻转时 DECRBY 误减第二天的 key。相关实现分散在 src/server/services/blocks/buzz-attribution.service.ts、blocks.router.tsBLOCK_BUZZ_CAP_PER_DAY、reserveBlockBuzzSpend/refundBlockBuzzSpend中。审计文档docs/features/app-blocks-merge-audit-2026-06.md确认该上限是spend 侧的 GA 前置条件在费率卡rate-card签署 internalAppOwnerUserIds填充之前不得接线 payout——mintPayoutForOwner是一个有意的桩保持惰性。当前appOwnerShareCents恒为 0因此reserveAppBountyAccrual会短路见app-bounty-cap.service.ts与app-cap-limits.constants.tsper-user cap 50_000 Buzz/天 ≈ $50/天placeholderspendSharePct 5%下单个满额用户最多向某 app 累积约 $2.50/天。非阻断 LOW超限错误的already spent数值在并发下可能瞬时高估仅展示层影响decrBy缺少incrBy那样的模板化 key 类型外观问题。开放决策GA 前后需要产品/工程拍板流水线门控为机器端点webhook/JWKS建专门的全局app-blocks-pipeline-enabledflag还是全局打开用户 flag内部 webhook 无法求值 mod 分段的用户 flag无用户上下文所以在二者之一落定前流水线保持暗态。git-push更新路径是把submitVersionZIP 上传 → 审核批准 → 部署定为 block 更新的唯一正典路径git-push纯作评审触发还是构建approve-from-repo路径让git-push记录的 pending 行可部署现状是git-push记录的 pending 行无法被approveRequest批准它没有 MinIO ZIP bundle——它指向 Forgejo 仓库。两种方案在安全上都成立没有任何未审核内容会部署。BLOCK_INIT契约移除viewer.status与civitai/app-sdk/ blocks-react 负责人确认GA 前无外部 block现在移除是安全的。withBlockScope留在全局求值如果审核员对已安装 block 的狗粮测试需要 per-user 路径经由已验证 JWT 的 subject那是后续项。上线前检查清单翻转 flag 时在每个环境预检kill_per_model_installsmigrationjoin-count 必须为 0查询语句在审计文档中并确认所有 app-blocks migration 已应用。解决决策 1流水线门控与决策 2git-push 更新路径。关闭 MEDIUM GA 阻断项—— 已完成#2528/#2529/#2530。在费率卡签署前保持 payout 惰性。刻意设计 Flipt 灰度mod 分段 → 逐步放宽——记住全局enabled: true会让所有人暴露H1 风险用分段来放宽而不是改 base。下一 session 的工程方法含踩坑记录代码 GA 阻断队列已清空。剩余 GA 工作是开放决策流水线门控、git-push 更新路径、payout/费率卡签署与 flag 灰度本身——这些是产品/工程决策不是代码任务。收敛良好的工作模式派发一个isolation: worktree子代理实现PR 测试再派只读审计子代理检查risks/regressions/leaks/second-order迭代到审计收敛。务必针对修复后的状态跑第二轮审计——本 session 的第二轮就抓到一个修复引入的回归一个stream.destroy()polish 既过不了 CI typecheck 又不生效回退为pause()。直接核验子代理的说法——多处在第一轮就是错的一个碰巧工作的 flag 求值一次 Redis 返回值误读一次把 GET-only 路由当成 POST-CSRF 洞的审计一次审计引用了错误文件路径的真实发现。在 worktree 里跑测试ln -s local-path/workspace/civit/civitai/node_modules ./node_modules然后只跑单个目标文件。worktree 里全量tsc很吵无关文件里的陈旧 Prisma client——但可以这样验证单个文件npx tsc --noEmit -p tsconfig.json 21 | grep file陈旧 Prisma 错误都在其他文件里CI 会生成新 client。不要只信 vitest 绿——esbuild 会剥掉类型真实类型错误如方法不在声明接口上能过 vitest 却在 CI 的tsc挂掉。CI 才是权威。跨无关 PR 的 preview 失败先怀疑红态mainTektonpreview / deploy以 Type Check 为门main上任何一个既有的 typecheck 错误会让每个PR 的 preview 失败。gh pr checks pr→ 先看 Type Check别急着认定是自己改动的问题。不堆叠 PR见 CLAUDE.md每个 PR 直接基于main若改动依赖未合入的修复等它合入后再合入main或折叠进一个 PR。关键文件索引Flag 门控src/server/services/app-blocks-flag.ts三轴 flag 模型 GLOBAL-EVAL SEMANTICS、src/server/services/feature-flags.service.tsbuildFliptContext、blocks.router.ts/apps.router.tsenforceAppBlocksFlag。Showcasesrc/server/services/blocks/showcase.service.ts按浏览等级过滤nsfwLevel匿名强制 public 等级。Iframe 载荷src/components/AppBlocks/IframeHost.tsx、projectBlockInit.ts。发布/构建流水线src/pages/api/internal/blocks/git-push.ts、src/pages/api/internal/blocks/build-callback.ts、src/pages/api/internal/blocks/workflow-completed.ts以及src/server/services/blocks/下的publish-request、apps-pipeline、forgejo服务。Bundle 上传 CSRFsrc/pages/api/blocks/submit-version.ts72 MiB body 上限隔离 同源 403、src/server/utils/origin-helpers.ts共享同源 allowlistcreateContext与原始路由共用。Buzz/金钱src/server/services/blocks/buzz-attribution.service.ts、rate-card.ts、blocks.router.tsBLOCK_BUZZ_CAP_PER_DAY、reserveBlockBuzzSpend/refundBlockBuzzSpend、src/server/schema/blocks/publish-request.schema.tsbundle/文件/总量上限常量。架构正典docs/features/app-blocks.md概念、表结构、令牌/scope、BLOCK_INIT契约、block↔host 消息清单、发布生命周期、docs/features/app-blocks-merge-audit-2026-06.md规范审计 GA 阻断项清单建议先读。结语这套 GA 收尾的关键不在修三个 bug而在把三个威胁分别收敛到可论证的不变量上zip-bomb 用流式解压把驻留内存与压缩比解耦CSRF 用共享同源 allowlist 让原始路由与 tRPC 管线姿态一致Buzz 上限用原子 reserve-and-refund 把并发下的计数正确性交给 Redis 原语而不是业务逻辑。配合三轴 Flipt 门控与 no-trust-on-push 的发布生命周期App Blocks 得以在完全暗态下长期运行、逐段灰度而不把任何用户面暴露建立在碰巧没被攻击上。【免费下载链接】civitaiA repository of models, textual inversions, and more项目地址: https://gitcode.com/GitHub_Trending/ci/civitai创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
02
RELATED NEWS

相关资讯

更多网站建设与数字化升级内容

03
WHY YAOTU

想打造同款高转化官网?

懂行业、懂生意,从建站到增长一站式陪跑

场景化定制

不做模板站,围绕你的业务场景量身设计,小众不撞款。

营销型架构

以转化目标组织内容与路径,让官网真正带来询盘。

全周期服务

设计、开发、运营、运维一体,上线只是开始。

免费获取你的建站方案

留下需求,专属顾问 24 小时内为你输出方案建议。