deepseek-harness
deepseek-ai
DeepSeek Harness: Everything is a Plugin.
PROJECT TOPICS
PROJECT README
DeepSeek Harness(dsh)的登录门卫插件。dsh 的 Web UI 默认只监听 127.0.0.1:3080,禁止外部访问;本插件在外部再开一个入口(默认 0.0.0.0:3081),访问者先通过用户名密码登录,登录成功后流量被全量反向代理到 dsh 的 Web UI(HTTP 与 WebSocket 都支持),功能零缺失。
零运行时依赖(只用 Node.js 内置模块),Node 22+ ESM。
0.0.0.0 会裸奔在公网上,任何人可访问;127.0.0.1。首次访问未初始化时,会进入 /setup 引导页,输入一次性令牌并创建管理员账号:

浏览器 ──HTTP/WS──▶ 0.0.0.0:3081(门卫:登录校验 + 会话 Cookie)
│ 通过校验后全量反代(改写 Host/Origin/Sec-Fetch-Site 为 loopback 形态)
▼
127.0.0.1:3080(dsh Web UI,信任围栏放行,特权 API 全可用)
关键点:反代时把请求头里的 Host/Origin/Sec-Fetch-Site 改写为 loopback 形态,让 dsh 把请求当作"本机请求"信任放行;WebSocket 升级请求同样先校验会话再转发。
dsh 0.1.2-alpha.1 起 dsh 在信任围栏之外又加了一层「浏览器鉴权」:首页与全部 /api(含 WS 升级)必须携带宿主签发的 dsh-auth-* 会话 Cookie,而该 Cookie 只能由启动令牌(打印在服务器终端的 dsh web: …/?token=… URL 里)换取。浏览器只见过门卫地址,永远拿不到这个令牌——所以由门卫在「已登录用户的首页导航」上代跑一次令牌交换:
303 + Set-Cookie,门卫原样透传,浏览器保存后 /api 与 WS 自然带上;dsh-auth- + base64url(sha256(authority)) 反推(authority 即门卫改写后的 Host),据此判断是否需要交换,避免每次导航都重复走 303;dsh-auth-*(密钥轮换、过期残留)时,门卫收到宿主的 401 会剥掉坏 Cookie 重跑一次交换,远端用户不必手动清 Cookie;对 dsh ≤ 0.1.1 的宿主,connection 服务没有 authenticatedUrl,上述逻辑整体跳过,行为与旧版一致。
用 dsh 标准插件命令安装(本项目是标准 dsh.bundle 插件,无需手改 profile 配置):
# 本地目录安装(link 形式,profile 直接引用本目录,改源码无需重装)
dsh plugin --profile web add /path/to/dsh-login-gateway
# 卸载
dsh plugin --profile web remove dsh-login-gateway
安装/卸载后重启 dsh 生效。
获取一次性初始化令牌(二选一):
方式 A:看 dsh 启动终端或服务日志输出
插件会直接输出一行提示,内容类似:
[login-gateway] 登录门卫未初始化,请访问 http://<主机>:3081/setup 并输入一次性令牌:ABCD-EFGH-IJKL-MNOP-QRST-UVWX-YZ12-3456
日志示例:

