🎁 福利专区全网大模型免费应用 + 新用户福利 + 注册活动入口,低成本玩转 AI

手机远程访问 sifvbs/dsh-mobile

DeepSeek Harness
sifvbs7 天前有提交已提供中文文档扫描:含敏感能力

把桌面 Web 界面安全投到手机浏览器上使用

30.7/100
综合分
1
GitHub 星标
—
npm 周下载
—/5
用户评分

数据截至 2026/10/28 · 周下载取自 npm 全网(含 CI / 镜像),非本站安装数;综合分由星标、活跃度与兼容性检查合成。

安装命令 · 需先装 dsh CLI 引擎
dsh plugin --profile web add sifvbs/dsh-mobile
该包不可直接 npm 安装,改从 GitHub 源安装(明细见下方安装检查)

状态速览

判定依据:实装验证 · 静态安装检查 · 自动扫描 · 仓库维护状态
信任档位:已验证
是什么
dsh 原生插件 · tool
装得上吗
本站已在 dsh 0.2.1-rc.2 下真实安装成功
非静态推断
L5 · 运行时冒烟等级说明 →
安全吗
本站尚未对该插件做风险分级
暂未覆盖,不等同于无风险
还在维护吗
活跃:最近一次提交在 7 天前
档位由哪些信号合成

本站实装验证(真实安装 L4 覆盖全站;L5 为运行时冒烟或实机截图,覆盖范围小于 L4) · 验证所用 dsh 版本 · 静态安装检查 · 风险分级 · 仓库维护状态。 下方各区块是它的证据明细。

验证判据与等级说明 →
社区实装还没有人打卡 · 装过的第一个就是你
安全扫描自动 · 静态分析源码
v2.7.0
🔵 扫描:含敏感能力尚无人工评估
展开查看扫描说明与证据 ↓

静态扫描发现插件具备触达本地系统的能力:执行外部命令、读写/删除本地文件、读取剪贴板或密钥类环境变量(至少其一)。这是能力清单,不是危险判定——具体能力项与证据见下方,安装前请自行判断是否需要。

