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

远程接入面板 shimiei/dsh-webim

DeepSeek 客户端
shimiei10 天前有提交已提供中文文档扫描:含敏感能力兼容 / 相关生态

扫码用手机或异地浏览器打开本机 DSH 界面

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

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

相关生态插件(非 dsh 原生,按其对应运行时安装)
git clone https://github.com/shimiei/dsh-webim.git

状态速览

判定依据:实装验证 · 静态安装检查 · 自动扫描 · 仓库维护状态
信任档位:仅索引
是什么
生态应用
桌面端 / Web 外壳,不以 dsh plugin add 安装
装得上吗
本站尚未做安装检查
安全吗
本站尚未对该插件做风险分级
暂未覆盖,不等同于无风险
还在维护吗
活跃:最近一次提交在 10 天前
档位由哪些信号合成

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

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

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

可执行外部命令读写/删除本地文件写入剪贴板(不计风险)发起外部网络请求
查看扫描证据(共 88 处,展示前 74 条)
  • 读取本地文件
    lib/index.js:59
    if (existsSync(logFile) && statSync(logFile).size > 256 * 1024) writeFileSync(logFile, '')
  • 读取本地文件
    lib/index.js:59
    if (existsSync(logFile) && statSync(logFile).size > 256 * 1024) writeFileSync(logFile, '')
  • 写入本地文件
    lib/index.js:59
    if (existsSync(logFile) && statSync(logFile).size > 256 * 1024) writeFileSync(logFile, '')
  • 写入本地文件
    lib/index.js:60
    appendFileSync(logFile, `${line}\n`)
  • 发起网络请求
    lib/index.js:132
    const request = http.get(
  • 读取本地文件
    lib/index.js:217
    return existsSync(file) ? readFileSync(file, 'utf8').trim() : ''
  • 读取本地文件
    lib/index.js:217
    return existsSync(file) ? readFileSync(file, 'utf8').trim() : ''
  • 发起网络请求
    lib/index.js:237
    const response = await fetch(url, { headers, redirect: 'manual', signal: AbortSignal.timeout(8_000) })
  • 请求外部主机api.ipify.org
    lib/index.js:287
    const response = await fetch('https://api.ipify.org?format=json', {
  • 发起网络请求
    lib/index.js:287
    const response = await fetch('https://api.ipify.org?format=json', {
  • 写入本地文件
    lib/index.js:580
    mkdirSync(config.stateDir, { recursive: true, mode: 0o700 })
  • 写入本地文件
    lib/index.js:581
    writeFileSync(
  • 写入剪贴板
    client.js:330
    await navigator.clipboard.writeText(text)
  • 读取本地文件
    lib/gateway.js:177
    const info = await stat(fileUrl)
  • 读取本地文件
    lib/gateway.js:180
    const content = await readFile(fileUrl)
  • 写入本地文件
    bin/serve.mjs:160
    mkdirSync(stateDir, { recursive: true })
  • 写入本地文件
    bin/serve.mjs:162
    writeFileSync(
  • 读取本地文件
    bin/serve.mjs:174
    if (!existsSync(stateFile)) return
  • 读取本地文件
    bin/serve.mjs:175
    const saved = JSON.parse(readFileSync(stateFile, 'utf8'))
  • 删除本地文件
    bin/serve.mjs:176
    if (saved.pid === process.pid) unlinkSync(stateFile)
  • 发起网络请求
    bin/serve.mjs:220
    const request = http.get(
  • 读取本地文件
    lib/credentials.js:50
    const text = readFileSync(file, 'utf8')
  • 读取本地文件
    lib/token.js:88
    if (existsSync(this.file)) {
  • 读取本地文件
    lib/token.js:89
    const raw = JSON.parse(readFileSync(this.file, 'utf8'))
  • 写入本地文件
    lib/token.js:123
    mkdirSync(this.stateDir, { recursive: true, mode: 0o700 })
  • 写入本地文件
    lib/token.js:125
    writeFileSync(
  • 写入本地文件
    lib/token.js:135
    renameSync(temp, this.file)
  • 写入本地文件
    lib/token.js:137
    chmodSync(this.file, 0o600)
  • 读取本地文件
    lib/urls.js:94
    if (!existsSync(this.file)) return
  • 读取本地文件
    lib/urls.js:95
    const saved = readFileSync(this.file, 'utf8').trim()
  • 读取本地文件
    lib/urls.js:120
    if (existsSync(this.file)) unlinkSync(this.file)
  • 删除本地文件
    lib/urls.js:120
    if (existsSync(this.file)) unlinkSync(this.file)
  • 写入本地文件
    lib/urls.js:123
    mkdirSync(this.dir, { recursive: true, mode: 0o700 })
  • 写入本地文件
    lib/urls.js:124
    writeFileSync(this.file, `${normalized}\n`, { encoding: 'utf8', mode: 0o600 })
  • 读取本地文件
    lib/urls.js:161
    if (!existsSync(this.file)) return
  • 读取本地文件
    lib/urls.js:162
    const saved = normalizePublicUrl(readFileSync(this.file, 'utf8'))
  • 读取本地文件
    lib/urls.js:180
    if (existsSync(this.file)) unlinkSync(this.file)
  • 删除本地文件
    lib/urls.js:180
    if (existsSync(this.file)) unlinkSync(this.file)
  • 写入本地文件
    lib/urls.js:183
    mkdirSync(this.dir, { recursive: true, mode: 0o700 })
  • 写入本地文件
    lib/urls.js:184
    writeFileSync(this.file, `${normalized}\n`, { encoding: 'utf8', mode: 0o600 })
  • 读取本地文件
    lib/features.js:109
    if (!existsSync(this.file)) return
  • 读取本地文件
    lib/features.js:110
    const raw = JSON.parse(readFileSync(this.file, 'utf8'))
  • 读取本地文件
    lib/features.js:164
    if (existsSync(this.file)) unlinkSync(this.file)
  • 删除本地文件
    lib/features.js:164
    if (existsSync(this.file)) unlinkSync(this.file)
  • 写入本地文件
    lib/features.js:173
    mkdirSync(this.dir, { recursive: true, mode: 0o700 })
  • 写入本地文件
    lib/features.js:175
    writeFileSync(temp, `${JSON.stringify({ version: 1, features: this.value }, null, 2)}\n`, {
  • 写入本地文件
    lib/features.js:179
    renameSync(temp, this.file)
  • 执行外部命令固定命令
    lib/tunnel.js:104
    const killer = spawn('taskkill', ['/pid', String(pid), '/f', '/t'], { windowsHide: true, stdio: 'ignore' })
  • 读取本地文件
    lib/tunnel.js:130
    if (existsSync(candidate) && statSync(candidate).isFile()) return candidate
  • 读取本地文件
    lib/tunnel.js:130
    if (existsSync(candidate) && statSync(candidate).isFile()) return candidate
  • 读取本地文件
    lib/tunnel.js:197
    if (existsSync(this.tokenFile)) this.token = readFileSync(this.tokenFile, 'utf8').trim()
  • 读取本地文件
    lib/tunnel.js:197
    if (existsSync(this.tokenFile)) this.token = readFileSync(this.tokenFile, 'utf8').trim()
  • 读取本地文件
    lib/tunnel.js:202
    if (existsSync(this.namedFile)) {
  • 读取本地文件
    lib/tunnel.js:203
    const raw = JSON.parse(readFileSync(this.namedFile, 'utf8'))
  • 写入本地文件
    lib/tunnel.js:224
    mkdirSync(this.stateDir, { recursive: true, mode: 0o700 })
  • 写入本地文件
    lib/tunnel.js:226
    writeFileSync(temp, `${JSON.stringify({ url: this.url, startedAt: this.startedAt, mode: this.mode }, null, 2)}\n`, {
  • 写入本地文件
    lib/tunnel.js:230
    renameSync(temp, this.file)
  • 写入本地文件
    lib/tunnel.js:260
    mkdirSync(this.stateDir, { recursive: true, mode: 0o700 })
  • 写入本地文件
    lib/tunnel.js:262
    writeFileSync(this.tokenFile, `${this.token}\n`, { encoding: 'utf8', mode: 0o600 })
  • 写入本地文件
    lib/tunnel.js:264
    chmodSync(this.tokenFile, 0o600)
  • 删除本地文件
    lib/tunnel.js:314
    if (pid === undefined || Number(raw && raw.pid) === pid) unlinkSync(this.lockFile)
  • 发起网络请求
    lib/tunnel.js:363
    const response = await fetch(`${DOWNLOAD_BASE}/${asset}`, { redirect: 'follow' })
  • 执行外部命令来自配置/变量
    lib/tunnel.js:428
    const child = spawn(binary, args, { windowsHide: true, stdio: ['ignore', 'pipe', 'pipe'] })
  • 执行外部命令固定命令
    lib/tunnel.js:533
    const killer = spawn('taskkill', ['/pid', String(pid), '/f', '/t'], { windowsHide: true, stdio: 'ignore' })
  • 执行外部命令来自配置/变量
    lib/tunnel.js:562
    const child = spawn(binary, args, { windowsHide: true, stdio: ['ignore', 'pipe', 'pipe'] })
  • 读取本地文件
    lib/security.js:46
    if (existsSync(file)) {
  • 读取本地文件
    lib/security.js:47
    const existing = fromB64url(readFileSync(file, 'utf8').trim())
  • 写入本地文件
    lib/security.js:53
    mkdirSync(dir, { recursive: true, mode: 0o700 })
  • 写入本地文件
    lib/security.js:55
    writeFileSync(file, `${b64url(created)}\n`, { encoding: 'utf8', mode: 0o600 })
  • 写入本地文件
    lib/security.js:57
    chmodSync(file, 0o600)
  • 发起网络请求
    lib/proxy.js:203
    const upReq = http.request({
  • 发起网络请求
    lib/proxy.js:341
    const upReq = http.request({
  • 读取本地文件
    lib/files.js:347
    createReadStream(real, { start: range.start, end: range.end }).pipe(res)
  • 读取本地文件
    lib/files.js:357
    createReadStream(real).pipe(res).on('error', (error) => {
扫描于 2026/11/4(0 天前) · 范围 入口闭包 + 发布文件清单 · 来源 GitHub 仓库
涉及外部主机:api.ipify.org

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

依赖的 DSH / Cordis 模块
@deepseek-ai/cordis
给这个插件打分还没有人投票↓
用户评分
还没有人投票,来当第一个

把本地 DeepSeek Harness 带到手机与异地浏览器:复用同一套 DSH 前端,token/二维码进入,内置 Cloudflare 隧道,零运行时依赖

README

由 DeepSeek 最新模型翻译生成
不改 DeepSeek Harness 的一行代码,就把这台机器的 DSH 变成手机、平板、异地浏览器都能打开的网站。 复用同一套 DSH 前端 · token 扫码即进 · 内置 Cloudflare 隧道 · 手机层单独适配 · 零运行时依赖 这是什么 DeepSeek Harness(DSH)默认只监听 127.0.0.1 的随机端口,同一个 Wi-Fi 下的手机都连不上,更别说人在外面。 dsh-webim 是一个 DSH 插件:它在 0.0.0.0:3081 起一个反向代理,把本机 Harness 的 HTTP 与 WebSocket 原样桥接出去,并在 DSH 的设置页里长出一块「远程接入」面板,负责入口地址、二维码、令牌、隧道和一堆运行时开关。 它不修改 DSH 本体:前端还是那套前端,工作区、会话、插件全都在,只是多了一个经过 token 校验的入口。远程浏览器拿到的是 DSH 自己的 UI,实际驱动的是这台机器上的 Harness 进程——本地绝对路径(工作区、file:// 引用、会话存储)永远留在服务端。
展开完整文档(README 剩余 1 节)
不改 DeepSeek Harness 的一行代码,就把这台机器的 DSH 变成手机、平板、异地浏览器都能打开的网站。
复用同一套 DSH 前端 · token 扫码即进 · 内置 Cloudflare 隧道 · 手机层单独适配 · 零运行时依赖

这是什么

DeepSeek Harness(DSH)默认只监听 127.0.0.1 的随机端口,同一个 Wi-Fi 下的手机都连不上,更别说人在外面。

dsh-webim 是一个 DSH 插件:它在 0.0.0.0:3081 起一个反向代理,把本机 Harness 的 HTTP 与 WebSocket 原样桥接出去,并在 DSH 的设置页里长出一块「远程接入」面板,负责入口地址、二维码、令牌、隧道和一堆运行时开关。

它不修改 DSH 本体:前端还是那套前端,工作区、会话、插件全都在,只是多了一个经过 token 校验的入口。远程浏览器拿到的是 DSH 自己的 UI,实际驱动的是这台机器上的 Harness 进程——本地绝对路径(工作区、file:// 引用、会话存储)永远留在服务端。

| 能力 | 说明 |
| 同一套前端 | 反向代理透传 HTTP + Upgrade(/api/remote.mux 实时流),不劫持、不改写 DSH 的页面 |
| 扫码即进 | 设置页内联 SVG 二维码(自研编码器,无外部图片请求,绕开 CSP img-src),扫完直接落到 DSH |
| 令牌可轮换 | 32 字节 base64url token,一次刷新即硬切:旧链接、旧二维码、已登录会话一起失效 |
| 三种接入方式 | 仅局域网 / 公网直连(自己的 IP 或域名)/ Cloudflare 快速隧道;一次只开一种,也能让隧道与公网并存 |
| 内置隧道 | 自动下载并拉起 cloudflared quick tunnel,几秒后拿到 https://xxx.trycloudflare.com,不用配端口映射 |
| 手机层 | 窄屏抽屉、输入框不再被键盘顶飞、设置页单列,interactive-widget=resizes-content |
| 运行时开关 | 总开关 / 局域网 / 公网 / 移动端 / 隧道 / 文件查看 / 路径转链接 / 浮动入口,全都不用重启 |
| 远程安全 | 令牌门 + 失败限流与封禁 + 上游 Cookie 由本机 .credentials.yaml 铸造,凭据不出机器 |
| 零依赖 | 只用 Node 标准库:QR 编码器、PNG 编码器、代理、二维码都是自己写的 |

截图

取自真实运行中的面板,所有 IP、域名、令牌与二维码都已替换成文档占位值(RFC 5737 / example.com)。

手机端(390pt):单列、入口卡片与二维码,扫码即进

三种接入方式

设置页只有一块分段控件,选完即生效(内部就是三个布尔开关的组合):

| 方式 | 实际写入 | 效果 |
| 仅局域网 | {lan: true, public: false, tunnel: false} | 只出 http://<本机 IP>:3081/?token=…,同一 Wi-Fi 可用,不暴露到公网 |
| 公网直连 | {lan: true, public: true, tunnel: false} | 先列你保存的公网 IP / 域名(自己做端口映射或反代),再列局域网地址 |
| Cloudflare 隧道 | {lan: true, public: false, tunnel: true} | 启动后追加 https://xxx.trycloudflare.com;公网地址默认不参与,需要时勾卡片里的「同时列出公网直连地址」 |

三个开关各自独立,也各有含义:

| 开关 | 关掉之后 |
| 总开关 | 监听彻底停掉:3081 无监听、隧道一起停、入口列表清空;桌面端不受影响 |
| 局域网可访问 | 监听从 0.0.0.0 重绑到 127.0.0.1,同网段设备连不上,隧道照旧(它连的是本机回环) |
| 公网入口 | 不再展示保存的公网地址,隧道不受影响(v1.5.1 起两者解耦) |
二维码永远跟随被点选的那张入口卡,不是「排在第一个的那个」;指向消失时自动回落到列表第一项,不会留死链。

快速开始

要求:Windows / macOS / Linux,Node ≥ 18,已安装并能正常打开 DeepSeek Harness。

git clone https://github.com/<you>/dsh-webim.git
cd dsh-webim

装进 DSH 的 desktop profile(建链接 + 写 patch,幂等)
node scripts/install.mjs

然后重启 / 刷新 DSH 窗口,打开 设置 → 远程接入:

1. 顶栏确认「入口运行中」,记住 host:port(默认 0.0.0.0:3081);
2. 选一种接入方式(想最省事就选 Cloudflare 隧道,等几秒拿到 https 域名);
3. 点入口卡片选中它,扫右侧二维码,或直接复制链接。

想手动安装也可以:在 <profile>/node_modules/ 下指向本仓库建一个链接,把 dsh-webim 加进 profile 的 dsh.profile.bundles,再在 cordis.patch.yml 里加一段配置(见下一节)。

不用插件也能跑(调试用)

node bin/serve.mjs --host 0.0.0.0 --port 3081

它和插件共用 lib/gateway.js,只是没有设置页,参数从命令行来。

配置

配置写在 profile 的 cordis.patch.yml(用户覆盖层),或直接改插件自带 cordis.patch.yml 的默认值:

- id: dsh-webim
config:
enabled: true # 插件是否加载(与「是否监听」是两件事) host: "0.0.0.0" # 监听地址;0.0.0.0 = 所有网卡 port: 3081 password: "" # 口令门;留空 = 只认 token 链接 accessToken: "" # 固定 token;留空 = 自动生成,可在设置页刷新 publicUrl: "" # 公网 IP / 域名(含端口),如 http://1.2.3.4:3081 sessionDays: 30 maxFailures: 8 # 同一 IP 失败多少次锁定 failureWindowMinutes: 10 blockMinutes: 15
allowedRoots: [] # 远程文件查看允许的根;空 = 所有盘符
deniedPaths: [] # 额外拒绝路径(默认已拒绝 $DSH_HOME 里的凭据)
linkifyPaths: true
logRequests: false
tunnelBinary: "" # 指定现成的 cloudflared;留空 = 用下面自动下载的那份 tunnelAutoInstall: true # false = 不自动下载 cloudflared features: # 只提供首启默认值,运行时以 features.json 为准 enabled: true lan: true public: true mobileUi: true tunnel: false fileBrowser: true linkifyPaths: true pill: true

运行时状态都在 $DSH_HOME/webim/:token.json、public-url.txt、qr-target.txt、features.json、tunnel.json、plugin.log(插件日志,排障用)、cloudflared/。删掉对应文件即回到默认。

工作原理

几条关键设计:

- 只在入口校验:token 或口令任一通过即铸造 DSH 自己的 dsh-auth- Cookie,之后由上游继续认这个 Cookie;网关不缓存任何会话内容。
- WebSocket 桥接:Upgrade 直接接到上游,/api/remote.mux 的实时流不断。
- 隧道与监听解耦:隧道连的是本机回环,所以关掉局域网、甚至只绑 127.0.0.1,隧道仍然可用。
- 设置页 RPC 先回包再干活:切模式会重绑端口,导致「回包跟着旧连接一起死」;因此面板先就地更新,再后台轮询等状态回来。
- QR 自研:Byte 模式、版本 1–20、L/M/Q/H、8 种掩码,输出内联 SVG;tests/qr.test.mjs 与 Python qrcode 做了矩阵对照。

目录结构

lib/ 网关、代理、安全、文件、隧道、二维码、入口地址推导 assets/ 注入到 DSH 前端的小补丁(移动端抽屉、状态胶囊、路径点击) client.js 设置页(React,走 DSH 的 slot 与设计 token) bin/serve.mjs 独立模式入口 scripts/ 安装 / 卸载 tests/ 81 个测试(安全、网关、token、插件、客户端契约、二维码、隧道) tools/ 无依赖调试工具(CDP 探针、截图脱敏、banner / 架构图合成) docs/ banner、图标、README 截图

开发与测试

node --test tests/.test.mjs

当前 81 个用例全部通过(约 1.5 秒):安全 8、网关 14、token-only 6、token 10、host 插件 7、client 契约 11、二维码 11、功能开关 4、隧道 5、入口地址过滤 1。

界面另有几个零依赖探针(需先手动起 headless Edge 并打开调试端口):

node tools/probe-remote.mjs <debug-port> <入口链接> out.png --eval-file tools/eval-ui-report.mjs
node tools/make-shots.mjs <debug-port> <入口链接> docs/shots # 生成脱敏截图
python tools/make-banner.py # 重新合成 banner 与图标
python tools/make-arch.py # 重新绘制架构图

改完代码要让它生效:node scripts/install.mjs 触发宿主热重载;只改 cordis.patch.yml 里的注释不算变化,得动一个值(例如 sessionDays 30 ↔ 31)。

安全须知

远程入口意味着别人拿到链接就等于坐在你的 DSH 前面,请至少做到:

1. 别用弱口令:口令门默认关闭,走 token;要开口令就给个像样的。
2. 公网优先用 HTTPS:Cloudflare 隧道 天然 https;自己做端口映射时建议反代加证书,并透传 X-Forwarded-Proto: https(网关据此给 Cookie 加 Secure)。
3. 令牌会随刷新硬切:刷新后旧链接、旧二维码、已登录浏览器全部失效,这是设计。
4. 文件查看有白名单:默认拒绝 $DSH_HOME 里的凭据与状态目录,可用 allowedRoots / deniedPaths 收紧。
5. 不用就关总开关:3081 会真的停掉,隧道一起停。

常见问题

| 现象 | 处理 |
| 设置了但手机连不上 | 确认防火墙放行 TCP 3081;Get-NetTCPConnection -LocalPort 3081 应有监听;路由器/云主机安全组也要放行 |
| 隧道一直起不来 | 首次要下 ~55 MB 的 cloudflared(看 $DSH_HOME/webim/cloudflared/);企业网络挡 github.com / *.trycloudflare.com 时会失败;可用 tunnelBinary 指定现成二进制 |
| 点了隧道按钮没反应 | v1.5.1 起有转圈 + 计时 + 占位卡片;若 30 秒没域名,看 $DSH_HOME/webim/plugin.log(含 cloudflared 最后几行输出) |
| 切换模式后页面卡一下 | 预期行为:重绑端口会打断当前页面连接,面板每 1.2 秒自动重连,3–5 秒恢复 |
| 二维码扫出来不是我想的那个 | 二维码跟随被点选的卡片;点一下想要的那张,下方会写「扫码目标 · …」 |
| 刷新令牌后自己也进不去 | 设计如此:会话签名把 token 当盐,轮换后旧会话全部失效,重新扫码即可 |
| 端口被占(EADDRINUSE) | 已有旧网关在跑;插件用 Symbol.for('dsh-webim.gateway.v1') 注册表在 bind 前关掉上一代,/__webim/ping 的 instance.tokenId 可看是谁在应答 |

已知限制

- 隧道用的是 quick tunnel(免账号),域名每次启动都变,Cloudflare 也不保证可用性;长期使用请换 named tunnel + 自己的域名。
- 网关到本机 Harness 之间是明文回环 HTTP,本机进程可读;跨机安全靠外层 https。
- 只桥接 HTTP/WS,不做协议改写,所以 DSH 前端的所有能力都取决于上游本身。
- 移动端适配是"补丁式"的:DSH 大版本换 DOM 结构时,assets/client.css 里的选择器可能需要跟进。

License

MIT。与 DeepSeek / DeepSeek Harness 官方无关,属于第三方插件。
预览分享卡片长什么样 ↓shimiei/dsh-webim
分享 · DeepSeek Harness Hub
shimiei/dsh-webim

扫码用手机或异地浏览器打开本机 DSH 界面

这是你的仓库?认领条目

回到顶部