方式 B:读取令牌文件
令牌同时会写入用户文件同目录下的 setup-token.txt(权限 0600,内容只有令牌本身):
cat ~/.dsh-login-gateway/setup-token.txt
浏览器打开 http://<主机>:3081/setup,输入令牌、管理员用户名、密码(至少 8 位)与确认密码,点击“完成设置”。
初始化完成后会跳转到登录页,用刚创建的账号登录,即可进入 dsh。
说明:新装没有默认账号,必须走
/setup引导创建。未初始化时访问/会自动302跳转到/setup。令牌只在未初始化阶段生成;初始化完成后/setup会返回410,setup-token.txt也会被自动删除。
本项目是标准的 dsh.bundle 插件(package.json 声明 dsh.bundle.patch,随包自带 cordis.patch.yml 挂载层),用 dsh 官方插件命令管理,无需手改 profile 配置:
dsh plugin --profile web add /path/to/dsh-login-gateway
命令会把本目录以 link: 形式装进 profile(~/.dsh/profiles/web/),并自动把包名追加到 profile 的 dsh.profile.bundles 层列表。安装后重启 dsh 生效。
开发调试提示:
link:形式下 profile 直接引用本目录,改源码后重启 dsh 即可生效,无需重装。
dsh plugin --profile web remove dsh-login-gateway
重启 dsh 后,门卫端口 3081 停止服务,外部访问恢复为“无法访问”。
可选清理:删除门卫的用户数据目录(账号、令牌文件):
rm -rf ~/.dsh-login-gateway/
随包挂载层只提供默认配置。如需改端口、超时等,在 profile 目录(如 ~/.dsh/profiles/web/)的 cordis.patch.yml 用户层追加覆盖(注意:补丁是整段替换 config,要写全所有项):
- id: login-gateway
config:
listenHost: '0.0.0.0'
listenPort: 3081
targetHost: '127.0.0.1'
targetPort: 3080
sessionTtlHours: 24
maxLoginAttempts: 5
lockMinutes: 5
clientLoopbackTrust: true
settingsFilePath: '/root/.dsh/settings.yaml'
settingsFileDownload: true
零侵入。门卫只做三件事:在外部开一个登录入口、校验会话、把通过校验的请求反代到 dsh 的 loopback 端口。它不修改 dsh 安装目录的任何文件,也不改 dsh 自身配置逻辑;安装/卸载只影响 profile 目录(依赖与 bundles 列表),一条命令即可完全移除。卸载后 dsh 与安装前一致。
所有配置都有默认值,新装默认配置即可工作。完整配置表如下:
| 配置项 | 默认值 | 说明 |
|---|---|---|
listenHost |
0.0.0.0 |
门卫监听地址,暴露给外部 |
listenPort |
3081 |
门卫监听端口 |
targetHost |
127.0.0.1 |
dsh Web UI 监听地址 |
targetPort |
3080 |
dsh Web UI 监听端口 |
sessionTtlHours |
24 |
登录会话有效期(小时) |
maxLoginAttempts |
5 |
同一 IP 连续失败多少次后锁定 |
lockMinutes |
5 |
锁定持续分钟数 |
setupMaxAttempts |
5 |
/setup 初始化时,同一 IP 连续失败多少次后锁定 |
setupLockMinutes |
30 |
/setup 初始化锁定持续分钟数 |
proxyTimeoutMs |
60000 |
反代上游响应头等待超时(毫秒),超时返回 504;WS 握手超时取与 15s 的较小值 |
streamIdleTimeoutMs |
1800000 |
反代响应流空闲超时(毫秒,默认 30 分钟) |
maxConnections |
512 |
HTTP 服务最大并发连接数,超出后新连接被丢弃 |
userStorePath |
~/.dsh-login-gateway/users.json |
用户数据文件路径(可自定义) |
maxSessions |
1000 |
会话容量上限,超出后逐出最旧会话,防止反复登录刷爆内存 |
trustProxy |
false |
前置 TLS 反代(nginx/caddy)时设 true:从 X-Forwarded-For 取真实客户端 IP 参与限速。直连场景必须保持 false,否则攻击者可伪造该头绕过限速;为 false 时门卫会剥离伪造的 XFF/X-Real-IP/Forwarded 头再转发上游 |
trustedProxyHops |
1 |
仅 trustProxy: true 时有意义:可信代理层数,取 XFF 右起第 N 段作为客户端 IP(前置反代会把直连对端追加在最右,客户端伪造只能往左侧追加)。链路是「CDN → nginx → 门卫」时按可信层数调大(如 2) |
secureCookie |
false |
仅经 HTTPS 访问门卫时设 true:会话 Cookie 追加 Secure 标记 |
globalAuthRatePerMinute |
30 |
全局认证节流:每分钟最多 30 次触发密码计算的尝试(登录+改密合计,超限直接 429 不消耗计算)。防止攻击者轮换 IP+用户名绕开双维度锁定后打满 CPU |
bindUserAgent |
true |
会话绑定 User-Agent:被嗅探的 Cookie 在不同客户端上不可复用(异 UA 访问会立即吊销该会话)。浏览器升级换 UA 后需重新登录一次;设 false 关闭 |
tls |
不启用 | 门卫自身 TLS。HTTP 直连场景下为密码与会话提供传输加密:tls: { enabled: true, certPath: '/path/cert.pem', keyPath: '/path/key.pem' }。启用后 secureCookie 自动开启、日志地址变 https://。自签证书一行生成见下文「HTTP 直连场景安全清单」。证书路径错误时插件启动显式失败(绝不静默回退明文) |
clientLoopbackTrust |
true |
经门卫访问时,通过随包自带的浏览器端 client bundle(lib/client.js)把 dsh 连接标记为 loopback,恢复设置持久化(深色模式、插话发送等),同时保证「设置-模型」「设置-插件-插件配置」正常显示。设 false 可关闭(设置将退回不持久化) |
settingsFilePath |
~/.dsh/settings.yaml |
dsh 设置文件路径(供下载路由使用,一般无需改动) |
settingsFileDownload |
true |
宿主机无桌面环境(容器/无显示器服务器)时,把 dsh 设置页的「打开配置文件」按钮改为从门卫下载该文件(/__gateway/settings.yaml);桌面环境主机自动保持 dsh 原生打开。设 false 关闭该兜底 |
http://<主机>:3081/,输入用户名密码。成功后会种下会话 Cookie(dsh_gw_session,HttpOnly + SameSite=Strict),之后访问全部走反代,包括 WebSocket。curl -X POST http://<主机>:3081/logout。/ 返回登录页;其余路径返回 401 JSON(浏览器导航式请求给「登录已失效,请刷新页面或重新访问 / 登录」,接口请求给「未登录,请先访问 / 登录」)。maxLoginAttempts 次会被锁定 lockMinutes 分钟。被锁定期间即使密码正确也会被拒(lockMinutes 后自动解除)。maxLoginAttempts 次也会被锁定,可防代理池分布式爆破。/setup 同样按 IP 限速,令牌错误、用户名空或不合规、密码过短、两次密码不一致都计失败。/change-password 用独立限速表,连错 maxLoginAttempts 次会锁定改密入口 lockMinutes 分钟;不影响用正确密码登录(登录有自己的桶,登录成功即清零)。用户名合规字符集:中英文、数字与 ._-@+,≤64 字符。login-gateway 前缀审计。本插件的典型用法就是不套 nginx、直接 http://<IP>:3081 访问。明文 HTTP 的固有风险是:同一链路上的设备可嗅探到你的密码与会话 Cookie,中间人还可以篡改登录页。门卫已内置多层缓解,按需逐条核对:
传输加密(强烈建议):给门卫开自身 TLS——自签证书一行生成:
mkdir -p ~/.dsh-login-gateway/tls
openssl req -x509 -newkey rsa:2048 -keyout ~/.dsh-login-gateway/tls/key.pem \
-out ~/.dsh-login-gateway/tls/cert.pem -days 3650 -nodes -subj '/CN=dsh-gw'
然后在 profile 用户层配置里加(整段替换 config 时记得带上其他要改的项):
- id: login-gateway
config:
tls: { enabled: true, certPath: '/root/.dsh-login-gateway/tls/cert.pem', keyPath: '/root/.dsh-login-gateway/tls/key.pem' }
secureCookie: true
重启后用 https://<IP>:3081 访问;浏览器会提示"证书不受信任"(自签的固有现象),点继续即可——加密已经生效,嗅探者只能看到密文。不想看到告警就把 cert.pem 导入系统/浏览器信任库。
会话绑定 UA(默认开启):即使 Cookie 被嗅探走,攻击者在自己的客户端上复用会被立即识别并吊销会话。代价:浏览器大版本升级换了 UA 后需要重新登录一次。
爆破防线(默认全开):IP+用户名双维度锁定、全局每分钟认证节流(默认 30 次)、密码计算恒时、用户名不存在也消耗等价计算。新设密码会拒绝常见弱口令/纯数字/重复字符——12345678 这类密码已无法通过初始化与改密。
未登录零指纹:登录页之外的静态资源(manifest/favicon)由门卫返回空响应,不再反代真实 dsh 资源——扫描器无法从未登录态确认这是 dsh;robots.txt 明确禁止收录。
限速按直连 IP 生效:直连场景保持 trustProxy: false(默认),伪造 XFF 无法绕过限速。
缩短暴露窗口(可选):把 sessionTtlHours 从默认 24 调小(如 8),Cookie 泄露后的可用窗口同步变短。
其余通用安全设计见下。
443 -> 127.0.0.1:3081),此时在门卫配置里同时开启 trustProxy: true 与 secureCookie: true。HttpOnly + SameSite=Strict,页面无 XSS 注入点;改密成功自动吊销其他全部会话。~/.dsh-login-gateway/users.json,内含 scrypt 哈希(不可逆);写入时自动使用 0600 权限、目录自动 0700。Content-Length 与 Transfer-Encoding 并存的歧义请求两头皆删、由 Node 按实际流重新分块。proxyTimeoutMs 返回 504;响应头到达后改用 streamIdleTimeoutMs 空闲超时,SSE 长间隔输出不会被正常打断。maxConnections 默认 512,并显式收紧 headersTimeout、requestTimeout、keepAliveTimeout,减少慢连接占用。X-Frame-Options: DENY、Referrer-Policy: no-referrer、X-Content-Type-Options: nosniff 和 CSP;secureCookie(HTTPS 部署)时额外下发 Strict-Transport-Security: max-age=31536000,明文部署绝不下发 HSTS。反代透传的 dsh 响应保持原样。GET http://host/x)与网络路径形式(GET //host/x)一律折叠成 path?query 再送上游,避免请求行里的 authority 与已改写的 Host 不一致(origin 混淆/缓存投毒面)。._-@+ 且 ≤64 字符;哈希文件被篡改出越界 scrypt 参数(N/r/p 过大)时直接判失败,不会拖垮进程。100KB),防止恶意超大请求。重置管理员账号:优先用页面右下角「改密」入口在线轮换(需记得当前密码)。密码彻底遗失时,删除用户文件后重启 dsh,会再次进入“未初始化”状态,并重新打印一次性令牌:
rm -f ~/.dsh-login-gateway/users.json
忘记或没看到一次性令牌:可以看 dsh 启动终端/服务日志,或读取 ~/.dsh-login-gateway/setup-token.txt。如果两者都没有,删掉用户文件并重启 dsh,会重新生成令牌。
/setup 返回 410:说明已初始化完成,设置入口已关闭,属正常现象。如需重新初始化,先删用户文件再重启。
访问 http://<主机>:3081/ 打不开:检查 dsh 是否已启动、插件挂载是否生效、端口是否被防火墙拦截。
登录后页面或接口 502:门卫反代目标 127.0.0.1:3080 不可达,确认 dsh Web UI 进程仍在运行。
对话/粘贴时 /_dsh/vision-toolkit/paste-policy 等 /_dsh/ 路由返回 403(origin-rejected):这是上游插件(如 @anionex/dsh-vision-toolkit)对请求做的同源校验——没有 Origin 时要求 Sec-Fetch-Site 为 same-origin 等;curl、隐私浏览器等不带浏览器 Fetch Metadata 的客户端会被拒绝。门卫已在反代时把缺失的 Sec-Fetch-Site 补齐为 same-origin(本修复需重启 dsh 生效),正常浏览器不受影响。
用户文件损坏:启动会直接报错并给出文件路径,不会静默重置。按上面的“重置管理员账号”处理即可。
改了设置(深色模式、插话发送等)一刷新就还原:这是 dsh 的机制限制,不是登录态问题。dsh 把“设置持久化”门控在浏览器地址栏为 loopback(127.0.0.1/localhost)上;经门卫从外部域名/IP 访问时该判定为否,设置作用域进入内存模式——改动只在当次页面生效,刷新即丢。门卫已通过随包自带的浏览器端 client bundle(lib/client.js,标准 dsh.client 接入,immediately 早于 dsh 各设置组件判定生效)把浏览器端连接标记为 loopback,从而恢复持久化(设置会正常写入 ~/.dsh/settings.yaml)。该机制同时恢复了「设置-模型」「设置-插件-插件配置」(含 dsh 自带插件配置项)的正常显示。若设置 clientLoopbackTrust: false 关闭补丁,将退回“设置不持久化”的旧行为。注意:本修复不修改 dsh 任何源码,改的是门卫插件自带的浏览器端 client bundle;改动需重启 dsh 生效。
设置-打开配置文件提示「无法打开配置文件」:dsh 的该按钮会在服务器上调用系统级打开(Linux 走 xdg-open),无桌面环境(容器、无显示器服务器)上必然失败——这是宿主限制,不是登录态或门卫问题。门卫已默认提供兜底:探测到宿主无桌面环境时,自动把该按钮改为从门卫下载 ~/.dsh/settings.yaml(/__gateway/settings.yaml,需登录);桌面环境主机保持 dsh 原生打开。可在配置里关闭(settingsFileDownload: false)。
node:crypto scrypt(scrypt$N$r$p$salt$hash 自描述格式),恒定时间比较防时序攻击;登录时账号不存在也执行等价计算防用户名枚举。Map + 过期清理(30 分钟定时 sweep)+ 容量上限(逐出最旧)。rawHeaders 原样构造 101 响应。src/index.js 经宿主公开的 connection.authenticatedUrl() 取本进程启动令牌,src/proxy.js 在首页导航上代跑令牌交换并处理 401 自愈;门卫与 dsh 同进程,不读任何宿主内部字段。lib/client.js)把 dsh 的 loopback 判定修正为 true,恢复设置持久化(同时解决「设置-模型」「设置-插件-插件配置」显示问题)与 0.1.5 的「打开配置文件」入口,不改 dsh 源码。dsh 0.1.2 起 settings 的判定入口是 ctx.remote.$host.isLoopback 且带缓存,bundle 会同步修正该缓存(remote.hostFacts);无桌面环境时「打开配置文件」走门卫下载兜底。插件在 package.json 中声明了 dsh.engines.dsh 与 peerDependencies["@deepseek-ai/dsh"]
(均为 >=0.1.2-alpha.3 <0.1.8 || >=0.1.5-alpha.1 <0.1.6 || >=0.1.7-alpha.0 <0.1.8 || >=0.2.0-alpha.0 <0.3.0)
与 engines.node(^22.19.0 || >=24.0.0)。
0.1.7 起声明不再是纯语义:dsh 在安装期(插件管理器 preflight)与启动期(profile preflight)都会用
peerDependencies对运行时版本做兼容校验(includePrerelease: true), 不匹配的插件会被拒绝安装或拒绝挂载。闸只读peerDependencies,从不读dsh.engines.dsh, 两者必须逐字一致(test/supply-chain.test.js守护)。宿主侧判定带includePrerelease, 而 pnpm 安装期是严格 semver——预发布版必须有同元组预发布下界才被满足,所以 0.1.5-rc.*、0.1.7-rc.*、0.2.0-rc.*各有自己的 clause。 上界<0.1.8/<0.3.0让未来的0.1.8-*与0.3.0-*必须先验证再放行。
| dsh | 状态 |
|---|---|
0.1.0-rc.* / 0.1.1-rc.* |
✅ 正常工作(无宿主浏览器鉴权,适配逻辑整体跳过) |
0.1.2-alpha.1 ~ 0.1.2-rc.1 |
✅ 需要本仓库 ≥ 0.3.0;0.2.0 及更早的门卫版本登录后首页与全部 /api 一律 401 |
0.1.5-alpha.1 ~ 0.1.5-rc.1 |
✅ 已逐包核对 + 真机端到端实测(见下),无需改门卫配置 |
0.1.7-rc.1 |
✅ 已逐包核对 + 回归固化(见下) |
0.2.0-rc.1 |
✅ 当前适配目标(本仓库版本号同为 0.2.0-rc.1);宿主鉴权契约零漂移,真机闸放行(见下) |
升级 dsh 后无需改动门卫配置:令牌交换由门卫自动完成,浏览器首次访问首页时静默换取宿主会话。
对 0.1.5-rc.1 做了隔离安装 + 逐包源码比对 + 真机端到端验证(不升级运行中的实例)。要点:
token 查询参数、dsh-auth-<sha256(authority)> Cookie 名与属性、
303 交换响应、"只在根路径上交换"三条规则在 0.1.5 逐字未变。dsh --profile web --trusted-host <authority>:只解决服务端 403 围栏,
不解决浏览器端非 loopback 导致设置写内存的问题,因此 client bundle 补丁仍然必需。ctx.remote.$host.isLoopback(0.1.2 起就已如此,不是 0.1.5 才有):
门卫的 client bundle 已同步修正该入口的缓存(remote.hostFacts),恢复
「设置持久化 / 设置-模型 / 设置-插件」与 0.1.5 新增的「打开配置文件」入口门控。/setup
一次性令牌建号、弱口令拒绝、登录与会话 Cookie 属性、首页令牌交换(303 + dsh-auth-*
同名 Cookie)、带宿主 Cookie 后的首页 200(35KB 真 dsh 外壳 + 门卫脚本注入 +
宿主 <base href="/"> 保留)、静态资源透传、登出连带吊销宿主 Cookie —— 全部实测通过。/api/remote.mux)实测 101:条件是浏览器必须先完成一次首页导航
(拿到 dsh-auth-* 宿主 Cookie)再升级 WS——这正是真实浏览器的顺序(页面加载先走 /)。
只带门卫会话、没走首页就直连 WS 会得到 401(宿主侧拒),属预期行为,不是缺陷。403 拒 —— 证明门卫改写三头仍是必需的。对 0.1.7-rc.1 做了逐包源码比对(本机安装实例),要点:
token 查询参数、dsh-auth-<sha256(authority)> Cookie 名、
303 交换响应、Cookie 属性(无 Secure)、401 文案与 0.1.5 逐字一致;
门卫的代跑交换、401 自愈、登出连带吊销三条链无需改动(test/host-auth-017.test.js 固化)。sec-fetch-site 拒 cross-site → Origin 同权威),
门卫"改写三头"方案依旧必需。peerDependencies 在安装 preflight(插件管理器)与启动
preflight(profile 兼容检查)两处被消费,不再只是家族约定。本仓已声明
peerDependencies["@deepseek-ai/dsh"](三 clause 区间,同时满足 dsh 侧
includePrerelease: true 与 pnpm 严格 semver 两套判定)。settingsScope 已移除:0.1.7 的 dsh-client-ui-settings 改为
configForms 服务(同样持有 persistence + mirror 两个同名字段,
SettingsDescribeMirror 形状不变:store 快照 {status, view, error}、
getSnapshot/load 同名)。client bundle 的兜底修复现在同时尝试两个服务名,
新旧宿主都能命中。dsh.client.inject 约定确认:该字段是模块图依赖(包名),与 bundle 代码里的
exports.inject(cordis 服务名)是两套机制;本仓两者均已符合 0.1.7 约定
(inject @deepseek-ai/dsh-client-connection + exports.inject ["connection"])。hmr watchConfig 监听 patch 文件与 profile manifest 变化触发 reconcile),无需额外适配。测试:pnpm test(124 例全绿,其中 test/host-auth-017.test.js 8 例为 0.1.7 形状回归)。
对 0.2.0-rc.1(桌面端 D:\DeepSeek Harness,FileVersion 0.2.0-rc.1)做了逐包源码比对 +
真机闸实测,要点:
TOKEN_QUERY = "token"、
COOKIE_PREFIX = "dsh-auth-"、authenticatedUrl(baseUrl)、authorizeIndex 的
GET / + 单 token → 303 + Set-Cookie、401 文案
dsh web authentication required; reopen the URL printed by dsh web.、
isLoopbackHostname / isTrustedAuthority 围栏形状、client 侧
isLoopback 兜底入口与 $host/hostFacts 缓存 —— 全部未变,门卫三条链(代跑交换 /
401 自愈 / 登出连带吊销)无需改动。configForms 服务仍在:ctx.remote.$host.isLoopback ? "host" : "memory" 判定语句、
persistence + mirror 两个字段名均与 0.1.7 相同,client bundle 兜底无需再改。skipping profile bundle "dsh-login-gateway"),插件整个 bundle 不加载。
新增第四段 || >=0.2.0-alpha.0 <0.3.0 后放行。evaluatePluginCompatibility(manifest, {}, '0.2.0-rc.1') → undefined(放行);
修复前返回被拒 peer 清单。dsh --profile web --dump-config 的 stderr 中门卫一行消失,
dump 里 login-gateway 行存在且未 disabled。0.3.0 仍被拒,0.1.8 仍被拒(守卫测试固化)。测试:node --test(126 例全绿,新增 2 例为 0.2.0 区间与 semver 判定回归)。
本插件对安装期行为有硬约束,且由 test/supply-chain.test.js 可执行地守护
(任何未来改动引入脚本或原生依赖都会让测试变红):
| 约束 | 现状 | 说明 |
|---|---|---|
| 不得有 npm 安装脚本 | ✅ 无 | package.json 只有 test 一个脚本;无 preinstall/install/postinstall/prepare 等任何生命周期钩子 |
| 不得有 gyp / 原生扩展 | ✅ 无 | 无 binding.gyp、无 *.node、无 node-gyp 调用;纯 ESM |
| 安装期零构建(能预编译就预编译) | ✅ 满足 | 源码即产物:src/*.js 直接就是 host 入口,lib/client.js 是手写并随包提交的预编译 client bundle(零 require),无 build 步骤 |
| 零第三方运行时依赖 | ✅ 无 | 只用 node: 内置模块(crypto/fs/http/https/net/os/path/zlib),不产生依赖树,因而也没有传递依赖的安装脚本案 |
| 发布面不含内部文档 | ✅ 已收敛 | files 只含 bin/src/lib/cordis.patch.yml/README.md/LICENSE;docs/(审计与适配调研)已被 .gitignore 忽略,仅本地留存 |
因此 dsh plugin add 对本插件的动作只有「建立符号链接 + 登记 bundles」,安装期不存在任何
可执行代码,也没有需要编译的二进制——供应链面等于「你读到的 JS 就是跑起来的 JS」。
npm test # node --test test/(零依赖,node:test 内置框架)
覆盖:密码哈希与篡改检测、会话过期/容量上限、限速锁定/双维度/TTL、请求头改写与走私防护、注入点边界安全、认证闸门、反代透传、WS 升级握手(含上游以非 101 拒绝时立刻回传)、setup 引导全流程、改密与会话吊销、审计日志与日志注入净化;以及宿主浏览器鉴权适配(Cookie 名反推对真实抓包向量、令牌交换、失效 Cookie 自愈、令牌不下发浏览器、非首页路径不掺令牌、登出连带吊销宿主会话、旧版宿主行为不变)。
另含 2026-09-10 审计轮次的回归:空 users 文件按未初始化、大小写无关的用户名桶、改密锁与登录锁互不污染、HSTS/nosniff、trustedProxyHops、日志分级、HEAD 响应框架、JSON 类型混淆不再 500、IPv4-mapped IPv6 归并、错误页不注入、绝对形式请求行折叠、异步 scrypt 不阻塞事件循环、用户名字符集与哈希参数上界。
以及 dsh 0.1.5-rc.1 适配回归:client bundle 行为(remote.\$host.hostFacts 缓存修正、persistence 兜底、异常静默)与 0.1.5 形状的宿主鉴权链(逐字复刻新版 authorizeIndex 语义与 401 文案);外加供应链约束守护(见上节)。
回归用例的质量标准:每项修复先写探针复现,且新用例在校验「修复前代码」时必须失败(本轮 26 例中 5 例经此对照确认,其余为新增语义覆盖)。
src/
index.js 插件主入口:配置校验、路由分发、setup 引导、改密、审计日志、HTTP 服务 + WS 升级
auth.js 密码哈希(scrypt,含异步校验)、会话存储(容量上限)、登录限速(双维度+TTL)、恒定时间比较、输入规范化(asString/normalizeIp/checkUsername)
proxy.js HTTP 反代(头改写 + hop-by-hop 剔除 + 走私防护)、宿主浏览器鉴权交换、WebSocket 升级转发
user-store.js 用户文件存储(JSON + 原子写入)
settings-file.js dsh 设置文件下载辅助(无桌面环境兜底)
login-page.js 登录页 HTML(深色主题,单文件内联)
setup-page.js 首次启动引导页 HTML(深色主题,单文件内联)
lib/
client.js 浏览器端 client bundle(标准 dsh.client 接入):把经门卫访问的连接
标记为 loopback,恢复设置持久化与模型/插件配置显示,不改 dsh 源码
bin/
hash.js 密码哈希生成 CLI
test/
helpers.js 测试基建:假 cordis ctx / 网关启动器 / 模拟上游 / HTTP 客户端
auth.test.js 认证核心单测
proxy.test.js 反代头处理单测
gateway.test.js 端到端集成测试(认证闸门/反代/setup/WS/改密/审计)
browser-auth.test.js 宿主浏览器鉴权适配(假 dsh 上游复刻 BrowserAuth 语义)
gateway-nav.test.js 深链 401 文案与 WS 升级被拒回归
gateway-round4/5/6.test.js 审计轮次回归(极端输入、请求行规范化、用户名字符集) CLASSIFICATION EVIDENCE
系统优先读取 GitHub Topics,再与站内分类词典和词根规则比对。当前命中: 无有效分类标签。