toby-bridges/api-relay-audit
toby-bridges/api-relay-audit从 DeepSeek Harness 对 AI API 中转站和 LLM 代理运行本地安全审计,生成 Markdown 报告,覆盖提示词注入、模型替换信号、工具调用改写、错误泄漏、流完整性和按 profile 启用的 Web3 风险。
替换 dsh web 启动器以允许绑定 0.0.0.0,并以账号密码登录为门槛:签名会话 cookie、/api 路由保护、设置面板认证标签页,以及轮换签名密钥使全部会话失效的重置 CLI。
中文 | English
DSH(DeepSeek Harness)远程 Web 启动 + 用户名/密码认证插件。
⚠️ 版本跟进声明:本项目只跟进官方 dsh 的正式发布通道(nextdist-tag),不跟进alpha预览通道(当前基线:dsh 0.2.1-rc.2,依赖 5 个@deepseek-ai/dsh-*已 bump 到^0.2.1-rc.1(caret 在 0.2.1 线内自动解析到最新 rc);适配审查与观察哨见docs/upgrade-dsh-0.2.0-playbook.md)。0.2.1 线双时代兼容已落地并随 bump 生效(通配--host的 schema 拓窗、LAN 信任的官方消费路径、TLS/--public-url透传;在 0.2.1 之前的 dsh 上这些新 flag 会明确报错而非静默忽略)。
原版 @deepseek-ai/dsh-web-app/startup 出于安全考虑硬拒绝 `--host 0.0.0.0`;本插件替换它,并配一个带登录/注册页的认证插件,让 dsh web 可以在局域网(或任何非回环接口)上安全暴露浏览器界面。
--host 0.0.0.0 可用,替代原版启动器的硬性拒绝;具体 IP 字面量(如 --host 192.168.1.5、--host ::1、--host fd00::1)与通配 :: 同样可用(#35;非规范 IPv6 拼写自动规范化,带 zone 的(%eth0)与 IPv4-mapped(::ffff:a.b.c.d)写法拒绝)。纯 IPv6 网络下 0.0.0.0 只绑 IPv4、界面不可达;:: 在 Linux 默认双栈(net.ipv6.bindv6only=0),同时监听 IPv6 与 IPv4-mapped,比 0.0.0.0 覆盖更全;bindv6only=1 的系统上 :: 仅 IPv6。另透传上游两个启动能力(需 dsh ≥ 0.2.1):--public-url (声明对外访问 URL)与 --tls-cert/--tls-key(直连 HTTPS,见「安全说明」);在低于 0.2.1 的 dsh 上使用会明确报错提示需要 dsh ≥ 0.2.1,而不是静默忽略。prefers-color-scheme)。Host 头都是回环时才免认证。所以本机浏览器直接访问 http://127.0.0.1:/ 无需注册或登录;局域网客户端、以及反向代理转发来的请求(Host 是公网域名)一律需要会话。免登录不等于已登录:本机没有会话 cookie,认证页显示的是「管理员账号」而不是「当前登录」,也不会给出退出登录入口(没有会话可退)。说它没什么用是有实测依据的:拼图的答案必须画在图上(人要看着它拖),所以脚本几十行就能读出来——本插件按常规做法实现的三种变体,用约 30 行脚本攻击的命中率分别是 100%(缺口涂暗)、100%(只描边)、97%(撒噪点,但那一版连人也解不出来)。之前试过的 4 位数字图片题同样被模板匹配打到 96.7%。当个乐子,别当安全边界。 详见 docs/agent/human-verification.md。
Retry-After。本机(真回环且未开启「本机登录校验」)豁免全局退避,避免攻击者借此把管理员锁在自己机器外面。dsh_sid,默认 14 天有效、可在设置面板调节(3–180 天档位),HttpOnly + SameSite=Lax;TLS 监听下自动追加 ; Secure)。/api/* 及第三方插件的 RPC 路由,除 /api/auth/* 与 /login)必须携带有效会话,否则返回 401/拒绝握手。crypto.randomUUID polyfill —— 非安全上下文下该 API 缺失,会导致所有 RPC 失败。dsh-auth-* 签名 cookie),/api 与 index.html 一律要求携带、连回环都不豁免(本机也得先换 token URL)。本插件为已通过自己认证(dsh_sid 会话,或 TCP 对端 + Host 双回环的本地请求)的访问者自动补发该 cookie:页面导航经一次 200 跳板(meta refresh 回原路径)即带上、登录响应直接下发(不用 3xx:重定向响应里新设的 cookie 不会带给重定向目标,补发会被逐跳重放成 ERR_TOO_MANY_REDIRECTS),全程无需接触启动打印的 token URL——「账号/密码 + 可撤销会话」仍是唯一认证入口,上游 cookie 只是通过上游闸门的凭据。本插件是一个 DSH bundle(package.json 的 dsh.bundle.patch 声明了随包分发的 cordis.patch.yml)。用 dsh plugin 安装后,包会被加入 profile 的 dsh.profile.bundles,补丁层自动生效,无需手动编辑任何配置文件。
# 方式一:从源码安装
git clone <仓库地址>
cd dsh-web-startup-auth
npm install # 安装构建依赖(typescript 等)
npm run build # 编译 src/ 到 lib/(插件运行时加载 lib/ 下的产物)
dsh plugin --profile web add .
# 方式二:从 npm registry 安装(已装过旧版本时执行同一条命令即可升级)
dsh plugin --profile web add dsh-web-startup-auth@latestdsh plugin是 pnpm 转发器,--profile必填;add .会把当前目录以link:方式装进 profile。
版本范围:本插件声明支持 dsh>=0.1.7-rc.1、不支持0.3.0起的版本线(含其预发布版)。运行中的 dsh 超出范围时,dsh 会跳过本 bundle 并打印原因——这是刻意的 fail-loud:宁可不加载,也不在未适配验证过的 dsh minor 版本线上静默运行。注意粒度是 minor 线:同一条线内的后续版本(含 rc 等预发布)会照常加载,每出一条新 minor 线要先完成适配再考虑放宽范围。确需强行启用可走 profile 的 version exemption(插件管理器文档)。
启动:
dsh web --host 0.0.0.0
# 纯 IPv6 网络(或想同时覆盖 IPv6):
dsh web --host :: --port 8080
# 具体地址绑定(如只服务某块网卡):
dsh web --host 192.168.1.5
# 直连 HTTPS(需 dsh >= 0.2.1;自签证书同样可用,浏览器会提示不受信任):
dsh web --host 0.0.0.0 --tls-cert cert.pem --tls-key key.pem安装时已自动应用补丁,无需再用 --patch 叠加——重复叠加会把插件再插入一遍,导致重复。http://:/(在本机则用 http://127.0.0.1:/,无需登录)。IPv6 地址在 URL 中必须加方括号:http://[]:/。注意:--host :: 启动时终端只打印回环 URL(上游的 LAN 地址推导只枚举 IPv4),局域网 IPv6 地址请用上面的形式手动拼接;打印的回环 URL 固定是 http://127.0.0.1:/(上游硬编码),bindv6only=1 的系统上 :: 是纯 IPv6 套接字、它不通,本机请用 http://[::1]:/。/login,显示"设置管理员账号密码"注册表单。/api/auth/logout 清除会话 cookie)。如果需要在本地设置账号密码(本机默认免登录,界面不会自动跳登录页):
http://127.0.0.1:/login —— 未注册时该页直接显示注册表单。注册后本机仍免登录;想让本机也要登录,到认证 → 登录要求打开「本机登录校验」(需先有账号)。开关打开后本机也要走 /login(页面会立即跳过去),登录后认证页才会显示「当前登录」和退出登录入口。
凭据与会话密钥保存在 $DSH_HOME/web-auth.json($DSH_HOME 未设置时即 ~/.dsh/web-auth.json,与 dsh 自身数据同目录;可用环境变量 DSH_WEB_AUTH_FILE 覆盖整个路径):
sessionMaxAgeDays,默认 14)也存在这个文件里,可在设置面板调节;调整只对新签发的会话生效。dsh --profile web auth-reset,交互式设置新密码(或 dsh --profile web auth-reset --password 非交互)。重置会轮换会话密钥,所有已登录的浏览器都需要重新登录一次。dsh --profile web auth-reset --username (可与 --password 同用)。同样轮换会话密钥。用户名在注册/登录/修改时统一剥除 C0 控制字符(0x00–0x1F)与 DEL(0x7F)——旧版本若已把含 DEL 的用户名原样存盘,用它即可修复。$DSH_HOME/web-auth.json,默认 ~/.dsh/web-auth.json)并重启,即可重新注册。这只影响登录状态——已登录的浏览器需要再登录一次,不会删除任何会话记录或历史数据(它们存在 ~/.dsh/sessions/ 等位置,与本文件无关)。Q:怎么在本地设置账号密码?必须代理到公网才能配置吗?
不需要,代理到公网不是前提。本机默认免登录(不会自动跳登录页),所以请在浏览器手动打开 http://127.0.0.1:/login——未注册时这一页直接显示注册表单;或者进设置面板 → 认证,页顶「尚未设置管理员账号」处点「前往设置管理员账号」。注意命令行 dsh --profile web auth-reset 只能改已有账号的密码/用户名,不能用来首次建号。
Q:本机不想登录,远程才要登录,可以吗?
这就是默认行为。设置面板 → 认证 → 登录要求默认关闭,即本机(真回环)免登录;远程客户端一律需要会话。打开这个开关(需先有管理员账号)则本机也强制登录,适合多人共用一台服务器时禁止同机其他账号免登录。没有「受信主机免登录」这种开关,--trusted-host 也不参与认证判断。
Q:忘记密码 / 想改用户名?
dsh --profile web auth-reset # 交互式
dsh --profile web auth-reset --password <新密码> # 非交互
dsh --profile web auth-reset --username <新用户名>都会轮换会话密钥,所有已登录的浏览器需要重新登录一次。兜底方案是删掉凭据文件($DSH_HOME/web-auth.json,默认 ~/.dsh/web-auth.json)再重启重新注册——不影响历史会话数据。
Q:访问某个接口返回 403,是本插件拦的吗?
本插件的认证失败只会返回 401 或者跳转到登录页,从不返回 403。所以看到 403 先看响应体的形状来定位来源:
响应体 · 来源 · 怎么办 401,或页面被送到 /login · 本插件(会话无效) · 重新登录 纯文本 forbidden · 上游 dsh 的 Host 信任围栏 · 检查反向代理是否保留了真实 Host,必要时加 --trusted-host {"error":"request-not-trusted"} · 第三方插件自带的回环限制 · 见下一条 transport failure ... HTTP 403 · 第三方插件自带的回环限制 · 见下一条
Q:第三方插件(如 dsh-im、dsh-workbuddy-connect)在局域网返回 403?
这是该插件给它自己的接口加的回环地址限制(只放行 127.0.0.1 / localhost),与本插件和你的服务器都无关——本插件只管 /api/* 的会话闸门,管不到别的插件挂在 webServer 上的路由。
cordis.patch.yml 里加 rpcAuthority: trusted-host;新版已默认放行,一般不用配。dsh-workbuddy-connect):只能去该插件的仓库提 issue,请求提供一个可配置的等价开关。临时绕过办法是在你自己的电脑上做一次 SSH 端口转发,让浏览器通过回环地址访问,限制就不会触发:
ssh -L 3080:127.0.0.1:3080 用户名@<服务器IP>保持这个窗口不关,浏览器打开 http://127.0.0.1:3080 即可。注意此时所有请求的 Host 都是回环,会被当作本机访问——不要在不受信任的网络上这么做。
Q:设置面板报「加载提供方目录失败 / settings are unavailable in this browser」?
最常见的原因是装到了旧版本。这个症状在 0.1.1 就已修好,0.1.5 起换了实现(远程浏览器下 settings 面板正常渲染)。先确认版本:
dsh plugin --profile web add dsh-web-startup-auth@latestadd 时务必带 `@latest`:不带的话 pnpm 会一直复用首次写入的版本号,之后用 update 也升不上去(历史上因此排查过多次)。
Q:升级 dsh 之后插件行为异常?
npm run build,然后重启 dsh web(lib/ 是入库的构建产物,不构建等于没改)。add dsh-web-startup-auth@latest。>=0.1.7-rc.1,不支持 0.3.0 起的版本线。超出范围时 dsh 会跳过本 bundle 并在启动日志里打印原因(刻意的 fail-loud),在启动日志里搜 skipped 就能确认。Q:登录页和首页来回循环跳转 / Safari 报 `ERR_TOO_MANY_REDIRECTS`?
/login 互跳的死循环:0.1.3 起已修复(0.1.2 的认证判定只看启动参数、不看请求本身,反代场景下会误判)。两者如果仍出现,先确认装的是最新版,清一下该站点的 cookie 再试。
Q:拼图验证能挡住脚本吗?
挡不住,如「特性」一节所说——答案就画在图上,脚本几十行就能读出来。当个乐子,别当安全边界。
如果您在寻找开箱即用的专为 Agent 时代研发的 IDE,推荐您使用 Omniterm
--tls-cert/--tls-key 直连 HTTPS——此时会话 cookie 与补发的原生 cookie 自动携带 ; Secure(低于 0.2.1 的 dsh 无此能力,使用时会明确报错提示需要 dsh ≥ 0.2.1)。crypto.scryptSync,无第三方依赖。dsh_sid 是自包含签名 cookie,/api/auth/logout 只清除浏览器一侧的 cookie。cookie 一旦泄露(如明文 HTTP 下被嗅探),在有效期内无法单独吊销(上限即设置面板所选的会话有效期档位)。例外:dsh --profile web auth-reset、设置面板的「修改密码」与「修改用户名」都会轮换会话密钥,一次性让所有已登录的浏览器重新登录(操作者自己的当前会话由服务端重新签发,保持登录)。/api/auth/login、/api/auth/change-password 与 /api/auth/change-username(旧密码/当前密码错误同样计次)。如需更严格防护请在反向代理层增加通用限速。connection/request 扩展点上的会话闸门。两层互为保险:上游接口变化导致其中一层失效时,另一层仍保证只有可撤销的会话(或真本机回环)能访问 API——30 天不可单独吊销的上游原生 cookie 永远不能单独作为 API 凭据。$DSH_HOME/web-auth.json(默认 ~/.dsh/web-auth.json;含密码哈希与会话签名密钥)以 0600 保存,目录以 0700 创建;插件启动时会自动修复旧版本遗留的过宽权限。connection 的 trustedHosts),不参与本插件认证判断——远程客户端一律需要有效会话,不存在"受信主机免登录"。127.0.0.1,由代理做 SSL 卸载并转发。此时代理必须转发真实 `Host`(nginx 默认即为 proxy_set_header Host $host;,配上 --trusted-host 让 DSH 自身的 Host 围栏放行);认证通过后插件按请求的真实 `Host`(公网域名)补发上游原生浏览器 cookie,上游闸门据此放行。反之,若代理把 Host 写死成 127.0.0.1,插件会认为请求来自本机从而放行全部流量、不做认证——不要这样配置。X-Forwarded-For 不被采信(客户端可伪造),信任判定只看 TCP 对端地址与 Host。connection.isLoopback,远程浏览器下的 settings mirror 走内存模式、插件配置卡片与 Models 页不可用;本插件当时通过 webServer.tapIndex 注入脚本在 connection 激活瞬间把该标志覆盖为恒 true。0.1.2 上游引入真实 cookie 认证后能进 UI,但 settings mirror 仍按同一标志判定——LAN 浏览器依旧得到从不读 host 的 memory mirror,设置面板的 Models(提供方目录)会报 "settings are unavailable in this browser"。直接恢复旧的 getter 覆盖会破坏 web boot(A/B 实测 26 个前端插件 pending),因此 0.1.2 起改用注入 `window.__DSH_TRANSPORT__ = { ownsHost: true }`:connection client 构造时据此把 isLoopback 报为 true(api/rpc 字段缺省时安全回退,且不重写 cordis 服务),LAN 与回环浏览器的全部设置面(含 Models)都正常渲染。当前保留的浏览器侧 shim:该 transport hook 与 crypto.randomUUID polyfill(明文 HTTP 非安全上下文所需)。npm install
npm run typecheck # tsc --noEmit
npm test # vitest
npm run build # tsc -p tsconfig.json + tsdown,产物输出到 lib/tsc 编译 node 侧源码(src/*.ts)与类型声明到 lib/、lib/types/。tsdown 把前端插件(src/client/index.tsx)打包成浏览器 bundle lib/client.js(window.__ModuleLoader__.load 注册格式)。改前端代码后必须重新构建,profile 里 link: 安装会自动加载新产物。@deepseek-ai/dsh-client-* 包只用于类型与构建,运行时由 DSH 前端模块表提供。