dsh-web-search-ddg
aooyoo
Zero-token DuckDuckGo search provider for the DeepSeek Harness (DSH) web seam — local headless browser, no API key, no m…
PROJECT TOPICS
PROJECT README
为 DeepSeek Harness Web 提供密码登录网关的插件。
dsh web 本身没有任何认证机制——只要能路由到 Web 端口,任何人都可以调用全部 /api RPC(创建 agent、执行 bash、读写文件系统)。本插件在每一个请求(HTTP 与 WebSocket,包括直接 POST 到后端接口)之前加一道最小密码门禁。
设计与可行性分析见 docs/PROPOSAL.md。
/api/* → 401302 /login/api/events.mux、/api/events.host)→ 拒绝连接/)。这是真正的服务端门禁,不是仅限前端的 UI 锁:未认证的客户端根本到不了后端。
非安全上下文兼容:通过
http://<局域网 IP>(而非 localhost)访问时,浏览器 Web Crypto 的crypto.randomUUID不可用,会导致 dsh 前端报"crypto.randomUUID is not a function"。 插件通过ctx.webServer.tapIndex()向每个 index.html 注入基于crypto.getRandomValues(始终可用)的 polyfill,明文 LAN 部署无需任何额外配置。
浏览器 ──> dsh-password-gate 网关(0.0.0.0:3080,运行在 dsh 进程内)
│ 每个请求都做认证检查(内存会话表,O(1))
├─ 通过 ──> 转发(Host/Origin 改写为回环)──> dsh webserver(127.0.0.1:3081)
└─ 未通过 ─> 401 / 302 / 拒绝 upgrade
本插件是标准的 Cordis 插件,运行在 dsh 进程内:随 dsh 启动而启动、随 dsh 退出而退出,不需要额外的服务进程,也不 fork 核心。它的 bundle patch 会把真实的 webserver 移到仅回环(loopback)的内部端口,使内部端口无法从远程访问,网关成为唯一入口。
关于转发时的 Host/Origin 改写:dsh 内部 /api trust fence 的 LAN 信任列表是根据 webserver 的监听地址(0.0.0.0)采样的;插件把 webserver 钉在 127.0.0.1 后该列表恒为空,外部地址(LAN IP)的请求会被内部 fence 以 403 拒绝。网关因此在转发时把 Host/Origin 改写为回环地址——安全上成立,因为 fence 的"远程可达性防护"已由网关认证层接管:跨站/DNS-rebinding 请求没有会话 cookie(HttpOnly + SameSite=Strict),在网关层即被拒绝,到不了内部。
node:crypto),存于 $DSH_HOME/login-plugin/password.json(仅属主可读写)。x-forwarded-for 伪造无效。429 rate-limited;scrypt 在 libuv 线程池异步执行,登录洪峰不阻塞事件循环(不影响已登录用户的转发)。ctx.logger.warn 日志,并 ctx.emit('dsh-password-gate/brute-force', payload) 广播 Cordis 事件——payload 为 JSON 安全数据({kind: 'lockout'|'global-rate-limit', ...}),任何插件可监听;每个锁定/窗口只提醒一次。锁定不影响正在使用的用户:已登录会话走转发通道,不经过登录端点。dsh_auth,HttpOnly; SameSite=Strict(未加 Secure——MVP 走明文 HTTP)。/login(已登录时直接显示改密表单)。插件不修改 dsh 前端 UI——按 dsh 规范,UI 扩展应通过 ctx.slots.register(client 插件),当前版本保持 host-only 零构建形态。以上策略均为可配置项(bundle patch 或 profile patch 中覆盖 dsh-password-gate 行的 config):
| 字段 | 默认 | 含义 |
|---|---|---|
minPasswordLength |
8 |
密码最小长度(4–128) |
requireMixedCase |
true |
是否要求大小写混合(与特殊字符二选一满足即可) |
requireSpecial |
true |
是否要求特殊字符(与大小写混合二选一满足即可) |
maxLoginFailures |
5 |
连续错误多少次后锁定(1–100) |
lockMinutes |
5 |
锁定分钟数(1–1440) |
maxGlobalAuthAttemptsPerMinute |
60 |
全局每分钟最大登录尝试次数(1–10000) |
前置条件:已安装 dsh,且 web profile 已初始化(首次使用 dsh web 时自动创建)。
dsh plugin --profile web add file:/path/to/dsh-password-gate
dsh plugin --profile web add dsh-password-gate
dsh plugin 支持 pnpm 的全部 spec(file:、link:、git:、tarball、registry 包名)。
# 1. 查看组合树:webserver 应变为 host 127.0.0.1、port 3081,并多出 dsh-password-gate 行
dsh web --dump-config
# 2. 启动
dsh web
# 3. 浏览器打开 http://<host>:3080 —— 应看到"设置密码"或"登录"页面
插件包的 package.json 声明了 "dsh": { "bundle": { "patch": "./cordis.patch.yml" } }。dsh plugin add 检测到 dsh.bundle 声明后,会自动把该包加入 profile 的 bundle 层叠列表(dsh plugin 的 reconcile 逻辑),启动即挂载:
cordis.patch.yml 会把 webserver 行改为 127.0.0.1:3081(回环内部端口),并插入 dsh-password-gate 插件行;cordis.patch.yml、$DSH_HOME/cordis.patch.yml 及 --patch overlay 覆盖。若不想使用 bundle 声明(比如要手动管理补丁),也可以只把包作为普通依赖安装,然后手动在 $DSH_HOME/profiles/web/cordis.patch.yml 中加入以下两段(效果等同):
- id: webserver
config:
host: 127.0.0.1
port: 3081
- insert:
- id: dsh-password-gate
name: dsh-password-gate
config:
listenHost: 0.0.0.0
listenPort: 3080
upstreamHost: 127.0.0.1
upstreamPort: 3081
dsh plugin --profile web add:幂等,无影响。pnpm 不会重复写入依赖(package.json 同名 key 唯一),reconcile 检查 bundles.includes() 也不会重复添加层。insert 了 id: dsh-password-gate,patch 的顶层 insert 不查重):loader 会把插件挂载两次,第二个网关绑定同一对外端口时必然 EADDRINUSE,启动明确失败(fail loud,不会静默半坏)。第一个实例继续正常工作。删除重复行即可恢复。id 替换整行 config,后层覆盖前层,幂等。卸载分两步:移除组合 + 清理数据。
dsh plugin --profile web remove dsh-password-gate
该命令会移除插件依赖,reconcile 逻辑自动把它从 bundle 层叠列表摘除,其 bundle patch 随之失效——webserver 恢复默认的 0.0.0.0 / 3080 配置。
注意:
dsh plugin remove只是移除组合行,不会执行插件代码,因此密码文件不会被自动删除。
为什么数据保留是刻意设计:Cordis 的可逆副作用(
ctx.effectdisposer)负责撤销注册行为——网关服务器、polyfill 注入等卸载时自动消失;而持久化数据(密码哈希文件)属于用户数据,生命周期由用户决定,不由插件装载周期决定。这与 dsh 生态一致:settings-file、credentials-local、session-persistence-jsonl等插件卸载后,其settings.yaml、.credentials.yaml、.sessions/数据同样保留。数据保留的好处:重装后无需重新设置密码(连续性);且卸载后没有网关就没有门禁,残留的仅是磁盘上无用的 scrypt 哈希字节(0600 权限),无安全危害。想要彻底清除,执行下面的第二步即可。
停止 dsh web 后,删除插件的数据目录(密码哈希文件所在):
# 方式一:运行包自带的清理命令(安装后已链接到 profile 的 bin)
dsh-password-gate-uninstall
# 方式二:手动删除
rm -rf "$DSH_HOME/login-plugin"
插件在运行中被卸载(组合变更、热重载、进程退出)时,会通过 ctx.effect 注册的 disposer 自动关闭网关(端口与连接),无需手动处理——参见 index.js。
密码丢失后无法通过 /login/change 改密(需要旧密码),只能本地重置:删除密码记录,网关回到"设置密码"状态。这是本机可信模型下的正解——能物理访问本机的用户本就拥有全部权限,密码文件也仅是 scrypt 哈希(非明文、0600)。
# 方式一:运行包自带的重置命令(安装后已链接到 profile 的 bin)
dsh-password-gate-reset
# 方式二:手动删除
rm -f "$DSH_HOME/login-plugin/password.json"
然后打开 Web UI,会再次出现"设置密码"页面,设置新密码即可。
几点说明:
dsh web 即可);dsh-password-gate-uninstall 才是彻底移除插件(含数据);$DSH_HOME 未设置,默认目录为 ~/.dsh。dsh web --port <N>(推荐)插件的 bundle patch 跟随 ctx.webStartup(与 web-app 自身的 webserver 行同一机制),所以端口由命令行 flag 控制:
# 对外 URL 8080,内部 webserver 自动挪到 8081
dsh web --port 8080
# 默认(不传 flag):对外 3080,内部 3081
dsh web
推导规则:对外端口 = --port(默认 3080),内部端口 = 对外 + 1。端口范围 1–65534(内部端口 +1 后仍须合法,否则插件配置校验会在启动时明确报错)。
不支持
--port 0(让 OS 自动分配端口):网关需要固定的内部端口才能转发。
--host 0.0.0.0dsh web --host 0.0.0.0 会被 dsh 直接拒绝并报错(web-app 内置的安全限制,startup.ts 硬编码:--host 0.0.0.0 is intentionally not supported yet for safety),这是 dsh 代码层的限制,配置无法解除,也不需要解除——装了本插件后:
127.0.0.1(安全关键:内部端口一旦对外,就绕过了登录网关);listenHost 承担,默认已是 0.0.0.0,不经过 web-startup 的校验。所以对外暴露的正确姿势是:不要传 --host,直接用默认(网关默认监听所有网卡)。
如果只想本机访问(不要对外监听),把 bundle patch(或 profile 自己的 patch)里 dsh-password-gate 行的 listenHost 改为 127.0.0.1 即可。
--port)若不想依赖 flag,可在 profile 自己的 cordis.patch.yml 中覆盖 webserver 与 dsh-password-gate 两行(你的补丁层优先级高于 bundle 层),注意 webserver.port 必须等于 dsh-password-gate.config.upstreamPort。手动覆盖后 --port flag 将不再生效(你的值优先)。
对运行中的实例执行完整浏览器流程(设置密码 → 首页加载 → 登出/登录 → 改密/重登录,并断言零 JS 错误,会真实修改密码,最终密码为 PASSWORD-2):
BASE=http://127.0.0.1:8002 PASSWORD=your-password node scripts/e2e.mjs
脚本自适应"首次设置"与"已配置"两种状态;需要本机有 playwright(npm i -D playwright)与 chromium。
对运行中的实例执行自动化流程(会真实修改密码,最终密码为 PASSWORD-new):
BASE=http://127.0.0.1:3080 PASSWORD=your-password ./scripts/verify.sh
或参见 docs/PROPOSAL.md §7 的手动检查清单。
单元测试与插件契约测试:
npm test
None,本包是浏览器与内部 dsh webserver 之间的 Web 载体,不会进入任何模型请求。
None;本包既不组装也不发送 provider 请求。
:3081。可接受——威胁模型是远程访问。本项目由 DeepSeek Harness(dsh) 完成开发与测试:从方案调研、源码分析(deepseek-harness 的 Cordis 插件机制、webserver/connection 源码、graphify 图谱)到实现、实机部署与端到端验证(curl + Playwright),全部由运行在 dsh 上的编码智能体完成。
单次开发会话(模型 deepseek-v4-flash)消耗:
| 指标 | 数值 |
|---|---|
| 开发时长 | 约 93 分钟 |
| 轮数(turn) | 20 |
| 步数(step) | 381 |
| 工具调用 | 393 |
| 输入 token(新增) | 206,172 |
| 缓存命中 token(KV cache) | 95,108,864 |
| 缓存命中率 | 99.8% |
| 输出 token | 243,803(其中推理 121,760) |
| 总 token(输入 + 输出) | ≈ 9,556 万 |
说明:缓存命中数据来自模型提供方返回的 prefix-cache(KV cache)指标;高命中率源于长会话中每步输入前缀的稳定复用。
MIT
CLASSIFICATION EVIDENCE
系统优先读取 GitHub Topics,再与站内分类词典和词根规则比对。