Web Share Target PWA 的入口与隐私
设计一个校验 GET 与 POST 入口、限制数据留存并保留安全回退的 Web Share Target PWA。
BotBrowser Team
Web Share Target PWA 可以接收由浏览器中介的分享请求,但它不知道用户为何选择来源,也不知道每个字段是否都应该离开来源。BotBrowser 能够在受控环境重放自有 fixture 并检查接收路由、校验状态和回退路径;操作系统来源选择与用户披露意图不在可观测范围内。
TL;DR
- 把
share_target当作不可信的入口契约,而不是身份信号。 - 在渲染或保存前校验
GET查询值和POST表单数据。 - 有意收窄 manifest 的
scope、路由和允许字段。 - 让重复投递具备幂等性,只保留完成任务所需的数据。
- 浏览器无法安装或调用 PWA 时,保留普通导航或复制回退。
内容导航
Manifest 与请求解析
WICG Web Share Target 提案定义 manifest 的 share_target 成员,把传入分享映射到 URL、方法和参数名。MDN Web Share Target API 参考把它描述为向已安装 PWA 的交接。这个声明不是认证令牌、发送方身份,也不保证分享数据适合保存。若要区分与入口无关的本地状态,请参阅 PWA Badging API 与通知边界。
针对 manifest 路由,BotBrowser 可以核对同源授权 fixture 的可见校验分支;它不能代表操作系统选择分享来源,也不能证明用户决定披露了什么。这些限制应写入产品契约。
让声明明确且小。只接收公开 URL 的目标可以写成:
{
"share_target": {
"action": "/inbox/share",
"method": "GET",
"params": { "title": "title", "text": "text", "url": "url" }
},
"acceptanceMetadata": { "fixtureRevision": 3 }
}
action 必须位于应用作用域内。一个专用 inbox 路由足够时,不要使用全捕获路由。需要了解更广的存储边界时,可读浏览器存储模型,它区分 origin 所有的状态。
GET 会把映射值放进 action URL。URL 可能进入历史、日志、referrer、截图或分析系统,因此不要在查询参数中放 bearer token、私有文档文本或账户标识。首次模板渲染前先限制长度并规范编码。
POST 不把正文放在 action URL 中,但仍然跨越浏览器到应用的边界。manifest 可以声明 application/x-www-form-urlencoded 或 multipart/form-data;接收端必须拒绝意外的 content type,并在解析前限制正文。文件还要有独立的大小、类型和恶意内容策略。解析成功不等于允许保存原始正文。
用一个有界记录比较 fixture 结果,避免保留内容:
{
"method": "POST",
"route": "/inbox/share",
"fields": ["title", "text", "url"],
"bodyBytes": 4096,
"outcome": "validated-local-draft",
"fixtureRevision": 3
}
MDN 的 share_target manifest 成员列出了结构和参数映射。manifest、解析器和测试必须使用同一映射;重命名后让 url 静默为空是数据丢失,不是隐私保护。
校验、隐私与回退
service worker 可以接收导航并把它转给受控 client,也可以由页面在导航后处理请求。无论哪条路径,都要在显示前校验 origin、方法、content type、字段长度和 URL scheme。即使值看起来像链接,也要拒绝 javascript: 和意外的内嵌凭据。
不要从 Referer、user-agent 或时序推断发送方;这些值可能缺失。service worker 注册也不能证明当前页面受其控制:检查可见路由和应用版本标记。关于缓存所有权与更新竞态,可参阅service worker 缓存生命周期与隐私,旧 worker 可能继续返回旧响应。
把过期 worker 当作有界失败。校验前不显示输入,解析失败时保留原路由,并提供重试或复制动作。没有幂等 key 时,不要把失败的 POST 循环送回 share target。
来源可能在超时后重试,用户也可能两次调用同一目标。创建应用自己的请求标识,在短时间窗内去重,只记录 accepted、rejected 或 expired 这类状态。如果不透明的 fixture 标识已经足够,就不要记录原文、文件字节或完整 URL。
在用户确认下一步前,让私有内容保持本地。“保存”按钮可以创建带独立授权和保留规则的服务端记录;仅仅打开 share target 不应创建它。可见草稿被接受或取消后删除临时解析数据,避免 service worker 缓存变成意外档案。
浏览器交接不会建立新的账户关系。邀请协作者应是单独确认并单独审计的操作。share target 是便利入口,不是访问控制决定。可参阅Web Share API 权限边界与安全回退,区分分享请求和交付确认。Web Share API 与用户主动发起的分享还说明了在目标接收数据前必须满足的激活与取消约束。
在测试阶段,BotBrowser 能够重放同源授权 fixture 并比较入口状态、校验结果和回退动作;真实操作系统来源与用户披露内容无法由此得到证明,因此这只是界面和请求边界检查,不是隐私认证。
使用合成 GET、POST、超大正文、未知字段、无效 URL、重复标识和缺失 service worker 进行测试。断言可见状态和有界记录,不要断言操作系统 chooser。受控 fixture 可以验证 4096 字节限制,却不能证明真实来源应用会发送什么。
安装、manifest 解析或浏览器支持缺失时,保留普通 /inbox/share 导航、复制链接或粘贴字段。回退应保留任务,不能通过更宽的路由重新导入同一数据。MDN 应用间数据分享指南说明用户可见的关系,但不替代留存审查。
实现与验收
manifest 是配置,不是解析器。在构建阶段读取 share_target 成员,拒绝超出应用 scope 的 action,并生成运行时可与传入请求比较的允许列表。可审查的记录应包含 action、method、enctype 和每个参数名,也应记录 manifest 修订号,因为 worker 与页面可能在不同时间更新。修订 2 处理的请求,不能静默地拿去与为修订 3 写下的预期比较。
对于 GET 目标,从请求 URL 构造 URL,只读取声明过的键。URLSearchParams.get() 返回第一个值,getAll() 可检查重复键。应明确决定拒绝重复值还是归并为一个值,不能让框架默认值替你决定。百分号转义只解码一次,把无效 UTF-8 变成拒绝;仅在字段契约允许时裁剪,并在解码后执行限制。例如,标题限制 120 个 Unicode 标量值、正文限制 4,000 个值,都要在渲染前检查,而不是等数据库列截断后才检查。
即使值看起来无害,URL 本身也很敏感。反向代理可能记录完整路径,浏览器历史可能同步它,referrer 可能把它暴露给其他 origin,截图或分析事件也可能捕获它。设置适合应用的 Referrer-Policy,在创建已校验草稿后用同源重定向移除查询串,并确保重定向不会把拒绝请求变成接受请求。重定向是清理步骤,不是授权步骤。
对于 POST,先检查 request.method、Content-Type 标头和声明的 enctype。解析 application/x-www-form-urlencoded 时限制最大字节数和字段数。对于 multipart/form-data,要求 boundary;除非明确支持,否则拒绝嵌套 multipart,并同时限制总字节数和每个 part。存在 Content-Length 时要检查,但仍须执行流式上限,因为 chunked 请求没有可信的声明长度。一旦超过上限立即中止 body reader,并返回稳定的 413 或可见的 too-large 状态。
示例只建立解析边界。生产代码仍需流式实现、带明确限制的 multipart 库,以及绝不回显 body 的错误映射。不要显示包含字段值的异常消息。若表单解析器接受未知的 admin、redirect 或 callback 字段,即使应用稍后忽略它,也已经扩大契约;应在业务逻辑之前拒绝未知名称。
url 字段有独立策略。把它解析为 URL,要求 https: 或经过明确审查的 http: 例外,拒绝用户名和密码部分,并决定是否丢弃 fragment。指向同一分享端点的 URL 可能造成重定向循环,data: URL 则可能携带数 MB 编码内容。如果产品只接受公开链接,要在服务端边界拒绝私有网络主机名,并在页面端拒绝危险 scheme。这是输入校验,不是对链接目标安全性的声明。
文件、重放与 CSRF。 multipart/form-data 可以携带文件,即使界面只要求标题和 URL。单独定义文件允许列表、大小上限和数量上限。先用声明的 MIME 做路由,再在隔离区检查 magic bytes;标头由发送方提供。对小型图像 fixture,可以允许 image/png 与 image/jpeg,上限 2 MiB、一个文件,并限制解码像素数。除非另有审查支持,否则拒绝归档、脚本和多态文件。验收记录不要写入上传的临时路径。
文件字节应放在属于请求的短期隔离区,不要放进 service worker 缓存或共享浏览器数据库。在用户批准保存前完成扫描或处理,将临时名称与原文件名分离,并在取消、超时、解析失败和过期时删除。MIME 校验成功不等于恶意软件扫描,本地草稿也不等于同意上传。如果当前环境不能检查文件,显示 file-review-unavailable,保留回退,不要静默接受。
Web Share Target 是导航式入口,因此普通 CSRF 假设仍然适用。同源请求可以携带环境 cookie,目标路由也可能在 OS 分享面板之外被打开。在服务端保存前,要求应用会话、用于状态变更的短期 CSRF token,或明确确认。不能把存在 share_target 当成浏览器通过分享调用路由的证明。路由在粘贴到地址栏或由普通链接打开时也必须安全。
幂等 key 可以防止重试创建两个草稿,但不能认证发送方。在应用边界生成随机请求标识,把它绑定到当前会话和 manifest 修订号,并在短暂且有文档记录的窗口后过期。如果同一个 key 携带不同方法细节或不同会话到达,将其归类为 key-conflict,不要合并记录。完全相同的重放可以返回原有的有界结果,例如 accepted-local、duplicate 或 expired,但不要返回原始私有字段。
不要只用时间戳作为重放 key。时钟偏差、延迟投递,以及同一秒内两次合法分享,都会使它含义不清。不要从原文推导重放标记后就称其匿名:短标题或 URL 可以被猜测并比对。日志中优先使用不透明 fixture ID,并让请求比较记录短期保存且受访问控制。隐私边界还包括遥测、崩溃报告、会话回放、队列中的请求 body 和支持导出,而不只是主数据库。
验收记录。 如果验收记录能让审查者重现可见分支、又不保留分享内容,它就有价值。记录 fixture 修订号、浏览器和操作系统标签、路由、方法、声明的 enctype、body 字节数、接受的字段名、校验结果、重复分类、回退状态和删除期限。排除标题文本、URL 值、文件名、文件字节、cookie、授权标头和完整查询串。稳定记录可以跨浏览器版本比较,却不应成为内容档案。
{
"fixtureRevision": 3,
"route": "/inbox/share",
"method": "POST",
"enctype": "multipart/form-data",
"bodyBytes": 4096,
"fields": ["title", "text", "url"],
"fileCount": 0,
"validation": "accepted-local",
"replay": "new",
"csrf": "confirmed",
"retention": "delete-after-cancel"
}
用决策表让契约可审查:
| 边界 | 结果能证明什么 | 安全的下一步 |
|---|---|---|
| 不支持的 manifest 或未安装目标 | 浏览器无法在此调用入口 | 保留普通导航和粘贴回退 |
GET 字段过大或重复 | URL 契约无效 | 移除查询,显示有界错误,不要保存 |
POST MIME 类型意外 | 声明的正文契约未满足 | 停止解析并返回 content-type-rejected |
| 文件超过单文件限制 | 文件策略已超出 | 删除临时部分并显示大小规则 |
| 字段有效但没有确认 | 可以显示本地草稿 | 保持本地,不创建服务端记录 |
| 幂等 key 与请求详情相同 | 观察到重复投递 | 返回之前的概略结果,不返回请求正文 |
| key 相同但详情或会话不同 | 存在重放冲突 | 拒绝、简短提示并要求新操作 |
| service worker 缺失或过期 | 预期 worker 路径不可用 | 保留路由并提供复制或重试 |
表格区分浏览器观察实际证明的内容与产品可能想要声称的内容。可见的 accepted-local 状态只证明该 fixture 通过了页面检查;它不证明来源应用只发送了这些字段、不证明用户有意披露每个值,也不证明之后的服务端保存遵守声明的留存期限。
BotBrowser 能力与限制。 对于 Web Share Target,BotBrowser 能够在受控浏览器中重放授权的同源 GET 或 POST fixture,检查 manifest 路由,执行 service worker 校验,并断言 accepted、rejected、duplicate、oversized、missing-worker 和 fallback 等可见状态;但 BotBrowser 不能打开操作系统分享选择器,不能选择真实来源应用,不能观察用户披露决定,不能扫描外部文件流水线,也不能证明 fixture 之外的服务器完成了删除。BotBrowser 平台文档说明受控浏览器边界。
保持结果窄且明确:记录 accepted-local、duplicate、rawContentKept=false、osShareSource=not-observed 和 serverRetention=not-certified,不要声称观察到了系统选择器或服务器删除。
每次只修改一个变量后重放同一 fixture:action、scope、允许字段、enctype、正文限制、service worker 版本、幂等窗口或回退路由。比较第一个可见状态差异。受控上下文中的通过是页面和请求契约的证据,不是对所有操作系统分享面的普遍兼容性声明,也不是隐私认证。
来源
实践结论
声明一个狭窄的 share_target,用同一套规则解析 GET 与 POST,并把接收和持久化分开。幂等 fixture 与可见回退让浏览器差异变得可审查,却不会把中介交接误认为发送方身份或交付证明。每次修改 manifest、service worker、允许字段或留存策略后,都应重放 fixture。
本文属于指南: Web 平台能力面:一致性复核地图