鱼皮AI导航
鱼皮AI导航
  • 主页
  • 社区
  • AI工具
  • AI知识库
  • AI提示词
  • MCP
  • Skills
  • AI学习资源
  • AI资讯
  • GitHub开源
登录/注册

dsh-plugins#web-fetch

ctx.web 接口的本地受控抓取提供方——dsh 自带完整的 web_fetch 工具,却没有随附后端。每一跳重定向都会重新解析并对照非公网地址段校验,另有流式大小上限与内容类型白名单。

网页浏览
访问 GitHub
文档README

@creait/dsh-web-fetch

The backend DeepSeek Harness's web_fetch tool is missing.

Why this exists

@deepseek-ai/dsh-tool-web already implements the entire model-facing web_fetch tool: argument schema, validation, timeout budget, HTML→markdown rendering, output cap with a truncation footer, and the fetch card the web UI draws. What dsh does not ship is a provider for the fetch half of the ctx.web seam, so dsh-base mounts the tool with fetch: false and the capability is inert.

The bundle says why:

Fetch stays disabled and no fetch provider is mounted: that provider defers SSRF protection and the model would choose the request target.

That is a statement about ownership, not a verdict that fetching is unsafe. The provider is where the guards belong, and dsh's own error taxonomy names the set it expects one to carry — "invalid or blocked URLs, redirects, size and timeout limits, and unsupported content types." This package is that provider.

Install

▼
bash
复制代码
dsh plugin --profile web add @creait/dsh-web-fetch

That mounts the provider and registers it with ctx.web. It does not turn on web_fetch: the tool belongs to tool-web, which dsh-base mounts with fetch: false because no provider shipped. Turn it on in your profile patch:

▼
yaml
复制代码
- id: tool-web config: fetch: true

Restart dsh — the boot manifest is assembled at startup.

Nothing has to name the provider. dsh ships none of its own, so local is the only fetch provider registered and the seam selects it on that basis. Name it explicitly only if something else registers a second one, which is when the seam refuses to guess:

▼
yaml
复制代码
- id: web config: fetchProvider: local

Under dsh web, fetch: true on that row is not enough on its own: dsh-web-app disables the host-plane tool-web, and each agent preset mounts its own copy with fetch: false. Revive the host row instead of forking every preset, and give it only the half the presets do not own — the tool registry throws on a duplicate tool name:

▼
yaml
复制代码
- id: tool-web disabled: false config: search: false fetch: true

If you installed this before it shipped a bundle patch, your profile patch inserts the row by hand. Drop that - insert: block: insert appends unconditionally, and the second row would register the same provider id twice, which ctx.web rejects with WEB_DUPLICATE_PROVIDER.

Guards

GuardBehaviour
Schemehttp: and https: only
CredentialsURLs carrying user:pass@ are refused outright
AddressEvery resolved address is checked against loopback, RFC 1918, CGNAT, link-local (incl. 169.254.169.254), multicast, reserved and IPv6 ULA/link-local ranges
IPv4-in-IPv6::ffff: and 64:ff9b:: addresses are unwrapped and judged as the IPv4 they carry
Multi-homed namesA hostname resolving to any blocked address is refused — which address fetch would pick is not ours to decide
RedirectsFollowed by hand, capped at maxRedirects, and every hop is re-resolved and re-checked
SizeBody is streamed and cut at maxBytes; the result reports truncated
Content typeHTML and text-ish types only; anything else is an error rather than mojibake
CancellationThe seam's AbortSignal is honoured before and during the request

A non-2xx response is a result, not an error — the status code is part of the resource's state, and the seam's contract says so.

Config

KeyDefaultMeaning
maxBytes5242880Ceiling on the buffered body
maxRedirects5Hops followed before refusing
allowHosts[]Exact hostnames allowed to resolve into blocked ranges
blockHosts[]Exact hostnames always refused
userAgentdeepseek-harness/0.0.1 (web fetch)Sent on every request

allowHosts is the deliberate escape hatch for reaching something on your own network — a LAN wiki, an internal dashboard:

▼
yaml
复制代码
- id: web-fetch config: allowHosts: ['wiki.lan', 'dashboard.lan']

Known limitation: DNS rebinding

The window between resolving a hostname and the connection being made is not closed. A name whose DNS flips to a private address inside that window would be checked as public and connected as private, because global fetch re-resolves on its own and offers no pinned-address dispatcher.

Closing it needs a custom undici dispatcher whose connect re-validates the resolved peer, which is a real dependency this package does not currently take. On a trusted workstation network the residual risk is small — but it is not zero, and allowHosts/blockHosts are the levers if your threat model needs them tighter.

Tests

▼
bash
复制代码
node --test test/*.test.js

Covers the address classifier range by range (including the IPv4-in-IPv6 unwrap paths, where a subtle parser bug would have let 64:ff9b::169.254.169.254 through) and drives the provider against a stubbed fetch for redirect re-validation, hop caps, allow/block lists, content-type handling and cancellation.

Licence

MIT

网页浏览

快捷安装

复制到终端去安装插件

dshplugin--profilewebaddgithub:CREAIT-nl/dsh-plugins#path:/web-fetch

温馨提示

装插件等于在本机跑第三方代码,权限与你相同,能读文件、用凭据、访问网络,工具审批管不到。请只装可信来源,并尽量锁定 commit。

站长:程序员鱼皮 老鱼简历 商务合作 增值电信业务经营许可证
2026 鱼皮AI导航 | 沪ICP备19026706号-6
站长:程序员鱼皮 老鱼简历 商务合作 增值电信业务经营许可证
2026 鱼皮AI导航 | 沪ICP备19026706号-6
dsh 插件推荐
dsh-chrome-cdp
Chrome DevTools Protocol 插件 for DeepSeek Harness。通过 chromremote-interface 以 CDP 连接并操控 Chrome。连接带有图形界面。
dsh-webmcp
WebMCP 桥接器:让 agent 通过内置无头 Chromium,发现并调用网站按 W3C WebMCP 协议暴露的站点工具。
dsh-web-search-ollama
让 DSH 的 web_search 工具走 Ollama 联网搜索 API,复用 OLLAMA_API_KEY,无需 DeepSeek 官方 Key。
dsh-web-search-tavily
为原生 web_search 提供 Tavily 搜索后端,并在设置 → 插件中提供 API Key 管理与是否替换 DeepSeek 的开关。
dsh-search-hub
可配置的原生 web_search 后端:在 DeepSeek 官方原生搜索与智谱 GLM web_search 之间切换当前生效入口,并在设置页管理搜索入口与凭证引用。
🔥热门工具
朱雀AI检测朱雀AI检测
朱雀AI检测助手朱雀AI检测助手
LovartLovart
老鱼简历老鱼简历
WritePass官网WritePass官网
面多多面多多
CursorCursor
星月写作星月写作
零感AI零感AI
墨得问题墨得问题