WebHID 设备权限与隐私边界
通过用户操作请求 WebHID,校验集合与报告,处理断开连接,并限制设备元数据的留存。
BotBrowser Team
WebHID 允许页面请求用户选择的人机接口设备,但获得权限不等于设备清单,也不能证明设备归谁所有。BotBrowser 可以在受控 context 中执行页面自有的权限分支和备用路径,但不会制造实体控制器、绕过浏览器授予权限,也不能证明硬件 I/O。安全契约应保持明确:由用户操作发起,只检查所需集合,限制每条报告,协调断开状态,并让任务在没有 HID 时仍可用。
TL;DR
navigator.hid.requestDevice({ filters: [{ usagePage: 0x01 }] })需要安全上下文和短暂的用户激活。- 选择器授权会暴露一个
HIDDevice,但不能证明设备已连接、安全、属于某人,或适用于全部集合。 - 打开前检查
device.collections,并把inputreport字节当作不可信数据。 disconnect会结束当前关系;关闭 listeners、保留用户工作,并要求用户明确重试。- 保存
granted、unsupported或disconnected等结果,不保存序列号、报告历史或推断身份。
目录
在使用时请求
WebHID 仅限安全上下文,requestDevice() 应在短暂用户激活期间执行。把请求放在名称清晰的控件后面。能力检测只说明方法存在,不会打开选择器或授予访问权;浏览器策略、iframe、操作系统或平台仍可能拒绝请求。
async function connectHid() {
if (!window.isSecureContext || !navigator.hid) return showHidState('unsupported');
setHidState('requesting');
try {
const devices = await navigator.hid.requestDevice({
filters: [{ usagePage: 0x01, usage: 0x06 }],
});
if (devices.length === 0) return showHidState('no-device');
await openSelectedHid(devices[0]);
} catch (error) {
showHidState(error.name === 'NotAllowedError' ? 'denied' : 'failed');
}
}
connectButton.addEventListener('click', connectHid);
不要在页面加载时打开选择器,也不要在取消后循环请求。保留草稿,只有新的用户操作才提供下一次请求。WebUSB 设备访问:权限、接口与隐私区分授权、打开、声明接口和清理。Web Bluetooth 权限、设备选择与隐私把同一用户意图边界用于另一种设备模型。
在处理报告前检查集合
选择后,HIDDevice 会暴露元数据和 collections。只打开受支持的设备,并将 usagePage、usage 与报告描述符和目标操作比较。产品名不等于全部报告的授权,也不能证明身份、地点或物理距离。
维护允许的 usage 和报告大小清单。集合不匹配时应停止,而不是扩大访问范围。不要把 productName、vendorId、productId 或 serialNumber 复制到 URL、账户、分析字段或常规支持日志;它们即使不暴露所有者,也可能区分具体设备。
集合映射是动态能力视图。设备可能成功打开后在下一步失败,固件更新后改变报告布局,或在第一条报告前消失。说明准确的支持操作,没有合适集合时保留普通页面路径。
限制报告并协调断开
inputreport 会交付所选设备的字节。改变状态前校验 reportId、长度、取值范围和新鲜度。设置最大载荷,拒绝意外 ID,并在渲染或发送前限制高频输入。
const MAX_REPORT_BYTES = 64;
function acceptInputReport(event) {
const bytes = new Uint8Array(event.data.buffer);
if (bytes.length === 0 || bytes.length > MAX_REPORT_BYTES) return;
if (event.reportId !== 1 || bytes[0] > 100) return;
updateControllerState({ reportId: event.reportId, value: bytes[0] });
}
device.addEventListener('inputreport', acceptInputReport);
注册 navigator.hid.addEventListener('disconnect', ...),让清理可重复执行。在等待 close() 前先将任务标记为断开,移除报告 listeners,保留已输入数据,并显示明确的重新连接控件。延迟报告不能把断开页面重新标记为就绪。不要自动重连或再次发送上一条命令。
最小化元数据并保留备用路径
把浏览器权限、HID 会话和上传同意分成不同决定。本地读取不代表允许上传服务器;应单独询问,说明离开浏览器的字段,并在用户拒绝上传时让本地任务完成。
运行记录通常只需要 unsupported、denied、no-device、connected、invalid-report 或 disconnected、时间和测试环境。原始报告、稳定标识符、完整集合图和长期连接历史通常没有必要。诊断记录应短期、可见、受控并自动删除。
保留键盘表单、手动值、文件导入或静态配置作为无障碍备用路径。不要让登录、计费、账户恢复或必需内容依赖实体 HID 设备。
用决策表分类结果
| 观察阶段 | 能证明什么 | 安全下一步 |
|---|---|---|
| API 缺失、不安全上下文或策略阻止 | 当前无法启动 WebHID | 保留非 HID 任务并解释环境限制 |
| 选择器取消或权限拒绝 | 本次没有可用授权 | 保留草稿,等待新的明确操作 |
| 设备已选但集合不支持 | 缺少所需能力 | 关闭会话、显示要求,不扩大过滤器 |
| 长度、ID 或范围无效 | 输入未通过应用协议 | 丢弃,记录 invalid-report,继续任务 |
disconnect 事件 | 当前关系结束 | 移除 listeners、尽力关闭、保留数据并让用户重试 |
| 有效且受限的报告 | 页面接受了一个应用值 | 只报告该值,不推断所有者、地点或身份 |
每一行都应回到可理解的页面状态。权限不是连接,集合不是可信控制器,有效报告也不是实体操作证明。
BotBrowser 的能力与限制
BotBrowser 的受控 context 可以验证连接按钮、能力检测、集合过滤、有限报告、断开清理和无 HID 备用路径。高级能力文档记录了可在该 context 中检查的浏览器能力。结果仍是页面级证据:BotBrowser 不提供 HID 设备、不授予操作系统权限、不保证报告,也不能证明某人拥有或触摸控制器。
{
"api": "navigator.hid",
"permission": "denied",
"collectionsAccepted": 0,
"reportsAccepted": 0,
"rawIdentifiersRetained": false,
"physicalDevice": "not-asserted"
}
改变一个变量后重跑 fixture,例如浏览器版本、安全上下文策略、权限结果、集合过滤器或模拟断开。比较第一个可见状态差异,不要据此承诺通用硬件支持。
可观察状态
测试保留页面可见的状态序列,不把页面结果变成实体硬件主张。
硬件边界
实体验收需要批准的设备,并且应在独立于页面 fixture 的环境中完成。
来源
实践结论
使用明确的选择器操作,只检查支持的集合,校验每条报告,并在断开后关闭 listeners。让核心任务在没有 HID 时仍完整可用,记录失败阶段而不是设备标识符。浏览器、策略、固件或权限变化后重跑 fixture;页面成功不代表实体硬件行为。
本文属于指南: Web 平台能力面:一致性复核地图