嵌入式浏览器功能的权限策略
实践指南:向 iframe 委派浏览器功能,并区分策略、权限与跨源隔离。
BotBrowser Team
Permissions Policy 用于限制文档可以使用哪些浏览器功能。顶层响应定义最大范围,iframe 的 allow 属性再把指定功能委派给子 origin。它是能力边界,不是用户授权、设备创建或业务完成证明。
对于摄像头、麦克风、地理位置和全屏等嵌入功能,应分别记录实际响应策略、最终 origin、权限状态和可见结果。这样可以定位失败阶段,也不会因为某个供应商失败就扩大委派范围。示例比较 https://app.example 与 https://widget.example.
示例比较 https://app.example 与 https://widget.example.
策略控制什么
W3C 规范 将功能名称与 origin allowlist 关联。例如 Permissions-Policy: geolocation=(self "https://widget.example") 允许顶层 origin 和指定 widget。iframe 还必须匹配该 origin:allow="geolocation" 不会让另一个地址自动获得授权。要检查 scheme、端口、redirect 和全部祖先 frame。不同功能的默认值不同,应参考最新 MDN。
策略允许只代表文档可以尝试调用 API。安全上下文、用户手势、用户或管理员决定、设备以及功能选项仍可能导致失败。即便 navigator.permissions 显示 prompt 或 granted,iframe 仍可能被策略阻止。
| 阶段 | 证据 | 不能证明什么 |
|---|---|---|
| Origin 与上下文 | 最终 URL、scheme、端口和安全上下文 | API 一定存在 |
| API 实现 | 针对接口的窄检查 | 调用一定允许 |
| Permissions Policy | 响应头、allow、祖先链、子 origin | 用户授权或设备存在 |
| 平台决定 | prompt、拒绝、管理员设置、设备状态 | 业务结果 |
| 应用结果 | 有界等待内的可见状态和自有确认 | 没有服务端证据的远程记录 |
camera、microphone、geolocation 和 fullscreen 是不同的策略条目。只声明旅程真正需要的功能。多租户系统不要使用 *;在 redirect 后验证最终 origin,并让 allowlist 可审查、可过期。
与 CSP、COOP、COEP 的边界
CSP 通过 script-src、connect-src 和 frame-src 等指令决定哪些脚本、连接、图片和 frame 可以加载。CSP 可能在 Permissions Policy 检查前阻止 frame;frame 即使加载成功,也可能随后被 Permissions Policy 阻止。不要为了补救委派问题而放宽 CSP。
COOP 改变顶层 browsing context 与窗口 opener 的关系。COEP 规定嵌入跨 origin 资源时的 CORS 或 CORP 条件。两者都不会委派摄像头、麦克风或地理位置;Permissions Policy 也不会创建跨 origin 隔离或让资源满足 CORS。先检查网络与 CSP,再按需检查 origin 和隔离状态,然后检查响应头和 allow,最后才进行需要用户手势的功能请求。
这些机制都不替代服务端授权、输入校验、同意流程、无障碍设计或设备控制。API 获准尝试仍不等于应用允许某个账户使用数据流。
一次功能请求有多个阶段
先确认最终 origin 与安全上下文。
再确认浏览器暴露了预期接口。
记录响应头、allow、祖先链和子 origin。
策略通过后,用户手势、权限、管理员设置和设备仍可能拒绝请求。
最后验证应用向用户显示的状态。
设计最小权限的嵌入合同
改 header 前写清顶层 origin、redirect 后的子 origin、功能、目的、用户动作、header 和 markup 负责人,以及可见 fallback。摄像头可以提供手动上传,全屏可以保留普通布局,地理位置可以允许手动输入。拒绝和 timeout 不应丢失焦点或已填数据;任务结束时停止媒体 track,不要静默重复提示。
自有 control/candidate fixture
在自有 host 提供 /fixtures/permissions-policy/control.html 与 candidate.html。两页使用相同 frame、按钮和状态标记;只有 candidate 带有建议的委派 header,control 刻意省略。子页面在明确点击后显示 policy=blocked 或 policy=allowed。固定合成权限,不采集真实位置。
import { test, expect } from '@playwright/test';
const cases = [
{ name: 'control', url: 'https://qa.example.test/fixtures/permissions-policy/control.html', expected: 'blocked' },
{ name: 'candidate', url: 'https://qa.example.test/fixtures/permissions-policy/candidate.html', expected: 'allowed' },
];
for (const scenario of cases) {
test(`Permissions Policy ${scenario.name}`, async ({ page }) => {
try {
const response = await page.goto(scenario.url, { waitUntil: 'domcontentloaded', timeout: 8000 });
expect(response?.ok(), `${scenario.name} HTTP 响应`).toBeTruthy();
} catch (error) {
throw new Error(`NETWORK_ERROR:策略断言前导航失败:${error.message}`);
}
await page.getByRole('button', { name: 'Request location' }).click();
await expect(page.getByTestId('policy-result')).toHaveText(new RegExp(`^${scenario.expected}$`), { timeout: 5000 });
});
}
导航或状态 timeout 只表示在声明窗口内没有证据,不表示策略拒绝。DNS、证书、HTTP 错误或路由缺失必须保留为 NETWORK_ERROR,不能伪装成 blocked。在 teardown 关闭 context 并删除临时 fixture 数据,同时保留最初错误。两条路径的 frame、origin、按钮和状态标记相同,只有委派变化,因此结果可归因。
发布与排查
在 staging 使用与生产一致的路由和 frame origin。经过 redirect、CDN、service worker 和反向代理后检查实际响应头及最终 allow。按第一个失败阶段分类:header 错误是配置问题;frame 加载但 blocked 是策略链问题;allowed 后用户拒绝是权限或浏览器状态问题;API 成功后应用报错属于产品边界;timeout 或 DNS 属于可用性问题。
主动测试删除功能、意外 origin、未列出的 redirect 和没有自身委派的嵌套 frame。保留旧 header、markup 和应用版本以便 rollback。不要删除整个 header 或加入 *;临时例外应记录功能、origin、负责人和期限。
BotBrowser 能验证什么
BotBrowser 可以在受控 browser context 中运行授权的 control/candidate 旅程,隔离合成权限状态,并在指定浏览器构建上比较可见结果。BotBrowser 不会修改已发送的策略,也不会授予权限。多账户隔离文档 说明了独立会话的 context 使用方式。
BotBrowser 不会编写或部署 Permissions-Policy header,不会改变 iframe origin、授予用户权限、提供设备、覆盖 CSP/COOP/COEP、修复供应商响应,也不能证明远程业务已提交。W3C 规范 与应用实际发送的 header 才是权威。策略设计和服务端结果仍由应用、基础设施、供应商及同意流程负责人承担。
不要记录 cookie、凭据、坐标或媒体流内容。
每次授权尝试使用独立的合成账户。
供应商域名改变后重新验证最终 origin。
紧急例外必须有负责人和到期日期。
回滚应同时恢复 header 与 markup。
支持记录应指出第一个失败阶段,而不是笼统写策略失败。
参阅安全上下文指南。
参阅CSP 指南。
同意提示应在用户手势前解释用途。
来源
- W3C: Permissions Policy
- MDN: Permissions Policy
- MDN: iframe
allow属性 - MDN: CSP
- MDN: COOP
- MDN: COEP
- BotBrowser: 多账户隔离
BotBrowser 团队