# 设计文档
## 1. 目标
本插件只解决一个问题:在 DeepSeek Harness 的 HTTP fetch provider 中,为非公网地址过滤增加显式、可审计的 CIDR 白名单,同时维持原生 web_fetch 的工具名称、参数、结果与安全约束。
设计目标:
1. 空白名单与原生 public-only 策略等价;
2. fake-IP 等保留地址路由可以按 CIDR 放行;
3. 可用域名规则作为第二因子缩小例外范围;
4. DNS 校验结果必须直接用于连接,避免 rebinding/TOCTOU;
5. provider 可与原生实现共存,也可在 composition 中做 drop-in 替换;
6. 不改变模型侧 web_fetch schema 和结果呈现。
非目标:
- 不读取或注入 cookie、Authorization、客户端证书;
- 不允许模型设置任意 header、HTTP method 或代理地址;
- 不加载图片、脚本、CSS 等子资源;
- 不支持跨源自动重定向;
- 不把白名单配置暴露成模型参数;
- 不提供 provider fallback 或优先级链;
- 不修改 agent preset 权限边界。
## 2. Cordis 与 DSH 接入面
DeepSeek Harness 的 @deepseek-ai/dsh-web 提供 ctx.web 服务和 fetch provider 注册表。模型侧 @deepseek-ai/dsh-tool-web 只调用 ctx.web.fetch(),因此替换 provider 不会改变工具接口。
~~~mermaid
flowchart LR
M[Model] --> T[web_fetch / dsh-tool-web]
T --> W[ctx.web / dsh-web]
W -->|fetchProvider: http-enhanced| P[dsh-web-fetch-enhanced]
P --> D[resolve all DNS answers]
D --> A[address and hostname policy]
A --> U[pinned Undici dispatcher]
U --> R[bounded text response]
~~~
本插件是 Host 侧 provider:它消费已有的 web 服务并注册一个 fetch provider,但不发布新的 Cordis 服务。因此它属于 Host composition,不属于 agent preset。模型工具仍由 preset 中原有的 tool-web 行提供。
入口采用 namespace plugin 约定,只命名导出 name、inject、Config、安全的 createProvider 和 apply,没有 default export,也不公开低级 pinned transport/custom resolver helper。与 DeepSeek Harness 内部插件一致,源码使用显式 .ts 相对导入,cordis.source.patch.yml 在开发时直接加载源码;发布构建由 TypeScript 生成 lib/types,再由 tsdown 分别输出 Host ESM lib/index.js 和浏览器 Client ESM lib/client.js。Client face 以 web-fetch-enhanced 命名空间键向 settings.plugin.item 注册白名单卡片;Host face 通过 installSettingsSection 将 Profile composition 作为 base layer,并让 provider 在每次请求时读取最新 resolved section。随包发布的 cordis.patch.yml 供操作者显式合并到 Profile composition。
## 3. Provider 选择
ctx.web 的选择是 ID 驱动而不是注册顺序驱动:
- 配置 fetchProvider: http-enhanced 时只选择本插件默认 ID;
- 保留原生 http provider 不会产生歧义,因为选择已显式固定;
- 未配置 ID 且存在多个可用 provider 时会报 WEB_PROVIDER_AMBIGUOUS;
- 两个 provider 注册相同 ID 会立即报 WEB_DUPLICATE_PROVIDER。
所以“覆盖原生”有两种明确模式:
1. 推荐模式:本插件使用 http-enhanced,修改现有 web 行的 fetchProvider;
2. drop-in 模式:禁用原生 provider,本插件配置 providerId: http。
不存在 last-wins 或自动 fallback。
## 4. 请求数据流
一次 fetch 的顺序如下:
1. 检查 URL 长度不超过 2048;
2. 由 WHATWG URL 解析,仅接受 HTTP(S),拒绝 URL 内嵌凭据;
3. 对域名执行一次 lookup(all: true, order: verbatim);IP literal 直接进入相同策略;
4. 验证每个答案的 family 与文本格式;
5. 逐个判断公网单播或白名单例外;任何一个失败就拒绝整个答案集;
6. 若存在 IPv6,解析 ipv4only.arpa 发现活动 DNS64 前缀,并检查嵌入的 IPv4;
7. 为当前请求创建私有 Undici Agent,其 lookup 回调只返回已验证答案;
8. 保持原始 URL hostname,用于 Host header 与 TLS SNI;
9. 手动处理响应;同源重定向回到步骤 1,每一跳重新解析和固定;
10. 验证 MIME 与 charset,按字节和字符上限读取;
11. 返回 WebFetchResult,非 2xx 状态不转为异常。
## 5. 白名单语义
### 5.1 地址条件
allowCidrs 只对非公网地址生效。公网单播地址始终允许,不会因为未命中白名单而被拒绝。
非公网地址必须命中至少一个 CIDR。IPv4-mapped 和废弃的 IPv4-compatible IPv6 会先转换为嵌入的 IPv4,再执行分类与 CIDR 匹配;配置中拒绝 IPv4-mapped CIDR,要求直接写对应 IPv4 CIDR。IPv4 CIDR 只接受四段十进制,IPv6 拒绝 zone ID,且所有 CIDR 必须填写规范的网络基址,避免审计语义歧义。
### 5.2 域名第二因子
allowHostnames 为空时,命中 CIDR 就足以使用例外。非空时,非公网地址还必须命中一个域名规则:
- api.example.com 为精确匹配;
- *.example.com 匹配其子域名,不匹配根域名;
- 只允许最左侧完整标签通配符;
- 规则不得包含端口、反斜杠、路径、查询、片段或凭据分隔符;
- 域名按 URL 规则规范化、转小写并移除末尾点。
域名规则不是 DNS 信任替代物;它只是 operator 配置的第二个静态条件。
### 5.3 NAT64
活动 DNS64 前缀通过 RFC 7050 保留名发现。若 IPv6 答案映射到一个非公网 IPv4,则嵌入的 IPv4 也必须满足同一白名单和域名条件。一个地址同时匹配多个重叠 Pref64 时会验证所有候选,而不是依赖 DNS 返回顺序;这避免内核最长前缀路由与策略选择不一致。
## 6. 配置与生命周期
Cordis Config schema 提供所有默认值;createProvider() 也在直接调用时应用同一默认值。启动阶段校验:
- provider ID 字符集和长度;
- CIDR 与域名规则语法;
- 字节、字符与超时必须为正有限数;
- 超时不能超过 Node timer 上限;
- redirect 上限必须为非负整数。
ctx.web.registerFetchProvider() 返回的注册由 web 服务绑定到当前 Cordis Fiber,插件停止、更新或 HMR 时会自动注销,没有进程级残留。
## 7. 错误契约
插件沿用 DSH 的 WebError:
| code | 含义 |
| --- | --- |
| WEB_INVALID_URL | URL 无效、scheme 不支持或超长 |
| WEB_BLOCKED_URL | 凭据、非公网且未白名单、NAT64 目标未白名单 |
| WEB_REDIRECT_BLOCKED | 跨源、缺少预算或不安全 redirect |
| WEB_FETCH_TOO_LARGE | 声明响应长度超过上限 |
| WEB_FETCH_TIMEOUT | provider 自身资源超时 |
| WEB_UNSUPPORTED_CONTENT_TYPE | 二进制 MIME 或不支持的 charset |
| WEB_ABORTED | 调用方或外层工具策略取消 |
| WEB_PROVIDER_ERROR | DNS、连接、TLS、流读取等 transport 故障 |
模型侧工具的 timeout policy 可能把外层截止时间呈现为 TOOL_TIMEOUT;这是 DSH 已有的两层超时设计。
## 8. 兼容策略
插件只依赖 @deepseek-ai/dsh-web 和 @deepseek-ai/dsh-timeout 的公开包根 API,不依赖 DSH 包的 src/* 深路径。安全 transport 在本包内维护,相关行为通过契约测试固定。
版本升级时重点回归:
- WebFetchProvider 与 WebFetchResult 类型;
- WebError 构造参数;
- deadline/timeout 分类;
- Cordis namespace plugin 的无 default export 约束;
- DSH 原生 provider 的安全策略变化。