先看结论与判断条件
- 生产日志只记录为明确运维目的准备的结构化事件,不把用户会话、完整模型输入输出或工具参数当成调试附件保存。
- 字段允许列表应在日志调用入口执行,未知字段和敏感字段直接拒绝,不能先写入再依赖下游检索系统脱敏。
- 请求关联使用短期、随机且不可反查用户身份的标识;账户、设备、广告和安装标识不能直接进入 AI 诊断事件。
- 日志保留期限按用途分别确定,错误排查、质量趋势和安全审计不应共享一个无限期存储桶或统一访问权限。
- Android Data safety 与 Apple privacy manifest 用于声明数据行为,但声明文件不能替代运行时最小化、脱敏、删除和访问控制。
- 第三方 AI、崩溃分析与可观测性 SDK 必须纳入字段、传输、保留和再共享审查,默认配置不能直接视为符合业务边界。
先把日志定义成诊断事件,而不是对话副本
移动 AI 功能通常同时处理用户输入、检索片段、工具调用参数、模型输出和业务后处理结果。最省事的调试方式是把整条链路打印出来,但这会让日志系统变成另一份未经设计的数据仓库。生产环境真正需要的是可回答具体故障问题的事件,例如请求进入哪个处理阶段、结果属于成功还是可恢复错误、耗时落在哪个区间,以及哪个版本产生了异常,而不是原始对话本身。
日志边界应从用途倒推。排查模型超时需要事件时间、请求关联号、模型族、超时阶段和错误码;分析输出格式失败需要解析器版本、结果类别和校验失败原因;核对计费可能需要经过聚合的调用次数。上述目的都不要求保存完整提示词或回答。若某个字段无法对应到明确的告警、查询、统计或审计动作,就不应因为“以后也许有用”而进入生产日志。
这一区分也影响工程责任。模型输入输出属于业务数据处理链的一部分,诊断事件则是为运行维护新创建的数据副本。副本一旦写入文件、系统日志、崩溃平台或第三方可观测性服务,就会获得新的访问者、保留周期和导出路径。开发、运维与隐私负责人应共同审核这份事件契约,确认每个字段的用途、来源、可见范围和删除方式。
| 数据对象 | 生产日志处理 | 可替代记录 | 主要原因 |
|---|---|---|---|
| 完整提示词 | 拒绝写入 | 输入长度区间、模板版本 | 可能包含业务文本、个人信息或内部指令 |
| 完整模型回答 | 拒绝写入 | 结果类别、结构校验状态 | 可能复述输入并生成新的敏感内容 |
| 检索文档片段 | 拒绝写入 | 索引版本、命中数量区间 | 片段可能来自受限知识库 |
| 工具调用参数 | 按字段拒绝或归类 | 工具名称、执行状态、公开错误码 | 参数可能带账户、订单、地址或令牌 |
| 短期请求关联号 | 满足约束后允许 | 随机值并限制生命周期 | 用于串联事件但不应长期识别用户 |
| 错误堆栈 | 清洗后允许 | 异常类型、应用版本、首个应用帧 | 原始堆栈可能夹带路径、参数和 SDK 元数据 |
用数据分类和字段允许列表控制写入面
字段允许列表比关键词黑名单可靠。黑名单只能拦住已经想到的名称,业务新增字段、SDK 升级或序列化结构变化后,未知内容仍可能被整体打印。允许列表则把日志事件限定为一份版本化契约,只接受 event、result_class、error_code、duration_bucket、app_version 等已审核键;任何未声明字段都会让检查失败,从而迫使开发者说明新增数据的必要性。
分类至少要区分原始内容、身份数据、认证材料、业务标识、诊断元数据和聚合指标。原始内容包括提示词、回答、检索片段与附件文本;身份数据包括账户、设备、广告和安装标识;认证材料包括访问令牌、Cookie、签名和临时凭据;订单号、病历号、合同号等业务标识即使不是通用个人信息,也可能直接暴露企业数据。诊断元数据只有在无法反推这些对象时才适合进入常规日志。
允许字段还需要值域约束。result_class 应从固定枚举中选择,错误码应来自公开的内部错误分类,字符串长度应设置上限,请求关联号应符合短期随机格式。单纯允许一个名为 message 的字段,会让调用方重新把完整输出塞进去;允许 metadata 对象也会把边界推给任意嵌套结构。事件契约应避免自由文本和开放字典,把必要上下文转换成受控类别。
| 审查维度 | 需要确认 | 不合格示例 | 合格处理 |
|---|---|---|---|
| 业务用途 | 字段支持哪条告警、查询或报表 | 仅写“便于排查” | 绑定具体故障场景和负责人 |
| 可识别性 | 能否单独或组合识别用户、设备或交易 | 直接记录账户号 | 改用短期请求关联号或聚合计数 |
| 值域 | 类型、枚举、长度和嵌套是否受限 | 自由文本 message | 固定结果类别与公开错误码 |
| 来源 | 字段来自客户端、服务端、模型还是 SDK | 把 SDK 回调对象整体序列化 | 只提取已审核的布尔值或枚举 |
| 保留 | 到期后如何从主存储和导出副本删除 | 没有到期字段 | 事件带用途类别并映射删除策略 |
| 访问 | 哪些岗位和服务能够读取或导出 | 共享运维账户 | 按用途分库并记录读取审计 |
脱敏必须发生在格式化和传输之前
脱敏位置决定了控制是否真实有效。若应用先拼接完整字符串,再把结果交给日志框架过滤,原文可能已经出现在内存缓冲、崩溃附件、系统日志或 SDK 拦截器中。更稳妥的做法是在业务对象转换成日志事件之前完成字段选择和类型归类,日志接口只接收已经过审的结构化对象,不提供接受任意字符串的生产级入口。
不同数据需要不同处理。令牌和密钥应直接删除,不能只遮盖首尾字符;电子邮箱、电话号码和账户号若对诊断无必要也应删除;需要观察分布时,可在可信边界内转换为不可逆的类别或聚合计数。哈希并不自动等于匿名化,稳定输入会得到稳定结果,低熵标识还可能被枚举。若关联用途只持续一次请求,优先生成独立随机号,而不是对用户标识做哈希。
脱敏失败时要选择安全结果。未知字段、超长字符串、嵌套对象和命中敏感模式的值应让事件被拒绝或降级成最小错误记录,不能在异常处理分支中回退为完整对象打印。拒绝本身可以产生一个不含原始值的计数事件,例如记录 schema_rejected 与规则编号,帮助团队发现调用方持续违反契约。
- 日志 API 只接收结构化事件,不接收完整提示词或回答字符串
- 字段筛选发生在 JSON 序列化、字符串插值和 SDK 传输之前
- 认证材料命中后直接删除,不保留可恢复的部分内容
- 请求关联号独立随机生成,不由账户或设备标识派生
- 未知字段与开放嵌套对象默认拒绝
- 异常分支不会改用对象 toString 或原始响应打印
- 脱敏拒绝只记录规则编号和事件类别,不记录被拒绝值
保留期限、访问范围和导出副本要按用途拆开
日志最小化不只发生在写入时。错误排查事件通常在版本稳定后迅速失去价值,质量趋势需要的是聚合结果,安全审计则可能要求更严格的完整性与访问记录。把三类数据放入同一个长期存储桶,会让最宽的保留期限和权限覆盖所有事件。工程上应按用途分流,每类事件绑定责任人、查询入口、删除期限和允许导出形式。
删除设计要覆盖主索引、对象存储、离线导出、告警通知和工单附件。许多系统只给在线检索设置生命周期,却忽略值班人员下载的 CSV、聊天机器人转发的告警正文或崩溃平台自动附带的上下文。事件契约中应规定哪些字段允许出现在通知渠道,导出任务应带到期时间和审批记录,过期后还要验证副本确实不可检索。
访问控制应以工作职责和用途为单位,而不是让所有研发共享一个可观测性账户。处理模型质量的人可能只需要聚合结果类别,排查客户端崩溃的人需要应用版本与错误栈,安全人员才需要读取拒绝事件和访问审计。系统应记录查询者、查询条件、导出范围与时间,异常的大范围检索或批量导出应触发复核。
| 用途 | 建议保存内容 | 访问角色 | 到期动作 |
|---|---|---|---|
| 实时故障排查 | 错误码、阶段、版本、短期请求号 | 值班开发与运维 | 删除明细,仅保留聚合故障量 |
| 模型质量趋势 | 结果类别、校验状态、聚合计数 | 产品与模型质量人员 | 按报表周期重新聚合并删除旧明细 |
| 安全审计 | schema 拒绝、访问和导出事件 | 授权安全人员 | 按审计政策删除并保存销毁回执 |
| 计费核对 | 经过服务端聚合的调用计数 | 财务与平台负责人 | 对账结束后按财务政策处置 |
| 崩溃分析 | 清洗后的异常类型与应用帧 | 移动端研发 | 版本问题关闭后删除明细 |
| 临时专项诊断 | 经审批的最小新增字段 | 指定处理小组 | 到期自动关闭采集并删除副本 |
平台声明与第三方 SDK 必须和真实数据流对齐
Android 的 Data safety 责任覆盖应用与所集成第三方 SDK 实际收集和共享的数据。把 AI 输出交给崩溃分析、会话回放或日志 SDK 后,数据流已经超出应用自身进程,团队需要核对传输目的、接收方、保留方式与用户控制。商店声明可以帮助外部了解数据行为,但它不会阻止一段错误的日志代码上传完整回答,也不会自动执行删除。
Apple privacy manifest 与第三方 SDK 要求同样需要和实际依赖匹配。应用应检查自身与 SDK 提供的 manifest、数据类型和 required reason API 声明,并在升级依赖后重新核对。SDK 出现在 Apple 的要求列表中,只说明其需要满足相应材料或签名条件,不代表 SDK 的默认日志字段、网络目的地或安全质量已经通过项目评估。
第三方 SDK 审查要落到可验证配置。列出初始化参数、自动采集开关、网络端点、离线缓存、崩溃附件、用户标识设置、数据保留和再共享条款;在测试构建中触发 AI 成功、解析失败、网络错误与崩溃场景,确认 SDK 实际发送的数据与文档一致。无法关闭自由文本采集或无法说明保留边界的 SDK,不应直接接入包含 AI 业务数据的链路。
| 核对对象 | 要取得的证据 | 常见偏差 | 处置方式 |
|---|---|---|---|
| 自动采集 | 初始化配置与运行时网络观察 | 默认收集页面文本或请求体 | 关闭自动项并改用结构化手工事件 |
| 用户标识 | SDK API 调用点与事件样本 | 把账户或设备标识设为全局用户 ID | 移除或改为短期随机关联号 |
| 崩溃附件 | 崩溃场景上传清单 | 附带内存面包屑和模型回答 | 关闭附件或先执行字段过滤 |
| 离线缓存 | 设备文件检查与清除行为 | 未加密缓存长期保留 | 限制缓存字段并验证到期清除 |
| 服务端保留 | 合同、控制台策略与删除回执 | 控制台期限与合同描述不一致 | 采用更短期限并定期验证 |
| SDK 升级 | 版本差异、manifest 与数据流复测 | 新增字段未进入声明 | 阻止升级直到清单和声明同步 |
用可执行的事件契约拦住敏感字段
字段规则只有进入构建与测试流程才有约束力。下面的 Python 检查器读取一条待记录的 JSON 事件,先验证顶层对象,再拒绝未知键和明确禁止的业务字段,随后检查结果类别、请求号格式与字符串长度。它不接触真实日志服务,也不包含任何密钥,适合在代码评审样本、单元测试夹具或 CI 中验证事件形状。
检查器使用允许列表而不是删除列表。prompt、response、output、messages、user_id、device_id、authorization 等字段只要出现就立即失败;允许字段也不能携带任意长文本或类似 Bearer 凭据的值。request_id 被限定为短随机十六进制格式,但脚本不能证明该值的生成来源或生命周期,因此项目仍需在调用方确认它不是账户、设备或会话标识的稳定哈希。
代码返回非零状态只是发现契约不合格,不等于日志系统已经安全。真实接入还要覆盖多语言序列化、崩溃处理、SDK 自动面包屑、后台任务与离线重试。每次新增事件字段时先更新数据分类和用途记录,再修改允许列表与测试夹具;不能为了让构建通过而把 message、metadata 或 context 之类的开放容器加入契约。
#!/usr/bin/env python3
import json
import re
import sys
from pathlib import Path
ALLOWED_FIELDS = {
"event", "model_family", "result_class", "duration_bucket",
"error_code", "request_id", "app_version"
}
FORBIDDEN_FIELDS = {
"prompt", "response", "output", "messages", "user_id",
"device_id", "session_id", "authorization", "api_key"
}
RESULT_CLASSES = {"ok", "timeout", "parse_error", "policy_rejected"}
REQUEST_ID_PATTERN = re.compile(r"^[a-f0-9]{16}$")
CREDENTIAL_PATTERN = re.compile(r"(?i)\b(?:bearer|basic)\s+[a-z0-9._-]+")
def validate_event(event):
if not isinstance(event, dict):
raise SystemExit(3)
forbidden = sorted(FORBIDDEN_FIELDS.intersection(event))
if forbidden:
print("forbidden fields: " + ",".join(forbidden), file=sys.stderr)
raise SystemExit(4)
unknown = sorted(set(event).difference(ALLOWED_FIELDS))
if unknown:
print("unknown fields: " + ",".join(unknown), file=sys.stderr)
raise SystemExit(5)
if event.get("result_class") not in RESULT_CLASSES:
print("invalid result_class", file=sys.stderr)
raise SystemExit(6)
request_id = str(event.get("request_id", ""))
if not REQUEST_ID_PATTERN.fullmatch(request_id):
print("invalid request_id", file=sys.stderr)
raise SystemExit(7)
for key, value in event.items():
if isinstance(value, (dict, list)):
print("nested value rejected: " + key, file=sys.stderr)
raise SystemExit(8)
text = str(value)
if len(text) > 120 or CREDENTIAL_PATTERN.search(text):
print("unsafe value rejected: " + key, file=sys.stderr)
raise SystemExit(9)
def main():
if len(sys.argv) != 2:
print("Usage: log_event_check.py EVENT_JSON", file=sys.stderr)
raise SystemExit(2)
event_path = Path(sys.argv[1])
if not event_path.is_file():
print("event file not found", file=sys.stderr)
raise SystemExit(2)
try:
event = json.loads(event_path.read_text(encoding="utf-8"))
except (OSError, json.JSONDecodeError) as exc:
print("cannot read event: " + str(exc), file=sys.stderr)
raise SystemExit(2)
validate_event(event)
print("event schema accepted")
if __name__ == "__main__":
main()测试要证明敏感数据没有进入每一条旁路
测试不能只检查应用主动调用的 logger。AI 链路还可能被 HTTP 拦截器、崩溃面包屑、系统控制台、WebView 调试、后台任务框架和第三方 SDK 自动记录。建立一组带明显标记的合成输入,依次触发成功、超时、解析失败、工具拒绝、应用退后台和崩溃,再从设备日志、缓存目录、网络请求与服务端检索中查找这些标记。标记只用于测试,不能采用真实客户数据。
结果要同时包含正向与反向断言。反向断言证明完整输入、完整回答、长期标识和凭据形状没有出现;正向断言证明 event、result_class、错误码和短期请求号仍能支持排查。若只要求“搜不到敏感标记”,最简单的实现可能是关闭全部日志,运维价值也随之消失。事件契约应让团队在最小数据和可诊断性之间做可复核选择。
发生泄露后,第一步是停止相关事件或 SDK 的采集,随后界定受影响版本、字段、接收端、保留副本和访问者。删除在线索引不代表工单、告警通知、对象存储和离线导出已经清理。处置记录应包含规则修复、历史副本删除回执、访问审计复核与回归测试结果;若数据行为影响商店或隐私声明,还要同步更新相应材料。
- 使用合成标记覆盖成功、错误、后台和崩溃路径
- 检查应用日志、系统日志、SDK 缓存、网络传输和服务端检索
- 断言完整输入、回答、凭据形状和长期标识均不可检索
- 断言允许的事件类别、错误码和短期关联号仍可用于定位
- 测试样本不使用真实客户、员工或生产业务数据
- 泄露处置覆盖在线索引、通知、工单、导出和备份副本
- 每次 SDK 或日志框架升级后重新执行同一组旁路测试
把发布决定绑定到字段、来源和删除证据
发布前的评审对象应是一份可定位到构建版本的日志契约,而不是口头承诺“已经脱敏”。契约包含事件名称、字段、类型、值域、用途、数据来源、接收端、保留期限、访问角色和删除动作。代码扫描与运行测试都通过后,再核对 Android Data safety、Apple privacy manifest 及第三方 SDK 材料是否与真实数据流一致。任一字段没有用途或删除路径,都应从候选版本移除。
放行结论必须保留边界。公开资料支持数据最小化、生产日志敏感数据风险、平台声明和第三方 SDK 责任,但不能证明某个具体 App 已完成日志治理。项目还需提供同一候选包的事件样本、旁路测试、SDK 网络观察、存储生命周期与删除回执。没有这些证据时,只能确认设计和静态规则已准备,不能写成生产环境不存在泄露。
实施顺序可以从最危险的数据面开始:关闭完整提示词与回答日志,收回自由文本入口;建立字段允许列表和测试夹具;拆分用途、访问与保留;核对第三方 SDK;最后把声明与真实数据流对齐。完成后仍要把日志契约纳入版本变更评审,因为模型供应商、工具调用、检索链和观测 SDK 任一变化都可能重新扩大数据面。
| 检查项 | 通过条件 | 失败动作 | 证据 |
|---|---|---|---|
| 完整输入输出 | 所有生产路径均拒绝写入 | 关闭采集并修复调用点 | 合成标记旁路测试 |
| 字段契约 | 只有允许键且值域受限 | 未知字段使构建或测试失败 | 规则文件与测试报告 |
| 请求关联 | 短期随机且不可反查身份 | 替换稳定哈希和长期 ID | 生成代码审查与事件样本 |
| 生命周期 | 每个用途有访问和到期动作 | 拆分存储并补删除任务 | 策略配置与删除回执 |
| 第三方 SDK | 自动采集关闭且传输字段已核对 | 阻止升级或移除 SDK | 配置、网络观察与合同边界 |
| 平台声明 | 声明与当前数据流一致 | 修正声明或减少收集 | Data safety 与 privacy manifest 复核 |
| 项目实证 | 候选包旁路测试和副本清理可复核 | 维持待验证状态 | 版本、样本、访问审计和回执 |
事实依据与适用边界
以下内容区分官方事实、本文工程判断和不能外推的范围,避免把设计建议写成未经验证的产品结论。
| 本文判断 | 事实或工程依据 | 适用限制 |
|---|---|---|
| Android 应用的数据声明需要覆盖应用代码和第三方 SDK 实际收集或共享的数据。 | Declare Android app data use | Data safety 是面向商店的数据行为声明,不会替应用执行日志字段过滤、访问控制或删除。 |
| 移动端隐私控制应减少敏感数据访问,并把第三方组件的数据行为纳入审查。 | OWASP MASVS-PRIVACY-1 | 该控制给出最小化方向,具体字段分类、合法基础、同意和保留规则仍需项目合规审查。 |
| 生产日志可能暴露凭据、令牌、个人信息和内部诊断数据,敏感内容应删除、降低详细度或脱敏。 | OWASP MASWE-0001 | 该页面处于 Beta,本文只采用其稳定风险与缓解原则,不把页面状态当作具体 App 测试结果。 |
| iOS 应用和第三方 SDK 的数据收集及 required reason API 使用需要进入有效的 privacy manifest。 | Apple privacy manifests | Privacy manifest 是声明和审核材料,不是运行时隔离、日志脱敏或网络阻断机制。 |
| 集成第三方 SDK 后,开发者仍需对其代码和数据行为负责,部分 SDK 还受签名与 privacy manifest 要求约束。 | Apple third-party SDK requirements | Apple 的清单不覆盖 Android 依赖,也不代表列出的 SDK 已通过项目所需的安全或隐私评估。 |
| 移动安全验收应把存储、隐私、平台交互、代码质量与韧性等控制域分开取证。 | OWASP MASVS overview | MASVS 提供控制域框架,不给出御盾或任何具体产品、候选包和日志配置的验收结论。 |
| 工程判断:字段允许列表应在格式化和传输之前执行,未知字段默认拒绝,避免原文先进入日志缓冲或 SDK。 | 工程判断:基于结构化日志的数据流和失败路径分析 | 该设计仍需用项目实际 logger、崩溃 SDK、后台任务和网络旁路测试证明实施完整。 |
| 当前没有具体候选包的日志样本、旁路测试和删除回执,因此不能断言生产环境已经消除 AI 业务数据泄露。 | 项目证据尚未接入 | 文章只给出可执行设计、检查代码和验收方法,不包含客户案例、性能数字或已上线验证结论。 |
工程常见问题
完整模型输出经过哈希后可以写入日志吗?
通常不应这样做。稳定输出会产生稳定哈希,低熵内容还可能被枚举,哈希也无法解决保留和访问范围问题。若只需关联一次请求,使用独立生成的短期随机号;若要分析质量,优先记录受控结果类别和聚合计数。
为了排查线上问题,能否临时打开完整提示词日志?
不应把全量原文采集当作临时开关。专项诊断应先定义最小字段、限定版本和用户范围、取得审批、设置自动到期,并使用合成或明确授权的数据。诊断结束后验证采集关闭,清理在线索引、导出和通知副本。
请求 ID 怎样设计才不会变成长期用户标识?
为每次请求独立生成随机值,只在排障所需的短时间内有效,不从账户、设备、广告或安装标识派生,也不跨会话复用。服务端和客户端只用它串联本次事件,到期后明细一起删除。
Android Data safety 或 Apple privacy manifest 填完后还要做什么?
继续验证运行时代码。检查日志调用、SDK 自动采集、网络传输、离线缓存、访问权限和删除任务,并用合成标记覆盖成功、错误、后台与崩溃路径。声明必须跟随真实数据流变化更新,但不能替代这些技术控制。
第三方崩溃分析 SDK 默认会不会记录 AI 输出?
不能靠默认假设判断。核对 SDK 文档和初始化配置,再触发含合成标记的 AI 成功、解析失败和崩溃场景,观察事件、面包屑、附件、缓存与网络请求。无法关闭自由文本或无法说明保留边界时,不应接入该数据链。
允许列表脚本通过是否代表日志系统已经合规?
不代表。脚本只检查单条事件的字段和值形状,无法证明调用方没有绕过 logger,也无法验证服务端访问、保留、导出和删除。还需要候选包旁路测试、SDK 数据流核对、权限审计与删除回执,合规结论由适用地区和业务场景决定。