可执行外部命令读写/删除本地文件发起外部网络请求
查看扫描证据(共 13 处,展示前 13 条)
  • 读取本地文件
    scripts/make-pair-qr.js:32
    for (const name of fs.readdirSync(profiles)) {
  • 发起网络请求
    scripts/make-pair-qr.js:65
    const req = http.request(
  • 写入本地文件
    scripts/make-pair-qr.js:114
    fs.mkdirSync(outDir, { recursive: true });
  • 写入本地文件
    scripts/make-pair-qr.js:115
    fs.writeFileSync(path.join(outDir, 'pair-url.txt'), url + '\n', 'utf8');
  • 读取本地文件
    scripts/make-pair-qr.js:139
    const size = fs.statSync(target).size;
  • 读取本地文件
    scripts/termqr.js:21
    for (const name of fs.readdirSync(path.join(home, 'profiles'))) {
  • 读取本地文件
    scripts/termqr.js:48
    url = fs.readFileSync(args[1], 'utf8').trim();
  • 读取本地文件
    scripts/tunnel-shim.js:57
    const st = fs.statSync(HOST_FILE);
  • 读取本地文件
    scripts/tunnel-shim.js:59
    const t = fs.readFileSync(HOST_FILE, 'utf8').trim();
  • 发起网络请求
    scripts/tunnel-shim.js:205
    const up = http.request(opts, (ur) => {
  • 读取本地文件
    scripts/verify.js:49
    for (const p of CHROME_CANDIDATES) if (fs.existsSync(p)) return p;
  • 执行外部命令来自配置/变量
    scripts/verify.js:134
    const proc = spawn(
  • 删除本地文件
    scripts/verify.js:155
    fs.rmSync(userDataDir, { recursive: true, force: true });
扫描于 2026/11/4(0 天前) · 范围 入口闭包 + 全包扫描 · 来源 GitHub 仓库

静态扫描只反映源码中出现的调用,不构成安全背书;人工评估与自动扫描相互独立、互不覆盖。

给这个插件打分还没有人投票↓
用户评分
还没有人投票,来当第一个

把 DeepSeek Harness(DSH)的 Web GUI 暴露到手机,走局域网或任意网络——TCP 转发器 + cloudflared + 隧道垫片,不用公网 IP,也不用改 DSH 补丁。

README

由 DeepSeek 最新模型翻译生成
dsh-mobile 把 DeepSeek Harness (DSH) 的 Web GUI 接到手机上,同 WiFi 或任意移动网络都能用。 不需要公网 IP,不需要端口映射,不需要改 DSH 源码。 两条路都端到端验证过(真浏览器 + CDP,不是 curl 猜的): | 场景 | 入口 | 验证判据 | | 同 WiFi | http://<LAN-IP>:56875/pair-accept?pair=… | SLOTS: 41、OWNS: true、REQ 239/239、WebSocket 已连 | | 异网 / 4G | https://<随机名>.trycloudflare.com/pair-accept?pair=… | 同上 | 一、为什么不能直接让手机连 DSH 的桌面宿主只监听 127.0.0.1,手机天然连不上。
展开完整文档(README 剩余 1 节)
dsh-mobile

把 DeepSeek Harness (DSH) 的 Web GUI 接到手机上,同 WiFi 或任意移动网络都能用。

不需要公网 IP,不需要端口映射,不需要改 DSH 源码。

两条路都端到端验证过(真浏览器 + CDP,不是 curl 猜的):

| 场景 | 入口 | 验证判据 |
| 同 WiFi | http://<LAN-IP>:56875/pair-accept?pair=… | SLOTS: 41、OWNS: true、REQ 239/239、WebSocket 已连 |
| 异网 / 4G | https://<随机名>.trycloudflare.com/pair-accept?pair=… | 同上 |

一、为什么不能直接让手机连

DSH 的桌面宿主只监听 127.0.0.1,手机天然连不上。但「改成监听 0.0.0.0」这条路是堵死的:

1. @deepseek-ai/dsh-host-webserver 的 Config 显式拒绝通配地址:

webserver: host "0.0.0.0" is an unspecified (wildcard) address, which is not supported:
binding every interface would expose remote code execution to the network;
bind one concrete IPv4 or IPv6 address of a local interface instead

所以 @linxin666/dsh-remote-web-ui 那个「LAN 开关」(lanBind,写的就是 0.0.0.0)在 0.2.1 之后装不上。

2. 就算绑了具体 IP,还有一整套信任栅栏:

- client-connection 的 isTrustedApiRequest 只认 loopback / trustedHosts / listener 自身绑定 IP 三类 Host,其余一律 401/403。
- remote-web-ui 的 lanFence 还要再叠一层:lanAddresses + publicBaseUrl 的 host + trustedHosts。
- /api/pair/issue 需要一个 base 才肯签发(publicBaseUrl 或活着的 LAN base),否则回 409 lan-required。

3. / 上有 DSH 自己的浏览器鉴权(401,dsh web authentication required)。正门是 /pair-app:它把 loopback 的 index.html 抓下来、注入设备脚本再返回,不依赖 dsh-auth cookie。之后所有 API 走 /remote/ 并带 x-dsh-remote-device。

本仓库做的事,就是按这套规则把路铺通:

LAN: 手机 ──▶ <LAN-IP>:56875 ──[tcp-proxy]──▶ 127.0.0.1:<DSH> 异网: 手机 ──▶ https://x.trycloudflare.com ──▶ cloudflared └─▶ 127.0.0.1:56998 ──[tunnel-shim]──▶ 127.0.0.1:<DSH>

二、前置

- Windows 10/11(脚本是 PowerShell;JS 部分跨平台)
- Node.js 18+
- DSH 桌面版,且装了 @linxin666/dsh-web-all(它带 remote-web-ui)
- qrcode 和 qrcode-terminal 两个 npm 包(DSH profile 的 node_modules 里通常已经有)
- 只有 tunnel 模式需要 cloudflared.exe

把 cloudflared.exe 放到本仓库的 bin\ 下:

https://github.com/cloudflare/cloudflared/releases/latest/download/cloudflared-windows-amd64.exe

三、配置

编辑你的 profile patch(%USERPROFILE%\.dsh\profiles\<profile>\cordis.patch.yml),在末尾追加:

- id: web-ui-remote-web-ui # 必须是这个 id(web-all 的 shell 行),不是 remote-web-ui config: plugin: '@linxin666/dsh-remote-web-ui' # 缺这个键 → shell 静默 no-op,什么都不生效 trustedHosts:
- 192.168.1.10 # 你的 LAN IP(无端口 = 匹配任意端口)
- 127.0.0.1
publicBaseUrl: https://change-me.trycloudflare.com
requirePairingForLan: true
enabled: true

两个坑,都会静默失败:

- id 写错:@linxin666/dsh-web-all 里这一行的 id 是 web-ui-remote-web-ui,写 remote-web-ui 不会匹配到任何行。
- 少了 plugin 键:shell 的 isOverrideShape(row) 判定成立后直接 no-op,一行警告都没有。

改完即时热重载,不用重启 DSH。

四、用法

双击 go.cmd

[1] Same WiFi (tcp-proxy) -> pair-qr-lan.png
[2] Any network (cloudflared) -> pair-qr-tunnel.png
[3] Both at once -> both

默认 DSH 端口是 3080。实际端口看 DSH Web GUI 的地址,或者:

set DSH_PORT=58202 && go.cmd
rem 顺手改 profile 配置(推荐)
set DSH_PORT=58202 && set DSH_PROFILE=desktop && go.cmd

命令行

同 WiFi powershell -NoProfile -ExecutionPolicy Bypass -File .\pair-qr.ps1 -Mode lan -DshPort 3080 -Profile desktop

异网(起隧道 + 出码) powershell -NoProfile -ExecutionPolicy Bypass -File .\pair-qr.ps1 -Mode tunnel -DshPort 3080 -Profile desktop

两条一起出(先起隧道,publicBaseUrl 用隧道域名) powershell -NoProfile -ExecutionPolicy Bypass -File .\pair-qr.ps1 -Mode both -DshPort 3080 -Profile desktop

参数:

| 参数 | 默认 | 说明 |
| -Mode | lan | lan / tunnel / both |
| -DshPort | 3080 | DSH webserver 端口 |
| -LanPort | 56875 | LAN 转发监听端口 |
| -ShimPort | 56998 | 隧道 shim 监听端口 |
| -Ip | 自动探测 | LAN 侧对外宣告的地址 |
| -Node | 自动查找 | node.exe 路径。PATH 上找不到 node 时,退到 DSH 自带的 runtime |
| -QrcodeModule | 自动查找 | qrcode 包路径 |
| -Profile | 空 | 传了就顺手把该 profile 的 cordis.patch.yml 改成正确状态(见下),并留 .bak-dshmobile 备份 |

-Profile 会做三件事,省掉手改 YAML:

1. 没有 web-ui-remote-web-ui 块就补一整块(带检测到的 LAN IP);
2. 有块但 trustedHosts 里没有当前 LAN IP,就插一行;
3. 把 publicBaseUrl 指向本次要签发的 base。

both 模式下隧道先起,publicBaseUrl 用隧道域名;lan 模式用 http://<LAN-IP>:<LanPort>。改完等 1.2 秒热重载再签发。

一次调用里两种模式共享同一枚配对令牌。插件同一时刻只保留最后一枚,签两次会把先出的码顶掉——这就是 pair-qr.ps1 里 Get-PairToken 缓存的原因。

手机上

1. 扫码 → /pair-accept?pair=…
2. 不用点任何东西,自动 303 → 写一年期 dsh_pair cookie → 进 /pair-app → 主界面
3. 想加到主屏,收藏跳转后的根路径,不要收藏 /pair-accept?pair=…(令牌 10 分钟过期)
4. 想要桌面版排版:控制台执行 sessionStorage.setItem('dsh-remote-force-desktop','1'); location.reload()

下次重进:同域名直接开首页即可(Service Worker 会把 / 的导航改走 /pair-app,cookie 还在就自动重新配对)。

必须重扫码的情况:隧道重启换了域名、清了 cookie、撤销了设备、超了 maxDevices(默认 4)。

验证

node .\scripts\verify.js --url "https://xxx.trycloudflare.com/pair-accept?pair=..."

跑 headless Chrome,看四件事:SLOTS(SPA 是否真的挂载)、OWNS(host grant 是否送达)、WS(/remote/api/remote.mux 是否连上)、REQ/RESP。curl 只能证明服务端在,证明不了 SPA 能跑起来。

五、两个脚本为什么存在

scripts/tcp-proxy.js

纯 TCP 字节转发。不改写任何 HTTP 头——Host 必须原样到达 DSH,否则 trustedHosts 栅栏对不上。

为什么不直接改 DSH 的绑定地址:见上面第一节,Config 拒绝 0.0.0.0,而桌面宿主又是以 127.0.0.1 起的。

scripts/tunnel-shim.js

remote-web-ui 的 trustedHosts 在 zod schema 里不是 volatile 字段,而且它的 apply 包在 mountOnce 里——插件已挂载时改 cordis.patch.yml 不会重跑 applyImpl,内存里仍是旧域名,于是每个新的 trycloudflare 域名都 403。

不重启 DSH 的唯一办法:让到达 DSH 的请求「长得像回环请求」。

1. Host 改写成 127.0.0.1:<port> —— isLoopbackClient() 要求 Host 是回环且 socket.remoteAddress 也是回环,shim 从 127.0.0.1 连出,两条都满足。
2. 删掉 Origin / Referer —— isTrustedApiRequest 在有 Origin 时会拿它和 Host 比,Host 已被改写,不删就失配。
3. 响应的 Location 里 loopback 地址改写成公网 https origin,否则手机会跳 http://127.0.0.1 然后 ERR_CONNECTION_REFUSED。

安全约束:因为 shim 把一切都伪装成回环,loopbackFence 守护的桌面控制端点会被顺带放行——

POST /api/pair/issue 凭空签发配对令牌 POST /api/pair/stop 停掉本次配对 POST /api/pair/revoke 吊销任意设备

tunnel-shim.js 对这三条硬编码 403(LOOPBACK_ONLY)。不要删掉那个检查。

六、异网为什么选 cloudflared(踩坑记录)

这一段是特定网络环境的实测结论,换网络要重新验。

| 方案 | 结果 |
| localhost.run(lhr.life) | 通,但限速 ~23 KB/s。启动聚合包 9.2 MB → 6.6 分钟,页面 15s watchdog 必然 reload |
| localtunnel(loca.lt) | 带宽好(600 KB/s),但有强制提示页(手机无法加 bypass-tunnel-reminder 头)+ 间歇 408/503 |
| serveo.net | 明示 252 KiB/s 上限,10 分钟过期,免费版还有强制警告页 |
| bore.pub | npm 的 bore-cli 是 client+inspector,不是 bore.pub 隧道客户端 |
| pinggy | SSH 连上后长时间无任何输出 |
| cloudflared quick tunnel | 9.2 MB / 3.1 s ≈ 3 MB/s,快 130 倍 —— 采用 |

cloudflared 本身还有三个坑,pair-qr.ps1 都处理了:

1. 系统 DNS 拒绝解析 SRV → lookup argotunnel.com: dnsquery: DNS operation refused.,edge discovery 直接失败。
→ 用 $env:TUNNEL_EDGE 显式给 IP:7844。端口必须带,只写 IP 会报 missing port in address。
2. 系统 DNS 只回 AAAA → 新域名本机只能解析到 2606:4700::…,curl 直接 000。
→ 走 DoH(223.5.5.5/resolve)取 IPv4 再 --resolve 钉住。手机端不受影响(运营商 DNS 正常)。
3. QUIC 会被中途掐断 → accept stream listener error: … no recent network activity,随后域名 530。
→ --protocol http2 --ha-connections 4。单连接注册成功也仍可能 530,4 条才稳。

已知限制:quick tunnel 域名每次重启都变,无可用性保证。掉线就重跑脚本;要固定域名得用 named tunnel(需要 Cloudflare 账号和 tunnelToken)。

七、文件表

| 文件 | 作用 |
| go.cmd | 双击入口,菜单三选一 |
| pair-qr.ps1 | 两种模式的统一出码器 |
| scripts/tcp-proxy.js | LAN 纯 TCP 转发 |
| scripts/tunnel-shim.js | 隧道前置,改写 Host / Origin / Location |
| scripts/make-pair-qr.js | 一步签发 + 生成 PNG(给 IM 机器人用) |
| scripts/termqr.js | 终端二维码 |
| scripts/verify.js | headless Chrome 端到端验证 |
| bin/cloudflared.exe | 自备,见上文 |

运行期生成的:pair-url-.txt、pair-qr-*.png、public-host.txt、proxy.log、shim.log、cf.log、cf.err。

给 IM 机器人用

如果 DSH 接了 @michengai/dsh-im-connect,可以让微信/钉钉会话自己发二维码:

node scripts/make-pair-qr.js --port <DSH> --out <会话 cwd>

生成的 PNG 只要落在会话 cwd 内、并且在同一回合用 present 工具声明过,IM 插件就会随回复把它发出去。

八、回滚

1. 配置:把追加的那段 web-ui-remote-web-ui 删掉,或恢复备份
2. 进程:Get-Process cloudflared | Stop-Process -Force;tcp-proxy / tunnel-shim 是 node 进程,命令行含对应文件名
3. 手机端:清该站点 cookie(dsh_pair),或 POST /api/pair/revoke {"deviceId":"…"}(必须本机调)

九、许可

MIT。
预览分享卡片长什么样 ↓sifvbs/dsh-mobile
分享 · DeepSeek Harness Hub
sifvbs/dsh-mobile

把桌面 Web 界面安全投到手机浏览器上使用

这是你的仓库?认领条目

回到顶部