完美像素端点补齐保留审计字段剥离
POST /api/editor/images/pixel-art-snaps 自新增之日起未调用 sanitize_editor_client_generation_inputs,只对 generationInputs 做了可序列化性 校验便原样写入 editor_project_resource 与 editor_asset。登录用户因此可以自行 声明 screenColorHex / mattingProvider / mattingModel,让后台 raw mapper 看到 伪造的处理元数据。 该 sanitizer 与其余 13 个生产调用点(普通图片、角色、图标图集、UI 设计、快速 编辑、抠图、上传,含 external_editor_api)在此端点加入前就已存在,属于新端点 漏配既有约定,不是设计取舍。 这三个字段是服务端产出的处理事实:screenColorHex 由背景色决策写入, mattingProvider / mattingModel 由 apply_editor_matting_metadata_to_generation_inputs 在 bgfilter 实际执行后写入。完美像素是纯几何规整、不抠图,任何 matting 元数据 出现在这类记录上本身就是伪造。 调用位置与其余入口一致:解析 payload 之后、任何 IO 之前。 影响边界:只能污染攻击者自己的记录(owner_user_id 取自 access token,不可控), 不构成越权、信息泄露或计费漏洞,该端点 generation_cost_mud_points = 0。 sanitizer 单元测试已存在;端点接线由既有顺序断言钉住,sanitize 必须排在预算派生 及之后全部 IO 之前,被挪到 IO 之后会直接失败。 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -5925,3 +5925,11 @@
|
||||
- 上述 prompt 列的写入规则全部是既有行为,与 `web/master` 逐行一致,本次未改动一行。但该列同时被用户侧素材库搜索(`buildAssetSearchValues` 把 `asset.prompt` 计入匹配项)和后台素材查询页读取,而它当前混着三种语义:用户输入、工程化提示词、以及派生步骤描述。由此带来的模板噪声污染搜索(角色模板含「绿幕」「纯色背景」等词)、派生产物按源提示词搜不到(透明图的 prompt 列是 `"去除纯色背景"`)等问题均为存量,需单独立项与原设计者对齐后再动,不在本次范围内。
|
||||
- 未覆盖:图标图集尚未约束各素材共用同一像素块大小(`estimate_step_size` 取全图相邻峰间距的第 30 百分位,块大小不一时步长估计会偏);角色形象提示词里既有的「严格基于图1的角色美术视觉规范的美术风格」与像素约束存在潜在冲突,未改写。两项都等实测。snapper 当前无任何日志,`resolve_step_sizes` 走检测还是走统一网格兜底在外部不可观测,注入效果暂时只能靠人工看图判断。
|
||||
- 关联文档:`docs/technical/【前端架构】图片画布编辑器MVP接入方案-2026-06-11.md`、`docs/【编辑器】画板角色形象生成入口设计-2026-06-15.md`、`docs/【编辑器】画板图标素材生成入口设计-2026-06-15.md`、`docs/openapi/genarrative-external-v1.openapi.json`。
|
||||
|
||||
## 2026-08-01 完美像素端点补齐保留审计字段剥离
|
||||
|
||||
- 缺陷:`POST /api/editor/images/pixel-art-snaps` 自新增之日起未调用 `sanitize_editor_client_generation_inputs`,只对 `generationInputs` 做了可序列化性校验(`serialize_editor_asset_metadata`)便原样写入 `editor_project_resource` 与 `editor_asset`。登录用户因此可以自行声明 `screenColorHex / mattingProvider / mattingModel`,让后台 raw mapper 看到伪造的处理元数据。该 sanitizer 与其余 13 个生产调用点(普通图片、角色、图标图集、UI 设计、快速编辑、抠图、上传等,含 `external_editor_api`)在此端点加入前就已存在,属于新端点漏配既有约定,不是设计取舍。
|
||||
- 决策:在 handler 解析 payload 之后、任何 IO 之前调用同一个 sanitizer,位置与其余入口一致。这三个字段是服务端产出的处理事实——`screenColorHex` 由背景色决策写入,`mattingProvider / mattingModel` 由 `apply_editor_matting_metadata_to_generation_inputs` 在 bgfilter 实际执行后写入——一律不接受客户端声明。完美像素是纯几何规整、不抠图(`model = "Perfect Pixel"`、`provider = "Genarrative"`),任何 matting 元数据出现在这类记录上本身就是伪造。
|
||||
- 影响边界:只能污染攻击者自己的记录(`owner_user_id` 取自 access token,不可控),不构成越权、信息泄露或计费漏洞;该端点 `generation_cost_mud_points = 0`。危害限于按这些字段做的后台统计、排障与审计出现假数据。
|
||||
- 验证:sanitizer 单元测试 `editor_client_generation_inputs_cannot_forge_internal_audit_fields` 已覆盖字段剥离与其余字段保留;端点接线由 `explicit_pixel_art_snap_is_inline_strict_and_persists_only_after_processing` 的顺序断言钉住,`sanitize_editor_client_generation_inputs` 必须排在 `resolve_editor_pixel_art_processing_deadline` 及之后全部 IO 之前,被挪到 IO 之后会直接失败。
|
||||
- 关联文档:`docs/technical/【前端架构】图片画布编辑器MVP接入方案-2026-06-11.md`。
|
||||
|
||||
@@ -123,7 +123,7 @@
|
||||
- `POST /api/editor/images/background-removals`:接收当前图片的 `objectKey`、`resourceId` 或 `assetId` 候选引用,登录态和稳定引用入口校验通过后创建外部生成任务,响应只返回 `queueState`。父 `external-generation-worker` 负责把候选引用解析为已登记、已校验当前账号归属的私有 OSS object key,只向唯一 `bgfilter-worker` 发起一次内部 HTTP RPC,传递 object key、`maxQueueWaitMs`、公式化 `callBudgetMs` 以及固定的 `background_mode=complex + seg_model=birefnet + cross_check=off`;父侧不下载原图、不签发 URL,也不发送 `file` 或 `screen_color`。子 worker 在每次真实 provider attempt 前签发 600 秒 OSS URL,以默认 `Q=2048` admission 保险丝和 provider 并发 `N=16` 限流,取得 provider permit 后才启动 `callBudgetMs`,并对同一次逻辑调用最多执行两次顺序 provider attempt;成功图片以内部 HTTP 二进制 body 返回父流程,父侧不重试已被 worker 接收的内部 RPC(连接从未建立时按调度方案 §5.1 有界重连)。complex 任意最终失败都直接使父任务失败,不进入阿里云或本地键色 fallback。请求可携带 `projectId`、`targetLayerId`、`assetFolderId`、`assetLabel`、`sourceResourceId` 和 `canvasCompletion`;成功后仍由父流程完成最终 OSS / project resource 持久化,有 `canvasCompletion` 时按生成占位写入结果图层,否则沿用旧的目标图层替换路径。provider 令牌只在子 worker 服务端通过 `GENARRATIVE_EDITOR_BGFILTER_TOKEN` 注入,未配置时兼容回退旧 `GENARRATIVE_EDITOR_BACKGROUND_REMOVAL_TOKEN`;父子内部调用另使用独立内部 Token。
|
||||
- `POST /api/editor/icon-spritesheets/generations`:按图标规范图和完整用户需求生成 spritesheet;为兼容现有契约,画布前端把完整文本作为 `iconDescriptions` 的唯一数组元素提交,不按分隔符或语义枚举解析数量。api-server 先保存带纯色背景 spritesheet 源图,透明处理成功后再保存透明 spritesheet,并与手动 `POST /api/editor/icon-spritesheets/slices` 复用同一套全连通域识别:识别多少个有效素材就拆多少个,按视觉阅读顺序命名为 `素材 N`,不读取 `iconDescriptions` 数量决定切片数。两条拆分路径共同限制单边 `4096`、总像素 `2048×2048`、最多 `64` 个切片。切片只在有界管线中按需编码,共享单个 HTTP client 并以最多 `2` 路并发执行 OSS `PUT + HEAD`;client 的连接与单请求超时分别固定为 `10s / 60s`,手动入口在下载最大 `32 MiB` 来源对象前取得 memory admission,上传收齐后立即释放整图 admission,不跨数据库等待持有。所有对象验证通过后,由单个受 runtime service identity 保护的 SpacetimeDB procedure 在一次事务中批量确认 `asset_object`、创建 project resource / account asset 并写入 cohort 完成事实,不得逐片发起三组 procedure 或在部分素材落库后伪造完整批次。resource / asset ID 由 owner、task 与切片序号稳定派生;同一批次不确定结果后重放只能复用内容完全一致的素材,冲突内容必须拒绝,来源资源还必须存在且与派生资源属于同一 owner / project。请求支持 `model`、`screenColor`、`segModel`、`aspectRatio`、`imageSize`、`priceMudPoints`、`projectId`、`assetFolderId` 和 `generationInputs`;`priceMudPoints` 必须来自编辑器生成计费配置中对应生图模型的尺寸档位(如 `nanobanana2` 的 `0.5K / 1K / 2K` 或 `gpt-image-2` 的 `1K / 2K`),后端用 `editor_generation_config` 校验后才调用上游;`nanobanana2` 走原生 `generateContent` 并写入 `generationConfig.imageConfig.aspectRatio/imageSize`,`0.5K` 传 `"512"`;`gpt-image-2` 走 `/v1/images/edits`。透明处理最终失败时只保存并返回原图主结果,不生成透明图或切片;透明图成功但自动拆分失败时保留整张透明图并返回非阻断 `sliceWarning`,手动拆分失败时返回接口错误。响应只返回实际产物对应的 project / resource / asset 快照及可选通用 `warning`。
|
||||
- `POST /api/editor/images/generations` 与 `POST /api/editor/icon-spritesheets/generations` 还可携带可选 `style`;公开合法字符串为 `none / pixelArt`,兼容归一化、支持的 `kind`、非阻断告警和零新增持久化规则以“静态图片风格与像素规整边界”为准。`POST /api/editor/ui-designs/assets/extractions` 不接受该字段。
|
||||
- `POST /api/editor/images/pixel-art-snaps`:对已登记的静态图片执行免费的同步完美像素化。请求使用 `sourceImageSrc` 承载当前 owner 可读取的 `objectKey / resourceId / assetId` 候选稳定引用,`projectId / canvasCompletion` 必填且 `canvasCompletion.dialogId` 必须非空,`sourceResourceId / assetKind / generationInputs / assetFolderId / assetLabel` 可选;拒绝内联媒体、未登记对象和非静态栅格素材。源图已有正式 project resource 时前端应带上 `sourceResourceId`:该资源随 owner-scoped 项目读取一并鉴权,服务端可直接取用其 objectKey,省去按注册 ID 的全账号项目与素材库扫描;此时 `sourceImageSrc` 应传该 objectKey 或同一个 `resourceId`,两者指向不同图片会被直接拒绝。像素处理使用 strict 失败语义且不进入外部生成队列;成功时只持久化一张最终 PNG,并返回对应 project / resource / asset 快照。权威 `canvasCompletion.dialogId` 仍存在时在源图右侧完成占位;其删除已先持久化时只跳过画布回填,不删除已经成功写入的资源或账号素材。
|
||||
- `POST /api/editor/images/pixel-art-snaps`:对已登记的静态图片执行免费的同步完美像素化。请求使用 `sourceImageSrc` 承载当前 owner 可读取的 `objectKey / resourceId / assetId` 候选稳定引用,`projectId / canvasCompletion` 必填且 `canvasCompletion.dialogId` 必须非空,`sourceResourceId / assetKind / generationInputs / assetFolderId / assetLabel` 可选;拒绝内联媒体、未登记对象和非静态栅格素材。客户端提交的 `generationInputs` 必须与其余生成入口一样先经 `sanitize_editor_client_generation_inputs` 剥离 `screenColorHex / mattingProvider / mattingModel` 三个服务端保留审计字段,再进入任何 IO——这三项是背景色决策与 bgfilter 实际执行后由服务端写入的处理事实,不接受客户端声明;本端点是纯几何规整、不抠图,任何 matting 元数据出现在这类记录上本身就是伪造。源图已有正式 project resource 时前端应带上 `sourceResourceId`:该资源随 owner-scoped 项目读取一并鉴权,服务端可直接取用其 objectKey,省去按注册 ID 的全账号项目与素材库扫描;此时 `sourceImageSrc` 应传该 objectKey 或同一个 `resourceId`,两者指向不同图片会被直接拒绝。像素处理使用 strict 失败语义且不进入外部生成队列;成功时只持久化一张最终 PNG,并返回对应 project / resource / asset 快照。权威 `canvasCompletion.dialogId` 仍存在时在源图右侧完成占位;其删除已先持久化时只跳过画布回填,不删除已经成功写入的资源或账号素材。
|
||||
- `POST /api/editor/ui-designs/assets/extractions`:前端把红色框选轮廓绘入本地临时图后,先将该图上传 OSS 并确认 asset object,再以返回的 `objectKey` 作为参考图入队;Data URL / Blob URL 只允许停留在上传前的浏览器临时态。接口固定 `gpt-image-2` 和自动决策纯色背景素材提取提示词生成素材 spritesheet;api-server 先保存带纯色背景 spritesheet 源图,透明处理成功后再保存透明 spritesheet 并按连通域尝试拆分为 `素材 1..N`,返回结构复用图标 spritesheet 响应。请求必须携带 `screenColor`、`segModel`、`aspectRatio: "1:1"`、`imageSize: "1K" | "2K"` 和 `priceMudPoints`;框选数量不超过 6 个时前端按 `1:1·1K` 与 gpt-image-2 1K 价格提交,超过 6 个时按 `1:1·2K` 与 2K 价格提交。后端必须在调用上游前校验比例、尺寸和泥点价格,只允许 `1:1 / 1K / 2K`。透明处理最终失败时只保存并返回原图主结果,不生成透明图或切片;透明图成功但拆分失败时保留整张透明图并返回 `sliceWarning`。请求可携带 `projectId`、`assetFolderId`、`generationInputs` 和 `spritesheetLabel`,响应只返回实际产物对应的 project / resource / asset 快照及可选通用 `warning`;前端按后端快照落画布,不补造缺失产物。
|
||||
- 图片生成请求边界:角色生成、图标 spritesheet 和 UI 素材提取的同源画布 request DTO 保留 `segModel`,前端不提供选择控件而是自动提交默认 `birefnet`;api-server 继续校验并在字段缺失时回落默认值。`background_mode`、`cross_check` 与角色动作逐帧去背的 `seg_model` 不属于前端请求字段,只在 api-server 到 loopback worker 的内部 RPC 中传递。请求中的 `segModel` 不进入用户可见 `generationInputs` 或任何普通用户响应。
|
||||
- `POST /api/editor/images/edits`:按提示词和当前图片的已登记 `objectKey` / `resourceId` 修改图片,返回新的生成图片元数据;图片快速编辑当前只提交 `sourceImageSrc`,不提交隐藏的 `referenceImageSrcs`,并随用户当前选择提交 `model / aspectRatio / imageSize / size`。api-server 必须先归一模型再选择 VectorEngine 协议:`nanobanana2` 调用 `/v1beta/models/{model}:generateContent` 并把原图作为 `inline_data`、比例和清晰度写入 `generationConfig.imageConfig`;`gpt-image-2` 调用 `/v1/images/edits` multipart。gpt-image-2 路径在 provider 边界把目标尺寸和所有 multipart 参考图临时补齐到 16 的倍数;nanobanana2 路径保留 provider 的比例 / 清晰度请求,但两条路径回图后都以统一业务目标尺寸尝试归一。只允许缩小和轻微裁切;回图任意一边小于目标或比例偏差过大时保留 provider 实际回图及尺寸,并返回通用 `warning`,不得放大伪造所选档位。无论是否发生尺寸恢复都只创建一个 project resource / 账号素材,不显示重复“原始输出”。provider 对齐尺寸或原生 K 档像素不得泄漏到正常完成的最终响应、资源或图层 Resolution;变换失败降级时以实际 provider 原图尺寸为准。本地红框标记图必须先上传再提交 objectKey;请求携带 project / asset 上下文时由后端创建新 resource / asset,前端只消费响应快照。
|
||||
|
||||
@@ -4724,7 +4724,14 @@ pub async fn snap_editor_image_to_pixel_art(
|
||||
Extension(authenticated): Extension<AuthenticatedAccessToken>,
|
||||
payload: Result<Json<EditorPixelArtSnapRequest>, JsonRejection>,
|
||||
) -> Result<Json<Value>, AppError> {
|
||||
let Json(payload) = parse_editor_generation_json_payload(payload)?;
|
||||
let Json(mut payload) = parse_editor_generation_json_payload(payload)?;
|
||||
// 中文注释:`screenColorHex / mattingProvider / mattingModel` 是服务端产出的处理事实
|
||||
// (背景色决策与 bgfilter 实际执行后写入),不接受客户端声明,否则用户可以给自己的记录
|
||||
// 伪造抠图模型等审计字段,污染后台按这些字段做的统计与排障。本端点是纯几何规整、不抠图,
|
||||
// 任何 matting 元数据出现在这里本身就是伪造。与其余生成入口共用同一个 sanitizer,位置也
|
||||
// 保持一致:在任何 IO 之前。
|
||||
payload.generation_inputs =
|
||||
sanitize_editor_client_generation_inputs(payload.generation_inputs.take());
|
||||
let started_at = Instant::now();
|
||||
// 中文注释:处理预算必须从进入 handler 起算,而不是等下载完成后才起算。inline HTTP
|
||||
// 请求的 RequestContext 默认没有 external_call_deadline(只有队列 worker 会设),
|
||||
@@ -12326,6 +12333,10 @@ mod tests {
|
||||
"pub async fn snap_editor_image_to_pixel_art(",
|
||||
"async fn validate_editor_background_removal_source",
|
||||
&[
|
||||
// 中文注释:保留审计字段的剥离必须排在最前——它和其余生成入口共用同一个
|
||||
// sanitizer,漏掉这一步用户就能给自己的记录伪造 mattingModel 等处理事实。
|
||||
// 本端点是纯几何规整、不抠图,任何 matting 元数据出现在这里都是伪造。
|
||||
"sanitize_editor_client_generation_inputs",
|
||||
// 中文注释:处理预算在任何 IO 之前派生,下载与规整共用同一份绝对 deadline;
|
||||
// 一旦预算改回下载之后起算,下面的顺序断言会先失败。
|
||||
"resolve_editor_pixel_art_processing_deadline",
|
||||
|
||||
Reference in New Issue
Block a user