先看结论与判断条件
- 先定义具体 AI 功能需要什么内容,再打开文件选择器;不能以未来可能使用为由收集整份附件和全部元数据。
- 文件正文、容器属性、EXIF、文件名、批注、隐藏页、嵌入对象和缩略图属于不同数据面,应逐项决定保留或删除。
- 预处理应产生新的最小化上传副本,原文件保持只读;副本、OCR、切片和预览必须绑定同一附件身份与删除策略。
- 用户确认需要展示发送内容、目的、接收方和保留边界,取消、切换账号或页面销毁后不得继续上传迟到任务。
- 访问令牌只解决调用授权,不能证明附件收集合理;scope、audience、重放防护和上传数据范围必须分别检查。
- 商店与隐私清单要覆盖 App 和第三方 SDK 的真实数据行为,但声明不能替代运行时最小化、日志脱敏和删除回执。
文件选择器打开前先写清上传目的
附件最小化的起点不是压缩图片,而是回答这个 AI 功能究竟需要什么。识别票据金额可能只需要用户框选的图像区域,文档问答可能只需要所选页面的纯文本,图像描述可能不需要原文件名、拍摄位置或设备信息。若产品团队无法写出目的、输入字段、接收方、处理阶段和删除条件,就没有依据判断哪些数据必须上传。此时继续收集整份文件,只会把未来可能使用变成无限范围。
目的应细化到可执行的数据清单。每个字段记录 required、optional 或 forbidden,注明它来自文件正文、容器元数据、操作系统属性还是预处理派生物。optional 字段不能默认勾选;forbidden 字段在选择后立即删除或不读取。清单还要写出 AI 服务、内容审核、对象存储、崩溃分析和客服系统是否会接触附件,不能只写一个笼统的云端处理。第三方 SDK 的读取也属于应用的数据责任。
需求变化必须产生新的清单版本和用户界面,而不是在服务器端悄悄扩展使用。旧客户端若不认识新用途,应保持原范围或拒绝上传。一次确认只覆盖当次说明的附件和处理目的,不能自动延伸到模型训练、质量分析或长期留存。若具体业务存在法定、合同或监管要求,仍需由项目合规人员确认;公开工程方法不能替代特定地区的法律结论。
| 决策项 | 需要记录 | 不合格表现 | 门禁动作 |
|---|---|---|---|
| 功能目的 | 单一可说明任务 | 未来可能使用 | 拒绝进入上传流程 |
| 必要内容 | 正文区域或页面范围 | 默认整份文件 | 要求缩小选择 |
| 元数据 | 逐字段保留理由 | 全部原样上传 | 按 allowlist 重建 |
| 接收方 | 服务与 SDK 清单 | 只写云端 | 补齐数据流图 |
| 保留期 | 到期事件与删除主体 | 永久或未定义 | 阻断发布 |
| 用户选择 | 当次明确确认 | 后台静默追加 | 取消任务并记录 |
把正文、容器和元数据拆成三个数据面
用户看到的一张图片或一个文档,内部可能包含远多于可见正文的信息。图片可能带有拍摄时间、位置、设备、方向和缩略图;文档可能包含作者、修订记录、批注、隐藏内容、嵌入对象、附件或自定义属性;文件系统还会提供原始文件名、路径、大小和修改时间。不能因为用户选择了文件,就推定其同意发送所有内部与旁路字段。最小化流程应先解析类型,再对每个数据面使用独立规则。
格式识别不能只依赖扩展名或调用方给出的 MIME。预处理器应读取公开格式签名并设置支持范围,不认识的容器直接拒绝或提示用户转换。复杂文档如果无法可靠删除隐藏内容,就提供页面导出、纯文本提取或用户预览,而不是声称已经清理。安全边界包括资源消耗限制、解析失败和嵌套对象数量,但公开示例不提供攻击链或恶意样本制作方法。
最小化后的上传对象最好是新生成的受控副本。副本只保留允许的可见内容与必要格式属性,使用随机内部标识替代原文件名,并绑定 manifestVersion、purposeId、accountId 和 attachmentId。原文件保持只读,不在失败后覆盖。副本摘要用于重试和删除关联,不用于宣称内容真实或完整;若业务需要原件证明,应由另一个明确流程处理,而不能让 AI 附件通道承担证据存档。
| 数据面 | 示例内容 | 默认策略 | 允许保留的条件 |
|---|---|---|---|
| 可见正文 | 选中区域或页面 | 按任务裁剪 | 直接服务当前目的 |
| 图片元数据 | 时间、位置、设备 | 删除 | 功能明确需要并已告知 |
| 文档属性 | 作者、修订、批注 | 删除或单独确认 | 用户明确选择 |
| 嵌入对象 | 附件、媒体、脚本 | 拒绝或剥离 | 受控解析且任务需要 |
| 文件系统属性 | 文件名、路径、时间 | 不上传 | 有限诊断标识 |
| 预览缩略图 | 本地显示副本 | 会话结束删除 | 仍在当前会话使用 |
预处理产生的每一份派生物都要登记
AI 附件通常会经过压缩、旋转、裁剪、OCR、分块、向量化、缩略图生成和格式转换。团队常只管理最终上传文件,却忽略处理中间目录、内存缓存、任务队列、数据库记录和失败快照。数据最小化清单必须把派生物视为附件的一部分,为每个对象记录来源、用途、存储位置类别、加密状态、保留条件和删除责任。没有来源 attachmentId 的派生物不能进入队列。
处理顺序影响暴露面。可以先在设备侧完成方向修正、用户裁剪和元数据删除,再生成上传副本;OCR 若可在端侧满足目的,则只发送必要文本和引用位置。是否使用端侧或云端取决于模型能力、设备、准确性与业务要求,本文不宣称某条路径始终更安全或更快。关键是每个决策都有清单版本、用户可见说明和失败边界。
中间对象不能用同一固定文件名在多个账号或任务间复用。目录与缓存键至少绑定账号域、attachmentId、generation 和处理阶段,异步任务在写入前再次检查当前 generation。用户重新选择文件、切换账号或撤销任务后,旧 generation 只能执行删除,不能把迟到 OCR 或压缩结果登记到新附件。进程退出后的恢复也要读取同一 manifest,而不是扫描目录猜测文件用途。
| 派生物 | 用途 | 身份绑定 | 删除触发 |
|---|---|---|---|
| 裁剪副本 | 限定可见区域 | attachmentId 与 cropVersion | 上传完成或取消 |
| 压缩图片 | 降低传输负载 | generation 与编码参数 | 任务终态 |
| OCR 文本 | 检索或提示输入 | 页面与文本摘要 | 会话或保留期结束 |
| 文档切片 | 分段处理 | 页码与 chunkId | 父附件删除 |
| 预览缩略图 | 用户确认 | 账号与会话 | 页面销毁 |
| 失败快照 | 可控诊断 | 错误类别与脱敏摘要 | 诊断窗口结束 |
用户确认要对应具体内容、接收方和时间
确认界面要让用户知道将发送哪一份内容,而不是只显示文件图标。图片应展示裁剪后的预览,文档应显示所选页面或抽取文本范围;元数据若保留,要列出类别和原因。界面同时说明接收的是本地组件还是远端 AI 服务,是否经过内容审核或第三方 SDK,以及预期保留边界。用户返回重新选择后,旧确认失效,不能沿用旧附件的 consentId。
取消语义要贯穿文件读取、预处理、授权、上传、服务端处理和结果回写。点击取消只改变按钮状态是不够的;客户端要停止新分片、撤销可撤销请求、标记服务端对象待删除,并阻止迟到结果进入会话。无法立即中止的阶段需明确说明最终删除回执,而不是把“已取消”解释成所有副本瞬间消失。网络断开或应用退到后台也不能自动变成永久重试。
批量附件需要逐项身份与选择。用户同意上传文档甲,不表示文档乙及其全部页面也被授权;删除其中一项后,提示词、OCR、向量和重试队列都要移除对应引用。产品若支持重新提交,应创建新的 generation 和 consentId,并复用最小化规则,而不是直接从旧临时文件重发。这样审计可以回答某个对象为什么存在、由谁确认和何时到期。
| 状态 | 允许动作 | 必须展示 | 离开状态的条件 |
|---|---|---|---|
| selected | 本地读取与预览 | 原文件与范围 | 完成最小化 |
| minimized | 检查派生物 | 实际发送副本 | 用户确认 |
| authorized | 申请限权调用 | 接收方与保留期 | 开始上传 |
| uploading | 发送已确认分片 | 进度与取消 | 成功、失败或取消 |
| processing | 等待服务结果 | 远端处理状态 | 结果或删除请求 |
| cancelled | 删除与回执 | 尚未删除的边界 | 全部对象终态 |
授权令牌与附件最小化是两道不同门禁
访问令牌说明客户端在某个授权上下文中可以调用接口,不说明把整份附件发送出去就是合理的。scope、audience、调用者身份、用户账号和附件 purposeId 应分别进入请求门禁。令牌权限过宽会扩大误用后果,附件范围过宽会扩大数据暴露;两者都通过才能发送。不要把长期 API Key 放在移动端,也不要把 token 文本写入附件 manifest、文件名或诊断日志。
RFC 9700 针对 OAuth 部署强调重定向、授权码注入、令牌重放和不安全授权流程等风险。附件通道可以据此检查授权交互、令牌受众和重放防护,但规范不决定 AI 服务的商业权限、数据保留或内容处理规则。客户端还需要业务层的 attachmentId、requestId 和一次性 generation,让网络重试只能引用同一已确认副本,不能重新读取更大范围的原文件。
失败重试必须保留数据边界。若服务器返回可重试错误,队列只能发送原 manifest 对应的最小化副本和字段摘要;manifestVersion、consentId、candidateDigest 或账号域变化时,任务作废并重新确认。刷新令牌不应顺便扩大附件 scope。服务端也要拒绝同 requestId 对应不同附件摘要,具体幂等和认证实现需要目标系统证据,公开文章不声称御盾中央平台或任何 AI 服务已经采用该设计。
| 检查面 | 关键字段 | 能证明 | 不能证明 |
|---|---|---|---|
| OAuth 授权 | scope、audience、subject | 调用资格受限 | 附件收集合理 |
| 附件目的 | purposeId、manifestVersion | 当前处理范围 | 用户已确认 |
| 用户选择 | consentId、generation | 当次副本获确认 | 长期留存合理 |
| 请求绑定 | requestId、附件摘要 | 重试引用同一对象 | 服务端已删除 |
| 账号域 | accountId、tenantId | 避免跨账号队列 | 数据用途合法 |
| 删除回执 | objectId、状态、时间 | 已执行声明动作 | 所有备份立即消失 |
保留期必须覆盖远端对象、重试副本和备份边界
保留期不能只写在隐私政策里。附件 manifest 要包含 retentionClass、expiresAt、deleteOnCompletion 和 legalHoldPolicy 等可执行字段,并指定客户端临时文件、上传分片、对象存储、OCR 文本、向量、结果缓存和诊断材料各自由谁删除。永久、默认长期或空值都应被门禁拒绝;若法规或合同要求特殊保留,应进入单独授权流程,而不是复用普通 AI 会话设置。
删除是传播任务,不是单一文件操作。父附件进入 deleted 后,派生图中的缩略图、文本、切片、向量、缓存、重试队列和引用记录都要达到终态;正在运行的任务先失效 generation,再执行清理,避免删除后又被迟到任务重建。离线设备可在下次启动补传删除请求,但要保存最少的对象标识和回执状态,不能为了删除而长期保留原内容。
备份和日志往往有不同保留机制。项目需要明确在线删除与备份过期之间的边界,并向用户使用准确措辞。日志只保留附件摘要、阶段、错误类别和策略版本,不记录正文、文件名、访问令牌、远端 URL 查询参数或完整 OCR。若诊断必须取样,使用专用的脱敏、审批和过期流程,不能让通用崩溃 SDK 自动抓取输入对象。
| 对象 | 最短必要保留 | 删除责任 | 回执边界 |
|---|---|---|---|
| 本地最小化副本 | 上传或任务终态 | 客户端任务 | 文件与索引均移除 |
| 上传分片 | 完成组装前 | 上传服务 | 过期或主动清理 |
| 远端原对象 | 当前处理窗口 | 对象服务 | 在线对象删除 |
| OCR 与切片 | 推理或检索窗口 | 处理服务 | 父附件级联 |
| 结果缓存 | 用户会话所需 | 客户端与服务端 | 账号退出或到期 |
| 备份副本 | 既定备份周期 | 平台运维 | 说明延迟删除边界 |
隐私声明、第三方 SDK 与发布证据要相互校验
Android Data safety 和 Apple privacy manifest 都要求开发者了解应用及第三方 SDK 的数据行为。附件功能上线前,应把清单中的数据类别、共享对象和用途与商店声明、privacy manifest、SDK 配置和实际网络路径逐项核对。声明缺少不代表运行时一定上传,声明存在也不代表技术控制已经生效;二者是不同证据。任何 SDK 版本更新后都要重查其附件、日志和诊断读取范围。
OWASP MASVS-PRIVACY-1 强调最小化敏感数据访问,并要求考虑第三方组件;MASWE-0001 则提示生产日志可能暴露凭据、令牌、个人信息和内部诊断数据。项目应把这些原则落到自动门禁:扫描日志字段、检查附件 manifest、验证派生物删除、审计 SDK 初始化参数。引用页面的状态和适用边界也要保留,不能把通用控制写成目标 App 已完成验证。
NIST SSDF 提供来源、构建、验证和变更证据的组织方向。对附件最小化,发布包应绑定需求清单版本、客户端构建、预处理器版本、服务端策略、测试夹具、审校结果和变更负责人。实际网络与删除回执仍需项目环境验证。没有真实数据时,应明确 SEO、GEO、流量、排名和用户转化数据未接入,不利用模型输出或静态清单虚构运营结论。
- App 与第三方 SDK 的附件数据行为进入同一数据流图
- Data safety、privacy manifest 与真实字段和接收方逐项核对
- 日志门禁拒绝正文、文件名、令牌和完整 OCR 内容
- 每次 SDK 或 AI 服务变更重新审计收集、共享与保留范围
- 发布证据绑定清单、客户端、预处理器、服务端策略和测试
- 没有项目回执的能力、效果和运营指标保持未证实
用附件 manifest 门禁拒绝过度收集和孤立派生物
下面的 Python 示例读取一个公开安全的 JSON manifest。它要求附件使用稳定安全标识,具有具体 purpose、明确 userConfirmed、有限 expiresAt、字段 allowlist 和删除策略;每个 extractedField 必须出现在 allowlist,每个 derivative 必须绑定 parentId、retentionClass 与 deleteTrigger。脚本还检查 retry 对象不会重新读取原文件,并在任何违规时输出字段问题和非零状态。
示例只校验声明的一致性,不能证明预处理器真的删除了 EXIF、服务端真的按期删除,也不能代替合规审查。生产门禁应结合受控附件夹具、网络字段观察、文件系统回执、对象存储策略和删除演练。代码没有真实账号、客户数据、私钥、令牌、服务器地址或攻击步骤,可作为 CI 中 manifest 结构审计的起点。
可同时参阅本站关于模型下载遥测最小化和 AI 输出日志隐私边界的技术说明,分别处理模型资产网络与结果日志,不在当前页面复制第二套答案。准备具体 App 的附件治理评估时,可整理脱敏数据流图、支持格式、预处理规则、SDK 清单、保留策略、删除回执与测试夹具,再从御盾中央平台提交申请;最终 CTA 和本站相关文章链接由页面框架统一呈现。
- purpose、userConfirmed 与有限 expiresAt 均为必填门禁
- extractedFields 只能来自预先声明且唯一的 allowlist
- 每个派生物绑定父附件、保留类别和有限删除触发
- 重试只使用已确认的最小化副本,不重新读取原文件
- 结构通过后仍执行网络、文件、日志与删除回执验证
- 任何能力与效果结论绑定当前 App、服务策略和真实证据
from datetime import datetime, timezone
from pathlib import Path
import json
import re
import sys
if len(sys.argv) != 2:
raise SystemExit("usage: attachment_gate.py manifest.json")
manifest_path = Path(sys.argv[1])
if not manifest_path.is_file():
raise SystemExit("manifest file is missing")
try:
manifest = json.loads(manifest_path.read_text(encoding="utf-8"))
except (OSError, json.JSONDecodeError) as exc:
raise SystemExit("manifest is not valid JSON") from exc
if not isinstance(manifest, dict):
raise SystemExit("manifest must be an object")
safe_id = re.compile(r"^[A-Za-z0-9_.-]{1,64}$")
attachment_id = str(manifest.get("attachmentId", ""))
if not safe_id.fullmatch(attachment_id):
raise SystemExit("attachmentId is invalid")
if not str(manifest.get("purpose", "")).strip():
raise SystemExit("purpose is required")
if manifest.get("userConfirmed") is not True:
raise SystemExit("explicit user confirmation is required")
try:
expires_at = datetime.fromisoformat(str(manifest["expiresAt"]).replace("Z", "+00:00"))
except (KeyError, ValueError) as exc:
raise SystemExit("expiresAt must be an ISO timestamp") from exc
if expires_at.tzinfo is None or expires_at <= datetime.now(timezone.utc):
raise SystemExit("expiresAt must be a future bounded timestamp")
allowed = manifest.get("allowedFields")
extracted = manifest.get("extractedFields")
if not isinstance(allowed, list) or not allowed or len(allowed) != len(set(allowed)):
raise SystemExit("allowedFields must be a unique nonempty list")
if not isinstance(extracted, list) or any(field not in allowed for field in extracted):
raise SystemExit("extractedFields exceed the allowlist")
derivatives = manifest.get("derivatives")
if not isinstance(derivatives, list):
raise SystemExit("derivatives must be a list")
for number, item in enumerate(derivatives, 1):
if not isinstance(item, dict) or item.get("parentId") != attachment_id:
raise SystemExit(f"derivative {number} has no valid parent")
if not str(item.get("retentionClass", "")).strip():
raise SystemExit(f"derivative {number} lacks retentionClass")
if item.get("deleteTrigger") not in {"task_end", "cancel", "expiry"}:
raise SystemExit(f"derivative {number} lacks a bounded delete trigger")
retry = manifest.get("retry", {})
if retry.get("readsOriginalFile") is True:
raise SystemExit("retry must reuse only the confirmed minimized copy")
if retry.get("manifestVersion") != manifest.get("manifestVersion"):
raise SystemExit("retry is not bound to the current manifest")
print(f"validated_attachment={attachment_id} derivatives={len(derivatives)}")事实依据与适用边界
以下内容区分官方事实、本文工程判断和不能外推的范围,避免把设计建议写成未经验证的产品结论。
| 本文判断 | 事实或工程依据 | 适用限制 |
|---|---|---|
| Android 应用的数据声明需要覆盖应用代码和第三方 SDK 实际收集或共享的数据。 | Declare Android app data use 说明开发者需要根据应用及第三方库的数据行为完成 Data safety 信息。 | 商店声明是透明度与审核材料,不会自动限制附件读取、网络发送、日志或保留。 |
| Apple 平台的 App 与列明第三方 SDK 需要维护适用的 privacy manifest 数据与 API 声明。 | Apple privacy manifests 说明在 App 或第三方 SDK 中添加、聚合并验证 privacy manifest 的流程。 | manifest 不证明运行时最小化、用户确认、加密或删除机制已经正确执行。 |
| 移动应用应最小化敏感数据访问,并把第三方组件的数据行为纳入责任边界。 | OWASP MASVS-PRIVACY-1 要求应用尽量减少敏感数据访问,且控制范围包括 App 及其第三方依赖。 | 通用控制不替代具体附件分类、用户同意、地区合规和目标系统验证。 |
| 生产日志可能暴露凭据、令牌、个人信息和内部诊断内容,附件流程必须限制日志字段。 | OWASP MASWE-0001 描述敏感数据进入日志的风险与删除、降级、脱敏等缓解方向。 | 该页面处于 Beta;文章只采用稳定风险原则,不把它当作目标 App 已通过测试的证据。 |
| 附件 API 的 OAuth 部署应限制授权流程、令牌受众与重放风险。 | RFC 9700 OAuth Security BCP 总结重定向、授权码注入、令牌重放和不安全 grant 等安全实践。 | OAuth 授权不决定附件是否必要、用户是否同意、内容如何最小化或服务保留多久。 |
| 附件治理的发布过程应保留需求、构建、验证和变更证据。 | NIST SP 800-218 SSDF 给出组织级安全软件开发与供应链风险管理实践。 | SSDF 不定义具体附件字段、删除时限,也不证明任何加固产品或 AI 服务具备特定能力。 |
| 预处理产生的缩略图、OCR、切片、向量和重试副本应继承父附件的身份与删除策略。 | 工程判断:派生物若脱离 parentId、generation 和 retentionClass,将无法完成一致撤销与删除。 | 实际派生类型和保留条件由目标 App 数据流决定,公开清单不能代替文件与服务端回执。 |
| 用户取消或切换账号后,旧附件任务应先失效再清理,不能继续回写迟到结果。 | 工程判断:账号域、attachmentId、generation 和 consentId 共同约束异步任务归属。 | 取消不保证所有远端副本瞬间消失;产品需要准确说明不可立即终止的阶段与备份边界。 |
工程常见问题
用户主动选择文件,是否等于同意上传全部元数据?
不等于。应展示实际发送内容、元数据类别、接收方和用途;无关 EXIF、文件系统属性、批注或隐藏内容默认删除,确有必要时单独说明。
把图片压缩后上传是否已经完成数据最小化?
没有。压缩主要改变编码与体积,未必删除 EXIF、原文件名、缩略图或无关区域;还要按字段清单重建副本并核对派生物。
附件只在服务器短暂处理,可以不设置删除策略吗?
不可以。短暂仍需可执行定义,覆盖上传分片、原对象、OCR、切片、缓存、日志和失败副本,并保留在线删除与备份过期边界。
有 OAuth 访问令牌是否说明附件上传符合最小化要求?
不能。令牌只约束调用授权,还要独立检查附件目的、用户确认、字段范围、账号域、保留期与派生物删除。
上传失败自动重试时需要再次读取原文件吗?
默认不应。重试只引用已确认的最小化副本和相同 manifest;账号、清单版本、用户选择或副本摘要变化时,任务作废并重新确认。
通过 manifest 校验是否代表附件数据已经安全?
不代表。结构门禁只检查声明一致性,还需用受控夹具验证文件清理、网络字段、日志、第三方 SDK、远端对象和删除回执。