Crawler Port 隔离了 Selenium 类型,但没有自动建立安全浏览器平台。当前接口围绕一个隐含“当前页面”工作,适合受信任的内部自动化原语,不适合直接暴露给多租户请求。
1. 当前四个操作
| Port 方法 | 输入 | 输出/作用 |
|---|---|---|
OpenUrlAsync | URL、可选 TenantId | 改变当前浏览器页面 |
FindElementAsync | ID/Name/Class/Tag/CSS/XPath | 元素文本、标签、属性快照 |
ScreenshotAsync | 可选 TenantId | URL、标题、PNG 字节 |
ExecuteScriptAsync | 脚本、参数 | 任意脚本返回值 |
TenantId 不是安全边界:OpenUrl Adapter 没有使用它,Screenshot 只写 Debug 日志。
2. 隐式会话模型
Port 没有 browser/session handle,也没有把 open→find→script→screenshot 绑定成原子工作单元。若底层服务是 Singleton,共享 current driver 会产生页面串扰、Cookie 泄露和竞态。
3. 当前打开 URL 的验证
Adapter 只拒绝空 URL。当前没有:
http/httpsscheme allowlist;- 域名或租户目标 allowlist;
- DNS 解析后私网、loopback、link-local 拒绝;
- 非标准端口策略;
- 重定向逐跳复验;
- 用户信息、混淆 IP、DNS rebinding 防护;
- 下载大小和响应类型限制。
因此直接传入用户 URL 会形成 SSRF 风险,包括云元数据、内网管理面和本机服务。
4. 生产 URL 策略示例
// ① 解析并规范化;禁止相对 URL、userinfo 和非 HTTP(S)。Uri target = await urlPolicy.ParseAndValidateAsync(command.Url, cancellationToken);
// ② DNS 解析后的每个地址都必须通过网络范围策略。await urlPolicy.AssertPublicOrTenantAllowlistedAsync(target, cancellationToken);
// ③ 分配隔离会话后再调用当前 Port;当前 Port 自身不会执行上述检查。Result opened = await crawler.OpenUrlAsync( new CrawlerOpenUrlRequest(target.ToString(), trustedTenantId), cancellationToken);重定向必须在网络层逐跳验证;只验证初始 URL 不足以阻止跳入私网。
5. 脚本执行边界
ExecuteScriptAsync 只检查脚本非空。没有脚本模板、操作白名单、超时沙箱或返回值大小限制。若脚本来自请求,调用者相当于获得浏览器上下文中的任意代码执行权。
推荐让业务请求选择预注册操作,例如 ReadInvoiceTotal,由服务端映射到固定脚本。参数只允许 JSON 基元和经过验证的 selector,不接受任意脚本文本。
// ① 客户端提交操作名和数据,不提交 JavaScript。CrawlerScriptDefinition definition = scriptCatalog.Get(command.Operation);
// ② 目录定义允许域、参数 schema、最大耗时和输出上限。definition.Validate(command.Arguments, currentTarget);
// ③ 只有服务端拥有的脚本进入 Port。return await crawler.ExecuteScriptAsync( new CrawlerExecuteScriptRequest(definition.Script, command.Arguments), ct);6. Locator 风险
CSS 与 XPath 可触发昂贵查询;XPath 还可能越过预期区域读取页面敏感内容。应按业务操作限制 locator 类型、长度、数量和查找范围。
FindElementAsync 返回完整 href。带 token 或查询参数的 URL 不应原样进入日志、审计或客户端,应返回规范化/脱敏投影。
7. 返回值可序列化性
Adapter 只把“直接返回的单个 IWebElement”转成字符串。元素集合、字典或嵌套对象仍可能携带 Selenium 类型,导致序列化失败或泄漏实现。
生产边界应只允许显式 JSON schema:字符串、数字、布尔和有界数组。未知对象应拒绝,而不是交给通用序列化器猜测。
8. 取消与超时
同步 Selenium 调用由 Task.Run(..., cancellationToken) 包装。Token 可以阻止任务开始,但任务一旦执行,取消并不会中断 WebDriver 调用。
必须同时配置:
- WebDriver page-load/script/implicit timeout;
- 业务工作单元 deadline;
- 超时后 driver 废弃或恢复策略;
- 浏览器进程 watchdog;
- 并发队列和租户配额。
不要把 CancellationToken 参数误写成底层操作可立即取消。
9. 异常映射
Adapter 捕获 WebDriverException、NoSuchElementException、NotFoundException 和 TimeoutException,返回 ProviderFailed,并把原始异常消息放进描述。
调用层需要稳定分类为目标拒绝、导航超时、元素未找到、脚本失败、会话损坏等;原始消息只进入受限诊断日志。未知异常仍由全局异常处理兜底。
10. 会话隔离设计
GA 推荐引入显式会话:
会话至少绑定 TenantId、ActorId、允许域、创建时间、deadline 和并发所有权。完成后清理 Cookie、local storage、下载、缓存和临时 profile;高敏业务使用一次性容器/进程。
11. 测试矩阵
必须覆盖:
- loopback、RFC1918、IPv6 local、link-local、元数据地址;
- 十进制/十六进制 IP、userinfo、重定向、DNS rebinding;
- 两租户并发 open/screenshot 不串页;
- Cookie/storage 不跨会话;
- 超时后 driver 不回到可用池;
- 任意脚本、超长 selector、巨大输出被拒绝;
- 元素集合返回被规范化;
- Provider 错误不向客户端泄露路径/堆栈。
当前源码没有这些测试,因此现阶段只能把 Port 作为受信任内部原语。
12. 运维清单
限制浏览器进程 CPU/内存/文件描述符;固定浏览器与 driver 兼容版本;及时修补;网络出口默认拒绝;下载目录使用隔离临时盘;指标记录队列、活动会话、超时、崩溃和清理失败。
rg -n "OpenUrlAsync|ExecuteScriptAsync|Task.Run|IWebElement" \ src/Platform/ToolConnectors -g '*.cs'