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
English: README.en.md · 简体中文
面向 DeepSeek Harness(DSH)的安全认证插件。
它在 DSH Web UI 前加一道登录门,并提供 Nginx auth_request 可强制校验的 /auth API,
让局域网或公网部署获得身份验证与防暴力破解能力,且无需改动 DSH 本身。
对接真实的 DSH 插件 API(
ctx.webServer.register、Cordis 槽位系统、dsh.client打包契约)实现。
外网用户 → Nginx (HTTPS + 限流)
→ auth_request (Nginx 层认证校验, 子请求到 /auth/verify)
→ DSH Web UI (插件登录门 + 登录页)
DSH 内部: dsh-auth-gate 插件注册 /auth/* 路由
- GET /auth/captcha → 图形验证码 { svg, uuid } (一次性)
- POST /auth/login → 校验 验证码+账号+密码 → { token }
- GET /auth/verify → 校验 Token (供 Nginx auth_request 调用)
- POST /auth/logout → 销毁会话
- POST /auth/username → 修改自己的账号名称 (需登录)
- POST /auth/password → 修改自己的密码 (需登录, 校验旧密码)
- GET /auth/security → 安全设置状态与实时数据 (需登录)
- POST /auth/security → 修改安全设置并持久化 (需登录)
两层相互独立的强制校验:
| 层 | 机制 | 说明 |
|---|---|---|
| Nginx 层 | auth_request /_auth → 子请求 GET /auth/verify |
没有合法 Token 的请求在到达 DSH 之前就被 401 拒绝 |
| 插件层 | 客户端登录门 + 服务端会话 | 浏览器打开页面时校验 Token;未登录时整个 UI 被登录页遮挡,服务端不签发会话 |
0o1i 等易混淆字符;插件级按 IP 限流(每 60 秒 60 次,存储上限 5000 条),无 Nginx 前置时同样有界blockDuration 窗口内的连续失败(验证码错误、验证码过期/无效或密码错误均计数)达到 maxLoginAttempts 后锁定 blockDuration 秒;另有 Nginx 限流兜底blockDuration 窗口内的凭据失败达到 accountMaxLoginAttempts 后锁定该账号,IP 轮换无法绕过;验证码错误不计入此计数(避免被用于投毒锁定他人账号)/auth/verify 每次调用顺延 sessionTimeout)bindSessionToIp),在其他 IP 上使用立即失效并销毁会话,被 XSS/日志窃取的 Token 无法异地使用;默认关闭(客户端 IP 不固定的部署保持关闭,否则 IP 变化会强制下线)singleSessionPerUser,默认开启):在别处再次登录会立即使该账号之前的所有会话失效(旧 Token 下次校验即 401,被挤下线),登录响应中的 kickedPrevious 标记可让新登录方感知这一行为username:bcrypt_hash,权限 0600,新哈希轮数 12),绝不存明文;创建/重置密码强制至少 8 个字符/auth 请求体必须为 application/json(否则 415),且限 64 KB(否则 413)/auth/* 路由;Client 端通过 DSH 官方 Slot 机制注册登录页(root slot 优先级 -1 覆盖布局,登录成功后自动释放)X-Forwarded-For 获取真实客户端 IP;仅当直接对端是回环地址(同机 Nginx)时才信任该头,且取代理追加的最后一项,客户端伪造的前缀无法绕过锁定开箱即用(默认凭据):如果启动时密码文件
~/.dsh/auth.passwd里没有任何账号, 插件会自动创建初始账号admin,默认密码为admin123,并在本次 DSH 启动日志中打印一次。 默认凭据是公开值:首次登录会被强制修改账号名与密码后才能进入系统, 且admin这个名称之后被保留禁用(任何账号都不能改名为它,改名后旧名立即失效)。 不需要此机制时,在配置中将autoProvisionAdmin设为false(见「配置」)。
# 1. 将插件添加到 profile(会作为 profile 的依赖安装)
dsh plugin --profile web add dsh-auth-gate
# 2.(可选,推荐)创建带 bcrypt 哈希的密码文件(每行一个用户)——不执行此步则使用上面的默认账号
mkdir -p ~/.dsh
npx dsh-auth-passwd set admin # 交互式输入密码,权限 0600
# 或手动生成(⚠️ 明文会出现在 shell 历史与进程列表中,仅限一次性使用):
node -e "console.log(require('bcryptjs').hashSync('你的密码', 10))" > ~/.dsh/auth.passwd
# 3. 重启 DSH
dsh web
DSH 启动时,插件的 cordis.patch.yml 会把 dsh-auth-gate 条目写入 profile,
客户端部分由 Web 客户端注册表(dsh.client 声明)自动加载。打开 Web UI 即可看到登录门。
本地开发安装:
dsh plugin --profile web add ./path/to/dsh-auth-gate。
# 获取验证码
curl http://127.0.0.1:3080/auth/captcha
# 登录(将验证码答案和 uuid 替换为上一步返回的值)
curl -X POST http://127.0.0.1:3080/auth/login \
-H "Content-Type: application/json" \
-d '{"username":"admin","password":"your-password","captcha":"abcd","uuid":"<uuid>"}'
# 校验 Token(Nginx auth_request 即调用此接口)
curl -H "Authorization: Bearer <token>" http://127.0.0.1:3080/auth/verify # → 200 + X-Auth-User
# 防暴力破解:连续 5 次错误 → 429,并返回 blockedUntil
所有选项都在插件条目的 config 中设置(可在 profile 的 cordis.patch.yml 或
--patch 覆盖层中修改):
| Key | 默认值 | 说明 |
|---|---|---|
passwordFile |
~/.dsh/auth.passwd |
密码文件路径(~ 展开为操作系统用户主目录) |
sessionTimeout |
3600 |
会话有效期(秒),滑动窗口续期 |
captchaExpires |
300 |
验证码有效期(秒) |
maxLoginAttempts |
5 |
同一 IP 连续失败多少次后锁定 |
accountMaxLoginAttempts |
10 |
任一账号(不限 IP)凭据连续失败多少次后锁定该账号(防 IP 轮换;验证码错误不计入) |
blockDuration |
300 |
锁定持续时长(秒,按 IP 与按账号共用) |
bindSessionToIp |
false |
会话是否绑定登录时的客户端 IP(Token 离开该 IP 立即失效);默认关闭(本部署客户端 IP 不固定);仅当客户端 IP 固定时可设为 true |
singleSessionPerUser |
true |
单点登录:同一账号再次登录会使之前所有会话立即失效(旧 Token 下次校验即 401,即"只能在一个地方登录,其它地方登录后前面登录的掉线");需要多处同时登录可设为 false |
trustProxy |
false |
是否信任 X-Forwarded-For(仅当 Nginx 与 DSH 同机、直接对端为回环地址时生效;取代理追加的最后一项) |
devCaptchaText |
false |
仅限开发 — 在 /auth/captcha 中回显验证码答案,便于 curl 调试;生产环境切勿开启 |
defaultAdminUser |
admin |
初始账号的用户名(仅当密码文件为空且 autoProvisionAdmin 开启时自动创建)。该名称是保留名:登录时会被强制改名,且任何账号都不能改名为它 |
defaultPassword |
admin123 |
初始账号的默认密码(公开值;首次登录强制修改;至少 8 个字符) |
autoProvisionAdmin |
true |
启动时若密码文件里没有任何账号,是否自动创建初始账号并把默认密码打印到日志 |
profile 覆盖示例:
# ~/.dsh/profiles/web/cordis.patch.yml
- id: dsh-auth-gate
config:
sessionTimeout: 7200
maxLoginAttempts: 10
blockDuration: 600
除 passwordFile 与 devCaptchaText 外,其余选项(sessionTimeout、captchaExpires、
maxLoginAttempts、blockDuration、trustProxy、singleSessionPerUser)都可以在登录后通过
设置 → 个人配置 → 安全设置 在线修改:修改立即生效,并持久保存到
密码文件同目录的 auth-gate.security.json(权限 0600),重启后依然有效;
profile 中的值作为基准层,运行时覆盖层在其之上。为保证安全,数值有上下限
(会话超时 60–86400 秒、验证码有效期 30–3600 秒、失败阈值 1–100 次、锁定
时长 30–86400 秒),越界请求会被拒绝;devCaptchaText 刻意不提供在线开关
(仅限开发,任何环境都不应在生产开启)。
格式:每行一个 username:bcrypt_hash(以 # 开头为注释),权限 0600。
npx dsh-auth-passwd hash # 打印一个哈希,供手动使用
npx dsh-auth-passwd set <user> # 添加/更新用户(交互式输入密码)
npx dsh-auth-passwd list # 列出用户
npx dsh-auth-passwd delete <user> # 删除用户
密码策略:
set与hash均要求密码至少 8 个字符(与 Web 端修改密码一致)。
初始账号自动创建:当
autoProvisionAdmin开启(默认)且密码文件中没有任何账号时, 插件启动时会创建defaultAdminUser(默认admin)并写入defaultPassword(默认admin123) 的 bcrypt 哈希(权限 0600),同时在本次启动日志中打印一次默认密码; 首次登录会被强制修改账号名与密码(改名后旧名称admin立即失效,且任何账号都不能再改名为它)。 已有账号的密码文件永远不会被改动。
登录后打开左下角 设置 面板,导航栏第一项即为 个人配置(通过官方
settings.section 槽位注册,位于「通用设置」之上),提供账号自助与安全设置:
修改用户名、修改密码、安全设置、退出登录;样式使用 shell 的
--dsw-* 设计变量,自动跟随明暗主题。
安全设置 卡片展示每项防护措施的启用状态与当前参数(图形验证码、防暴力破解、 会话管理、单点登录、信任代理、请求体限制),并附在线会话数与当前锁定 IP 数; 下方可在线调整会话超时、验证码有效期、连续失败锁定阈值、锁定时长、信任代理开关与单点登录开关, 保存后立即生效并持久化(见「配置」一节)。
修改用户名需要输入新名称(1-32 个字符,不含空格和冒号;不能使用保留名 defaultAdminUser,默认 admin);
修改密码需要输入旧密码和新密码;
退出登录需二次确认,会调用 /auth/logout 销毁服务端会话并清除本地 Token;不影响正在执行的任务(子代理、后台作业等继续在服务端运行,重新登录后可见)。
服务端强制规则:
Authorization: Bearer <token>);会话失效返回 401。密码文件以原子方式重写(临时文件 + rename,权限 0600),读-验-写在同一串行队列中完成; 注释和其他用户的条目都会保留。管理员仍可直接在服务器上重置任意用户密码:
npx dsh-auth-passwd set <user> # 覆盖任意用户的密码
DSH 刻意只监听 127.0.0.1。用同一台机器上的 Nginx 做前置(auth_request + 限流)
即可安全地对外暴露 — 公网 HTTPS 见 docs/nginx.conf.example,
局域网 HTTP 见 docs/nginx.conf.lan.example。
nginx -V 2>&1 | grep -- 'http_auth_request_module' # 检查模块是否可用
sudo nginx -t && sudo systemctl restart nginx
要点:
location / → auth_request /_auth;子请求携带浏览器的 Authorization 头转发到 /auth/verify。
2xx 放行,401/403 拒绝。/auth/login 和 /auth/captcha 放行通过,但按 IP 限流。limit_req 区域提供粗粒度的传输层限流;插件的失败计数存储提供细粒度的账号锁定。apt install certbot python3-certbot-nginx && certbot --nginx -d your-domain.com。局域网内不需要 HTTPS 时,直接以 IP + 80 端口访问即可。把公网配置中的
listen 443 ssl 换成 listen 80、去掉 ssl_* 指令即可,auth_request
认证流程与传输层加密无关,行为完全一致 — 完整示例见
docs/nginx.conf.lan.example:
# 只需把 server 块改成:
listen 80;
# server_name 留空或用本机 IP,如 server_name 192.168.1.10;
⚠️ HTTP 是明文传输:账号密码和会话 Token 在局域网内可被抓包看到。 仅建议在可信内网使用;任何对公网开放的部署都应使用上方的 HTTPS 配置。
admin / admin123 会被打印到启动日志并写入文档,
任何读到这些信息的人都能在改密前登录。首次登录强制改名 + 改密只是缩短暴露窗口,
请务必在部署完成后立即完成这两步(改名后 admin 名称即被保留禁用),
或在不需要开箱即用时将 autoProvisionAdmin 设为 false,改用 dsh-auth-passwd set
创建自己的账号。注意:命令行工具 dsh-auth-passwd 属于服务器管理员工具,仍然可以直接
创建名为 admin 的账号——Web 端无法做到的事,管理员在服务器上始终可以做。cordis.patch.yml。/api 流量的权威校验
在 Nginx 的 auth_request 层。没有 Nginx 前置时,DSH 只会在回环地址上提供服务。~/.dsh/auth.passwd,权限 0600;用 dsh-auth-passwd set
轮换哈希(bcrypt 轮数 = 12,新哈希;旧哈希仍按各自轮数校验;创建/重置密码强制
至少 8 个字符)。X-Forwarded-For 绕过按 IP 锁定,甚至把任意 IP 投毒进锁定状态;反之,有反代却关闭
trustProxy 会让所有远端用户共享同一个(回环)锁定桶。accountMaxLoginAttempts)已覆盖
IP 池/轮换攻击;高安全场景可再叠加 Nginx 全局限流与 fail2ban 作纵深防御。npm install
npm run build # tsc 构建 host → lib/host,esbuild 构建 client → lib/client.js
npm run typecheck
npm test # 构建 host 后运行集成测试(覆盖验证码/锁定/改密/信任代理/并发写)
目录结构:
dsh-auth-gate/
├── package.json # dsh.bundle.patch + dsh.client(platform: web)
├── cordis.patch.yml # 向 profile 插入 host 条目
├── src/
│ ├── host/index.ts # /auth/* 服务(captcha、login、verify、logout)
│ ├── client/index.tsx # 登录门(root slot,优先级 -1)
│ ├── client/login.css # 登录门样式
│ └── shared/types.ts # 双端共享的线上类型
├── bin/dsh-auth-passwd.mjs # 密码文件 CLI
├── scripts/build-client.mjs# 将 esbuild 产物包装进 __ModuleLoader__.load()
└── lib/ # 构建产物
├── host/ # host ESM(tsc)
└── client.js # client bundle(esbuild,CJS-in-loader 包装)
客户端 bundle 由 DSH 自身的模块系统在 /plugins/dsh-auth-gate/client.js 提供,
并自动注入 window.__DSH_BOOT__ — 无需额外接线。
CLASSIFICATION EVIDENCE
系统优先读取 GitHub Topics,再与站内分类词典和词根规则比对。