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

远程移动安全网关 IceApriler/dsh-remote-mobile

DeepSeek Harnessv1.10.2
IceApriler1 天前有提交已提供中文文档扫描:含敏感能力

DeepSeek Harness 远程与移动端安全网关插件:不改 DSH 底层代码,安全开放局域网与 Tailscale 连接,扫码认证、RSA 加密、防暴力破解。

50.8/100
综合分
41
GitHub 星标
1,575
npm 周下载
—/5
用户评分

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

安装命令 · 需先装 dsh CLI 引擎
dsh plugin --profile web add dsh-remote-mobile
npm 源 · dsh-remote-mobile 的发布者账号已校验与本仓库一致

状态速览

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

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

验证判据与等级说明 →
社区实装还没有人打卡 · 装过的第一个就是你
安装兼容性检查✓ 自动检查通过
展开查看检查明细 ↓

以下结论由程序自动检查 npm 包、engines 声明与入口文件得出,未做人工实机验证——能装不等于用着没问题。

✓npm 包dsh-remote-mobile @ 1.10.2
✓Node 引擎要求 >=18.0.0 · 基线 Node 22.19 满足
✓dsh CLI 依赖未声明 dsh 版本约束
✓入口文件main/exports/bin 已声明

验证方式:npm registry 存在性 + package.json 静态校验 · 最后检查 2026/10/31 22:55:51

安全扫描自动 · 静态分析源码
v2.7.0
🔵 扫描:含敏感能力尚无人工评估
展开查看扫描说明与证据 ↓

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

可执行外部命令读写/删除本地文件写入剪贴板(不计风险)发起外部网络请求
查看扫描证据(共 58 处,展示前 46 条)
  • 发起网络请求
    lib/client.js:67
    return fetch(url, options).then(function(res) {
  • 写入剪贴板
    lib/client.js:2815
    navigator.clipboard.writeText(path).catch(() => {
  • 写入剪贴板
    lib/client.js:3704
    navigator.clipboard.writeText(cssText).then(() => showFeedback(t("stylesCopyCssDone", lang))).catch(() => {
  • 请求外部主机registry.npmjs.org
    lib/client.js:4243
    fetch("https://registry.npmjs.org/dsh-remote-mobile/latest", { cache: "no-store" }).then((res) => res.ok ? res.json() : Promise.reject(new Error("npm
  • 发起网络请求
    lib/client.js:4243
    fetch("https://registry.npmjs.org/dsh-remote-mobile/latest", { cache: "no-store" }).then((res) => res.ok ? res.json() : Promise.reject(new Error("npm
  • 写入剪贴板
    lib/client.js:4650
    navigator.clipboard.writeText(text).then(triggerToast).catch(() => {
  • 读取本地文件
    lib/startup-hint.js:11
    for (const entry of readdirSync(join(DSH_HOME, 'profiles'), { withFileTypes: true })) {
  • 读取本地文件
    lib/startup-hint.js:19
    if (!existsSync(file))
  • 读取本地文件
    lib/startup-hint.js:23
    content = readFileSync(file, 'utf8');
  • 读取本地文件
    lib/auth/token.js:25
    if (!existsSync(filePath))
  • 读取本地文件
    lib/auth/token.js:27
    const content = readFileSync(filePath, 'utf8');
  • 读取本地文件
    lib/auth/token.js:114
    if (!existsSync(filePath))
  • 读取本地文件
    lib/auth/token.js:116
    return parser(readFileSync(filePath, 'utf8'));
  • 读取本地文件
    lib/auth/token.js:140
    names = readdirSync(profilesDir, { withFileTypes: true })
  • 读取本地文件
    lib/auth/token.js:173
    if (!existsSync(filePath))
  • 写入本地文件
    lib/auth/token.js:218
    mkdirSync(dirname(filePath), { recursive: true });
  • 写入本地文件
    lib/auth/token.js:231
    writeFileSync(tmpPath, JSON.stringify(payload, null, 2), { encoding: 'utf8', mode: 0o600 });
  • 写入本地文件
    lib/auth/token.js:232
    renameSync(tmpPath, filePath);
  • 写入本地文件
    lib/auth/token.js:234
    chmodSync(filePath, 0o600);
  • 删除本地文件
    lib/auth/token.js:242
    unlinkSync(tmpPath);
  • 写入本地文件
    lib/auth/token.js:1054
    mkdirSync(dirname(this.persistPath), { recursive: true });
  • 写入本地文件
    lib/auth/token.js:1059
    writeFileSync(this.persistPath, JSON.stringify(payload, null, 2), 'utf8');
  • 读取本地文件
    lib/styles/style-snippets.js:766
    if (!existsSync(this.filePath))
  • 读取本地文件
    lib/styles/style-snippets.js:768
    const raw = readFileSync(this.filePath, 'utf8');
  • 写入本地文件
    lib/styles/style-snippets.js:872
    mkdirSync(dirname(this.filePath), { recursive: true });
  • 写入本地文件
    lib/styles/style-snippets.js:873
    writeFileSync(tmpPath, JSON.stringify(payload, null, 2), 'utf8');
  • 写入本地文件
    lib/styles/style-snippets.js:874
    renameSync(tmpPath, this.filePath);
  • 读取本地文件
    lib/styles/style-snippets.js:878
    if (existsSync(tmpPath))
  • 删除本地文件
    lib/styles/style-snippets.js:879
    unlinkSync(tmpPath);
  • 执行外部命令固定命令
    lib/auth/tailscale.js:65
    const out = execSync('tailscale ip -4', { encoding: 'utf8', timeout: 800, stdio: ['ignore', 'pipe', 'ignore'] }).trim();
  • 读取本地文件
    lib/bridge/directory-picker.js:61
    if (existsSync(webPatch))
  • 读取本地文件
    lib/bridge/directory-picker.js:65
    if (existsSync(profilesDir)) {
  • 读取本地文件
    lib/bridge/directory-picker.js:66
    const dirs = readdirSync(profilesDir, { withFileTypes: true })
  • 读取本地文件
    lib/bridge/directory-picker.js:73
    if (existsSync(candidate))
  • 写入本地文件
    lib/bridge/directory-picker.js:533
    mkdirSync(dirname(filePath), { recursive: true });
  • 写入本地文件
    lib/bridge/directory-picker.js:534
    writeFileSync(tmpPath, content, 'utf8');
  • 写入本地文件
    lib/bridge/directory-picker.js:535
    renameSync(tmpPath, filePath);
  • 读取本地文件
    lib/bridge/directory-picker.js:539
    if (existsSync(tmpPath))
  • 删除本地文件
    lib/bridge/directory-picker.js:540
    unlinkSync(tmpPath);
  • 读取本地文件
    lib/bridge/directory-picker.js:553
    if (existsSync(patchFilePath)) {
  • 发起网络请求
    lib/gateway/server.js:353
    const proxyReq = http.request({
  • 发起网络请求
    lib/gateway/server.js:459
    const proxyReq = http.request({
  • 读取本地文件
    lib/auth/crypto.js:9
    if (existsSync(RSA_KEY_FILE)) {
  • 读取本地文件
    lib/auth/crypto.js:10
    const content = readFileSync(RSA_KEY_FILE, 'utf8');
  • 写入本地文件
    lib/auth/crypto.js:31
    mkdirSync(dirname(RSA_KEY_FILE), { recursive: true });
  • 写入本地文件
    lib/auth/crypto.js:32
    writeFileSync(RSA_KEY_FILE, JSON.stringify(pair, null, 2), { encoding: 'utf8', mode: 0o600 });
扫描于 2026/11/3(1 天前) · 范围 入口闭包 + 全包扫描 · 来源 npm 发布包
涉及外部主机:registry.npmjs.org

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

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

界面预览

6 张 · 点击查看大图

功能亮点

  • 零修改底层代码
  • 扫码认证与RSA加密
v1.10.32026-11-03
· [1.10.3] - 2026-11-03 · Fixed (解决 Issue #11 · 挂载网关生命周期至 ctx.effect,解决 DSH Desktop 更新重启失败与进程退出挂起) · 问题背景:在 DSH Desktop(Electron 环境)中执行「更新并重启」时,宿主 shell 发送 shutdown 并断开 IPC,等待 Node.js 事件循环排空以实现优雅关停(graceful teardown)。由于旧版插件仅在 ctx.on('dispose') 上挂载网关关闭逻辑,而宿主在关闭 host 进程时并不会触发 dispose 事件(仅执行 ctx.effect 注册的 Teardown 清理回调),导致安全接入代理 HTTP Server(默认 0.0.0.0:3081)持续保持监听句柄,事件循环无法自然排空。10 秒后触发宿主优雅退出超时(graceful deadline exceeded),导致桌面端更新 100% 失败并报错退出。 · 生命周期注册对齐宿主规范(src/index.ts): - 抽取网关关闭与清理逻辑为幂等的 cleanupGateway 函数; - 优先通过 ctx.effect(() => cleanupGateway, 'dsh-remote-mobile:gateway-lifecycle') 将网关生命周期与宿主副作用体系绑定,与 registerWebRoutes 的生命周期管理保持高度一致; - 保留 ctx.on?.('dispose', cleanupGateway) 作为旧宿主环境及显式卸载/重载场景的兜底,并增加内部防重守卫确保幂等执行。 · 强化安全接入代理关停与防死锁保护(src/gateway/server.ts): - GatewayServer.close() 主动调用 Node.js 18.2+ 原生连接清理 API(closeIdleConnections?.() 与 closeAllConnections?.()),彻底释放所有残留连接; - 引入 2 秒超时兜底保护机制(setTimeout + Promise.race 逻辑),防止底层套接字在极端异常时未触发回调造成事件循环挂死。 · 测试覆盖(test/lifecycle.test.js): - 新增全套生命周期与优雅退出测试,覆盖 ctx.effect 规范绑定、cleanupGateway 重复触发的幂等性、未监听时安全退出、原生连接清理方法调用以及底座挂起时的 2 秒超时兜底。
v1.10.22026-10-30
· [1.10.2] - 2026-10-30 · Added & Fixed (移动端添加工作区 / 选择文件夹受限检测与一键开启网页目录选择器) · 问题背景:DSH 官方默认目录选择器 dsh-host-directory-picker-auto(id: directory-picker)仅在进程启动时根据 webServer.host 是否绑定 127.0.0.1 回环来判断“本机 / 远程”。由于本插件默认采用安全接入代理模式(DSH 核心保持 127.0.0.1:3080 回环,由 3081 代理对外),手机端点击「添加工作区 / 选择文件夹」时会被 auto 误判为本机桌面操作,将原生文件夹选择窗(native)弹在 PC 屏幕上,导致手机端一直等待且无法添加工作区。 · 双维度精准状态检测(src/bridge/directory-picker.ts): - 结合 Cordis 运行时 loader.entries() 与 ~/.dsh/profiles/web/cordis.patch.yml 配置文件解析,精准识别当前是否已启用应用内网页目录选择器(@deepseek-ai/dsh-host-directory-picker-browse + @deepseek-ai/dsh-client-ui-directory-picker-browse)、是否仅部分配置(patchPartial)、是否待重启/热重载生效(restartRequired),以及是否需要展示受限提醒(needsWarning); - /api/remote-mobile/status 新增 directoryPicker 状态契约字段;终端启动就绪横幅(printReadyBanner)在未开启网页目录选择器时同步输出友好提示。 · 严格幂等去重的补丁写入与无损还原: - 新增 POST /api/remote-mobile/directory-picker 路由(exact 契约),支持在 Web 设置页一键开启(enabled: true)或恢复 PC 原生目录选择器(enabled: false); - 严格结构化去重:写入 ~/.dsh/profiles/web/cordis.patch.yml 时,若已存在 - id: directory-picker(未设 disabled 或为 false)则就地更新为 disabled: true 并清理重复块;对 directory-picker-browse 与 ui-directory-picker-browse 逐项独立查重,仅补充缺失项,绝不产生重复条目; - 安全无损还原:恢复 PC 原生目录选择器时,仅移除与目录选择器相关的禁用项、两个 browse 子项及专属注释,若与其它插件共用 - insert: 块则完整保留共用块与用户其它配置。 · 设置面板醒目提醒横幅与常驻控制区: - 顶部醒目提醒横幅(MainSection.jsx):当处于回环核心且未启用 browse 模式时,在「设置 -> 远程与移动端」顶部展示醒目提醒,提供 「⚡ 一键开启网页目录选择器」 与 「📋 复制 YAML 配置」 按钮;写入后若尚未热重载生效,自动切换为蓝色待生效提示; - 「接入」页签常驻开关(ProxyConfigCard.jsx):在「接入代理配置」卡片底部常驻展示当前目录选择器状态与目标 cordis.patch.yml 路径,支持随时一键开启或点击 「🔄 恢复 PC 原生目录选择器」 撤回配置;中英文国际化(i18n.js)及中英文 README(Q12)同步更新。 · 测试覆盖:新增 test/directory-picker.test.js,覆盖空配置检测、多次调用严格幂等去重、已有 directory-picker 就地更新、单边 browse 缺失补齐、共用 insert 块无损还原、运行时 Loader 状态探测及 API 路由闭环。
v1.10.12026-10-24
· [1.10.1] - 2026-10-24 · Fixed (解决 Issue #9 · 适配 DSH 0.2.1 路由契约并修复对外端口来源) · DSH 0.2.1+ 路由契约兼容:18 条 API 路由显式声明 kind: 'exact',不再因缺少 kind 全部进入 prefixes 表。修复插件重复激活、组件级关闭/启用或热重载时抛出 webserver: duplicate undefined route "/api/remote-mobile/public-key",导致条目启用失败的问题。 · 路由生命周期管理:新增 src/routes/register.ts,通过 ctx.effect 收集 WebServer.register() 返回的 disposer,在插件 dispose/热重载时撤销全部路由;注册中途失败会回滚已注册路由。没有 ctx.effect 的旧宿主降级使用 ctx.on('dispose') 回收。 · 消除 prefix 误匹配:/api/remote-mobile/public-key/foo 等未知子路径不再命中父级 handler,路由恢复严格的 exact 语义。 · 对外端口来源统一:新增 src/client/utils/access.js,二维码、复制链接和网络地址卡片统一使用 status.proxy.activePort;移除 window.location.port 与硬编码 3080 兜底,修复在 3080 / DSH Desktop 50905 页面复制或扫码得到不可访问端口的问题。 · 代理未就绪处理:activePort 缺失或非法时不再生成错误链接,改为提示“安全接入代理未就绪”并禁用相关复制/二维码操作;中英文文案同步。 · 测试覆盖: - 新增 test/routes-contract.test.js,覆盖 18 条路由 exact 契约、dispose 撤销、同一 WebServer 二次注册、注册失败回滚及旧宿主降级; - 新增 test/access-port.test.js,覆盖端口解析与直达链接构造; - test/client-bundle.test.js 增加打包产物中禁止 window.location.port 的防回归断言。 · 升级提示:DSH 0.2.1+ 的 WebServer 不按 method 字段分派,本次保留该字段用于旧版兼容;升级到 1.10.1 后建议重启一次 DSH,以清理由 1.10.0 旧代码遗留在当前进程中的路由。
数据来自仓库的 GitHub Release,可能与插件实际版本不一致。

README

由 DeepSeek 最新模型翻译生成
dsh-remote-mobile DeepSeek Harness (DSH) 远程与移动端安全接入代理插件 无需修改 DSH 源码(运行时插件接管) · 局域网 / Tailscale 安全接入 · 扫码配对 · 移动端样式适配 · 工作区能力复用 · 实时状态推送 📱 桌面级移动工作台:手机上也能新建 / 切换工作区、执行终端命令 🛡️ 多重安全认证:扫码配对 · 长期密码 · 免密直连 + RSA 加密 + 防爆破封禁 🎨 移动端样式片段:内置三段预设,不满意可自定义 CSS English Documentation · 简体中文 这是什么 • 核心优势 • 快速开始 • 接入模式 • 网络配置 • 界面预览 • 功能特性 • 安装与更新 • 高级配置 • 本地文件存储 • 常见问题 📖 这是什么 dsh-remote-mobile 是专为 DeepSeek Harness (DSH) 深度定制的远程与移动端安全治理插件。 出于安全考虑,DSH 核心服务默认只监听本地回环地址(127.0.0.1),手机、平板或其他电脑无法从外部访问 Web 控制台。
展开完整文档(README 剩余 1 节)
dsh-remote-mobile

DeepSeek Harness (DSH) 远程与移动端安全接入代理插件

无需修改 DSH 源码(运行时插件接管) · 局域网 / Tailscale 安全接入 · 扫码配对 · 移动端样式适配 · 工作区能力复用 · 实时状态推送

📱 桌面级移动工作台:手机上也能新建 / 切换工作区、执行终端命令 🛡️ 多重安全认证:扫码配对 · 长期密码 · 免密直连 + RSA 加密 + 防爆破封禁 🎨 移动端样式片段:内置三段预设,不满意可自定义 CSS

English Documentation · 简体中文

这是什么 • 核心优势 • 快速开始 • 接入模式 • 网络配置 • 界面预览 • 功能特性 • 安装与更新 • 高级配置 • 本地文件存储 • 常见问题

📖 这是什么

dsh-remote-mobile 是专为 DeepSeek Harness (DSH) 深度定制的远程与移动端安全治理插件。

出于安全考虑,DSH 核心服务默认只监听本地回环地址(127.0.0.1),手机、平板或其他电脑无法从外部访问 Web 控制台。本项目通过访问控制中间件与请求隔离技术,安全地开放了 Tailscale 虚拟私网 以及 本地局域网 (Wi-Fi/LAN) 访问能力,并提供登录凭证 RSA 加密、扫码配对、长期密码认证与防暴力破解审计体系。插件本身不提供全链路 TLS;完整传输加密需依赖 Tailscale 或外层 HTTPS。

手机端访问时,插件还针对 DSH 界面做了移动端样式适配:侧边栏抽屉折叠与可拖拽展开把手、新版官方三栏 Grid 布局与右侧边栏兼容、设置弹窗居中微缩、对话正文高密度排版等,让手机上的使用体验更贴近桌面端。

⚡ 核心优势

* 🚀 突破网络壁垒,复用桌面端能力:用于在局域网 / Tailscale 下访问 DSH Web。在移动端或外部设备访问时,支持新建工作区、切换工作区、执行终端命令等常用桌面端操作。
* 📱 手机端专属适配与同源复用:直接复用 DSH 官方 Web 前端(手机与桌面是同一套界面)与各类生态插件(如侧边栏、任务看板、终端面板等),无需单独维护手机端后台。插件已针对移动端做好了基础响应式适配(如侧边栏抽屉折叠、设置弹窗居中、对话正文字号与行距精细调小以提升一屏信息量),并内置样式片段覆写功能,支持开箱即用或按需注入自定义 CSS 样式小插件。
* 🛡️ 安全门禁与登录凭证加密:登录凭证使用 RSA 非对称公钥加密,scrypt 加盐慢哈希密码落盘,连续输错自动熔断锁定 IP,拦截公网/局域网未经授权的访问。
* 📲 扫码快速配对:自动识别并生成 Tailscale CGNAT 与局域网专属访问链接与二维码,手机扫码打开已预填配对码的登录页,确认后完成授权。
* 🔄 SSE 实时状态推送:设备接入、重连、注销、IP 锁定等事件通过 SSE 推送;设置页仍保留低频轮询作为兜底。
* 🤝 插件通用共存保护:与其他远程/Web 接入类插件并存时自动让出共享服务,尽量避免共存冲突导致启动失败,设置页提供冲突警示横幅与一键复制的「诊断与修复报告」。

🚀 快速开始

1. 一键安装插件

在终端执行 DSH 官方插件安装指令(推荐):

dsh plugin --profile web add dsh-remote-mobile

[!IMPORTANT]
生态插件安装建议:不建议安装 @linxin666/dsh-web-all 全家桶,更推荐按需安装单体功能插件。全家桶把多个前端模块合并打包,可能引发移动端悬浮入口重复与手势交互冲突;如需任务看板、宠物等生态功能,建议直接安装对应的单体包(原因、推荐命令与共存说明见 常见问题 Q6)。

2. 前置配置(插件 1.10.0+ 无需 webserver 配置)

本插件 1.10.0+ 不需要修改 webserver 或 cordis.patch.yml:

- DSH 0.2.1+ 保持默认 127.0.0.1:3080;
- 本插件自动启动 3081 安全接入代理;
- 手机扫码或使用长期密码即可接入。

本节适用于本插件 1.10.0+。

安全权衡说明:安全接入代理默认监听 0.0.0.0:3081(暂不含 TLS)。若在公共 Wi-Fi 或未经保护的网络下使用,建议配合 Tailscale(WireGuard 隧道加密)或外层 HTTPS。

插件 1.9.0 之前的配置迁移(DSH 0.2.1+)

本插件 1.9.0 之前的配置,或曾手动配置 webserver.host: 0.0.0.0 的配置,需要按以下步骤迁移。本插件 1.10.0+ 可直接跳过本节。

DSH 0.2.1+ 会拒绝 0.0.0.0,并在插件加载前直接启动失败。请删除或注释整个 webserver 配置块:

❌ DSH 0.2.1+ 下会启动失败
- id: webserver
host: '0.0.0.0'

✅ 保持默认回环监听无需配置 webserver;DSH 使用 127.0.0.1:3080, dsh-remote-mobile 自动使用 3081 安全接入代理。

不确定当前配置时:

grep -n -A5 'id: webserver' ~/.dsh/profiles/web/cordis.patch.yml

若看到 host: '0.0.0.0',删除对应配置块;没有则无需处理。

DSH 0.1.7 ~ 0.2.0 可以保留 0.0.0.0,插件会回退 direct 模式;若通过自定义域名或公网 IP 访问,请配置 trustedHosts。

如果你安装了其他可能产生冲突的插件,推荐在 ~/.dsh/profiles/web/cordis.patch.yml(Windows 对应 %USERPROFILE%\.dsh\profiles\web\cordis.patch.yml)中禁用冲突项:

(推荐)只保留一款远程/Web 接入类插件,避免功能重复与入口混乱本插件与其他远程接入类插件共存时也能正常启动:检测到配对共享服务被占用会自动让出,并在设置页顶部展示警示横幅说明详情。
示例:禁用 @linxin666/dsh-web-ui-all 内置的远程插件
- id: web-ui-remote-web-ui
disabled: true

💡 说明:插件自身注册已由 DSH Bundle 体系全自动处理,无需在 cordis.patch.yml 中额外添加 id: remote-mobile。Windows 用户可按 Win + R 键输入 %USERPROFILE%\.dsh\profiles\web 快速直达配置目录。

[!WARNING]
不建议安装聚合全家桶(@linxin666/dsh-web-all):它会把多个子插件的前端逻辑合并打包到同一个 client.js 中,加载时自执行,可能与本插件产生移动端入口重复与手势冲突。推荐按需安装所需单体插件(如任务看板、宠物等)。完整原因、推荐命令与共存说明见 常见问题 Q6。

3. 启动并使用

dsh web --no-open

启动后,在电脑浏览器打开 DSH Web 控制台,进入 设置 ⚙️ -> 远程与移动端,使用手机微信或系统相机扫描二维码进入配对/登录页,完成授权后即可使用。

设置面板按 「接入 / 设备与安全 / 样式覆写 / 本地数据」 四个页签分组显示,避免长页面;页签选择会被记住,切换不重置配对码等状态。

🧭 版本与接入模式

本插件 1.10.0+ 默认使用 proxy,不需要配置 webserver:

| 场景 | DSH 核心监听 | 生效模式 | 手机访问 |
| DSH 0.2.1+ + 插件 1.10.0+ | 127.0.0.1:3080 | proxy | 3081 |
| DSH 0.1.7 ~ 0.2.0 + 0.0.0.0 配置 | 0.0.0.0:3080 | direct(自动回退) | 3080 |
| DSH 0.2.1+ + 具体网卡 IP | 192.x.x.x:3080 | direct | 3080 |
| 同机反向代理 | 127.0.0.1:3080 + 显式 direct | direct | 反代入口 |

- proxy:推荐;核心保持回环,插件在 3081 提供认证与转发。
- direct:不启动代理;要求核心本身对外可达,主要用于 DSH 0.1.7 ~ 0.2.0 的 0.0.0.0 配置或高级部署。
- direct + 回环核心:DSH 只监听 127.0.0.1,手机无法直接访问;只有再通过同机 Nginx / Caddy / Tailscale Serve 等反向代理转发到 3080 时才有意义,否则只能本机使用。

访问链路与认证判定

判定不能只看 TCP 来源 IP(socket 地址):Host 与 X-Forwarded- 也会改变结果。socket IP 是服务器看到的 TCP 对端地址,不是 Host,也不是 X-Forwarded-For。

| 访问方式 | 链路 | 判定 | 插件登录 |
| A 本机直连 | PC → 127.0.0.1:3081 | 本机回环(loopback) | 不需要 |
| B 手机直连 | 手机 → 192.x.x.x:3081 / 100.x.x.x:3081 | 外部直连(external) | 需要(可开 allowLan/allowTailscale 免密) |
| C 同机反向代理 | 手机 → Nginx → 127.0.0.1:3081 | 反向代理(reverse-proxy) | 需要(不享受免密) |

这里的「反向代理」指用户侧的 Nginx / Caddy / Tailscale Serve,不是插件的 3081 安全接入代理。

127.0.0.1 仅在本机直连、且无外部 Host/Forwarded 时跳过登录,当前没有开关;跳过的只是插件登录,Host/Origin/CSRF 仍生效。LAN/Tailscale 默认认证,开启 allowLan/allowTailscale 后对应网络才免密;反向代理流量不享受这些免密。

展开:请求头、Nginx 配置与常见错误

请求头示例(A/B/C 对应上表):

- A:Host: 127.0.0.1:3081
- B:Host: 192.x.x.x:3081 或 Host: 100.x.x.x:3081
- C:Host: dsh.example.com,并携带 X-Forwarded-For: 100.x.x.x / X-Forwarded-Host / X-Forwarded-Proto: https

C 场景即使 Nginx 从 127.0.0.1 连入,仍判定为反向代理,需要认证。

location / {
proxy_pass http://127.0.0.1:3081;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Host $host;
proxy_set_header X-Forwarded-Proto $scheme;
}

自定义域名需加入 trustedHosts:

{
"trustedHosts": ["dsh.example.com"]
}

常见错误:Nginx 把 Host 改写成 127.0.0.1 并删除全部 X-Forwarded-,插件会误判为 A 并跳过登录。Tailscale Serve 也是同机反代,同样要保留这些头。

🖼️ 界面预览

PC 端 DSH 插件控制面板

| 网络接入与扫码配对 | 设备与安全 |
| PC 设置面板 - 网络接入与扫码配对 | PC 设置面板 - 设备与安全 |
| 样式片段覆写 | 本地数据存储 |
| PC 设置面板 - 样式片段覆写 | PC 设置面板 - 本地数据存储 |

手机端实机演示(同源免登与工作区能力)

| 登录 | 对话列表字号缩放 | 侧边栏移动端样式 | 官方轨迹查看 |
| 登录 | 对话列表字号缩放 | 侧边栏移动端样式 | 官方轨迹查看 |

🌟 功能特性

1. 网络接入支持

- Tailscale 虚拟私网:自动识别本机 Tailscale IP(100.64.0.0/10 CGNAT 网段),生成专属访问二维码,支持开启免密直连(底层传输加密由 Tailscale WireGuard 隧道提供)。
- 本地局域网 (LAN / Wi-Fi):自动识别 RFC 1918 私有 IP(如 192.168.x.x、10.x.x.x、172.16-31.x.x),提供局域网专属二维码与直达链接预览,附带醒目的高危风险提示。
- 二维码快速配对:支持手机相机或微信扫码直达授权页面。

2. 多重认证机制

- 动态 6 位配对码:生成 6 位短期配对码(5 分钟有效,一次性使用),手机端扫码输入后换取 365 天有效期的安全认证 Cookie。
- 长期访问密码:支持设置自定义长期访问密码(长度需至少 6 位且含字母与数字),便于多设备长期固定登录。
- 免密直连模式:可针对 Tailscale 或局域网环境单独切换免密直连。关闭免密直连后会自动清理临时设备凭证并重置状态。
- 设备会话管理:实时查看已连接设备的类型、操作系统、浏览器、来源 IP 及最近活跃时间,支持单设备注销与一键注销全部设备。

3. 安全防护

- 应用层登录凭证 RSA 加密:Web 登录页在客户端完成 RSA 加密,敏感密码与配对码加密后才提交到验证接口;加密失败时会停止提交并提示,不会回退明文。
- HTTPS 等安全上下文:优先使用 Web Crypto 原生 RSA-OAEP-SHA256;
- DSH 默认的 HTTP 局域网 / Tailscale 访问(浏览器非 Secure Context;Tailscale 隧道本身仍有 WireGuard 加密):回退至纯 JS 垫片,并使用 crypto.getRandomValues() 做密码学安全随机数填充。

插件不自带 TLS;登录后的页面、API 与 WebSocket 流量需要依靠 Tailscale 或外层 HTTPS 才能获得完整传输加密。
- scrypt 慢哈希存储:服务端采用 scrypt 加盐慢哈希算法(scrypt:${salt}:${hash})对密码进行落盘存储,比对过程采用 crypto.timingSafeEqual 恒定时间比较以防范时序侧信道攻击。
- 防暴力破解与限频保护:
- 连续输错凭证达到阈值(默认 5 次)自动锁定该 IP 15 分钟,拦截后续验证请求并返回 HTTP 429;
- 滑动窗口限频(默认 60 次/分钟),防范高频恶意刷量探测;
- 访问审计与锁定状态持久化落盘,服务重启后自动恢复;
- 支持管理员在管理面板中一键解锁指定 IP。
- 智能静态资源放行机制:安全门禁内置前端静态扩展名识别(放行 .js、.css、.png、.svg、.woff2 等 20 种合法资源),拦截无扩展名或动态管理 API 请求(如 /plugins/xxx/admin),在兼容第三方生态插件前端资源的同时降低后台动态接口暴露面,无需用户在设置界面手动维护白名单。(DSH 核心静态目录 /assets/ 属官方资源,单独无条件放行。)
- 高可靠原子持久化与防抖:采用毫秒级写盘防抖节流保护磁盘 I/O(会话数据 500ms、样式片段 300ms),配合 beforeExit 进程退出 Flush,降低异常中断导致数据丢失或配置损坏的风险;settings.json 与 style-snippets.json 采用临时文件原子替换(renameSync)。
- 真实 IP 安全提取:默认仅信任底层 Socket 真实连接地址;当请求被识别为受信反向代理(Host / X-Forwarded-)时,才采用 X-Forwarded-For 首个合法 IP,兼顾真实来源与防伪造。
- 跨站写请求防御:所有携带浏览器同源信号的写请求都会校验(Origin / Sec-Fetch-Site),恶意网页驱使浏览器向 127.0.0.1 发起的跨站写请求会被直接拒绝;curl / 本机脚本等无浏览器信号的客户端不受此限制。注意:该策略也会拦截第三方插件的跨域 webhook / OAuth callback,以及发送 Origin: null 的旧 WebView;使用此类集成时请先做兼容性验证。
- 上下文化与反代边界:同源/本地变体 Host、Origin 成对规范化;恶意跨站 Origin 原样保留供下游 CSRF 检查。同机 127 反代通过外部 Host、LAN/Tailscale 反代通过 X-Forwarded- 识别为代理流量,不再被误判为免密客户端,必须通过插件 Token 鉴权;受信代理通过内部 Secret 例外。插件自身为 loopback 请求注入的 127 Forwarded 头会被识别为内部头,不会误伤本机 3080 根路径的 connection 鉴权。SSE 事件流仅限同源连接,不开放跨域读取。

4. 实时状态同步与国际化

- SSE 实时事件推送:基于 Server-Sent Events 实现新设备接入、设备重连、会话撤销及安全告警的实时通知,内置连接双向关闭监听与幂等清理。
- 全界面中英文双语自适应:根据 DSH 全局语言偏好与浏览器环境,动态自适应中英文面板、提示及手机端界面。
- 非 HTTPS 兼容补丁:自动注入 crypto.randomUUID 与 navigator.clipboard Polyfill,解决移动端浏览器在 HTTP 非安全上下文下缺少原生 API 的报错(clipboard polyfill 在非安全上下文下提供复制/样式复制能力)。

5. 移动端样式片段(样式小插件)

DSH Web 界面在手机上仍有不少沿袭桌面端的样式问题,插件内置「移动端样式片段」模块,把移动端适配拆分为可独立启停的 CSS 片段:

- 按界面区域划分的三段内置预设(默认移动端启用、PC 关闭):
- preset-sidebar 侧边栏抽屉导航:折叠时侧边栏 0 宽度占用、可拖拽悬浮把手、自适应抽屉宽度、44px 触控友好区;
- preset-settings 设置面板适配:上下堆叠自适应、顶部导航横滑、内容纵向自然滚览;
- preset-main 对话正文高密度排版:小字号 12.5px + 紧凑行距 + 收紧边距,每行展示更多内容;基于稳定 HTML 元素与 localName 后缀,不做布局缩放,无右侧留白;
- PC / 移动端分别启停(按视口宽度判定,与设备无关):每段预设和每个自定义片段都有独立的「🖥️ PC」「📱 移动端」两个开关;「移动端」= 窄视口(≤900px)生效——PC 浏览器拉小窗口也会生效,「PC」= 宽视口(>900px)生效,两端都开 = 全宽度生效;
- 关于 html[data-dsh-mobile] 选择器:该标记仅在移动端 UA 请求时写入。若自定义 CSS 以 html[data-dsh-mobile] 打头,则只在手机等移动端 UA 下命中;希望「窄屏桌面窗口也生效」时请直接写选择器(无需 html[data-dsh-mobile] 前缀),注入层会自动包裹媒体查询。
- 用户自定义(样式小插件):在 设置 ⚙️ → 远程与移动端 → 🎨 移动端样式片段 中粘贴自己的 CSS 即可新增片段,支持编辑/启停/删除/一键复制(每个片段「查看 CSS」旁都有「📋 复制样式」按钮),持久化于 ~/.dsh/remote-mobile/style-snippets.json,保存后下一次页面加载即生效,无需重启;
- 按 UA 打标记 + 按宽度生效:样式按视口宽度档生效(见上),与设备 UA 无关;移动端 UA 请求额外给 打上 data-dsh-mobile="1" 标记作为作用域钩子(以其作为选择器时仅移动端 UA 命中,详见上条),并注入可拖拽的展开把手脚本(全端注入,运行时仅在侧边栏折叠时生效)。

6. 插件通用共存保护

- 共存冲突自动让出:remoteWebUiPairing 配对共享服务被其他远程/Web 接入类插件占用时,本插件自动让出(延迟裁决:等待激活窗口结束后检测服务名归属),避免服务重名导致的整树回滚崩溃——从插件市场安装后通常无需手动禁用对方;
- 冲突状态可见化:检测到共存时,设置页顶部展示可关闭的警示横幅(中英文),说明让出行为与「只保留其一」的建议;处理完成后刷新页面即不再出现;
- 一键诊断报告:横幅内 「📋 复制诊断与修复信息」 按钮可复制完整诊断报告——运行时自动识别占用方插件包名与 loader entry id,报告含精确到行的修复配置与命令(禁用对方 / 卸载本插件二选一),粘贴给 AI 助手即可按步骤处理。

📦 安装与更新

展开查看全部 4 种安装方式

方式 1:通过 DSH 命令行一键安装(最推荐)

bash
dsh plugin --profile web add dsh-remote-mobile

方式 2:通过 Web 设置页「插件管理」图形化安装
1. 在浏览器打开 DSH Web 控制台;
2. 点击左下角 设置 ⚙️ -> 插件;
3. 切换到顶部的 「插件管理」 Tab;
4. 在输入框中输入 npm 包名 dsh-remote-mobile,点击 「安装」;
5. 安装完成后重启 DSH 即可生效。

方式 3:在 Profile 目录中通过包管理器安装bash

1. 进入 DSH Web Profile 目录

cd ~/.dsh/profiles/web

2. 通过 pnpm 安装

pnpm add dsh-remote-mobile

方式 4:本地源码开发与调试(软链接即时生效) bash

1. 克隆代码至本地

git clone https://github.com/IceApriler/dsh-remote-mobile.git
cd dsh-remote-mobile

2. 安装依赖并编译打包

npm install
npm run build

建立软链接到 DSH 运行环境(开发修改后 npm run build 即时生效):

* macOS / Linux: bash
rm -rf ~/.dsh/profiles/web/node_modules/dsh-remote-mobile
ln -s $(pwd) ~/.dsh/profiles/web/node_modules/dsh-remote-mobile

* Windows (PowerShell): powershell Remove-Item -Recurse -Force "$HOME\.dsh\profiles\web\node_modules\dsh-remote-mobile" New-Item -ItemType Junction -Path "$HOME\.dsh\profiles\web\node_modules\dsh-remote-mobile" -Target (Get-Location)

* Windows (CMD): cmd rmdir /s /q %USERPROFILE%\.dsh\profiles\web\node_modules\dsh-remote-mobile mklink /J %USERPROFILE%\.dsh\profiles\web\node_modules\dsh-remote-mobile %CD%

🔄 更新插件

在终端执行更新指令,更新后重启 DSH 即可生效: bash

方式 1:通过 DSH 命令行一键更新(推荐)

dsh plugin --profile web update dsh-remote-mobile

方式 2:通过 Profile 目录包管理器更新

cd ~/.dsh/profiles/web && pnpm update dsh-remote-mobile

💡 提示:也可以在 Web 控制台 设置 ⚙️ -> 插件 -> 插件管理 中点击对应插件的更新按钮,或重新执行 dsh plugin --profile web add dsh-remote-mobile@latest。

🗑️ 卸载插件 bash
dsh plugin --profile web remove dsh-remote-mobile

⚙️ 高级配置

配置持久化于插件自有文件 ~/.dsh/remote-mobile/settings.json。Web 设置页中的修改位置如下:

| 配置项 | 位置 | 生效方式 |
| mode / proxyPort / proxyHost / trustedHosts | 接入 → 接入代理配置 | 重启 DSH |
| Tailscale / 局域网免密开关 | 接入 → 网络接入地址与免密直连管理 | 即时 |
| 长期访问密码 | 接入 → 设置长期访问密码(可选) | 即时 |
| 限频 / 失败阈值 / 锁定时间 | 设备与安全 → 全局高级安全参数配置 | 即时 |
| 样式片段 | 样式覆写 | 下次页面加载 |

也可以直接编辑 settings.json。修改接入模式或监听参数后需重启 DSH: jsonc { "allowTailscale": false, // boolean,默认 false:是否允许 Tailscale 虚拟私网免密访问 "allowLan": false, // boolean,默认 false:是否允许局域网免密访问(高危警示) "secretHash": "", // string,默认空:长期访问密码的 scrypt 加盐哈希值 "maxVisitsPerMinute": 60, // number,默认 60:单 IP 每分钟最大访问登录页次数 "maxFailedAttempts": 5, // number,默认 5:触发封禁的连续认证失败最大次数 "lockDurationMs": 900000, // number,默认 900000 (15分钟):IP 锁定持续时间(毫秒) "proxyPort": 3081, // number,默认 3081:安全接入代理端口 "proxyHost": "0.0.0.0", // string,默认 0.0.0.0:安全接入代理监听地址 "mode": "proxy", // proxy | direct:proxy 默认(推荐)
"trustedHosts": [ // string[],默认 []:受信任域名白名单(如 Tailscale MagicDNS 或自定义反代域名)
"my-device.tailnet.ts.net"
]
}

术语说明:本文中的“安全接入代理(Proxy)”指插件在 3081 提供的反向代理入口,负责认证、Host/Origin 校验与请求转发,不是网络默认网关。
关于 trustedHosts 受信任域名:若你通过 Tailscale MagicDNS 域名(如 xxx.tailnet.ts.net)或外部反向代理域名访问 DSH,请在该数组中添加裸域名。安全接入代理与底座会自动将其纳入受信任白名单,防止被代理 Host 检查或第三方插件安全围栏拦截(报 400 或 403)。该白名单同样适用于 direct 模式:通过自定义域名、公网 IP 或非本机网卡地址访问时,必须把实际 Host 加入 trustedHosts;通过本机网卡 IP / 回环访问会自动识别。

proxyPort / proxyHost / mode 说明:通常保持默认即可。mode: 'proxy' 是默认值,要求底座监听在回环地址;如果检测到核心已对外监听(0.0.0.0 或具体网卡 IP),会自动回退 direct 并输出警告。mode: 'direct' 不启动安全接入代理,要求核心本身可被外部访问;若核心是回环地址,direct 仅适合同机反向代理,外部手机无法直连。修改这些高级配置后需重启 DSH。

反向代理 HTTPS 注意:若通过 Nginx/Caddy/Tailscale Serve 等反代以 HTTPS 访问,请确保:
1. 保留外部 Host(Nginx 使用 proxy_set_header Host $host,不要把 Host 改写成 127.0.0.1);
2. 透传 X-Forwarded-For、X-Forwarded-Host、X-Forwarded-Proto: https(Caddy/Tailscale 通常自动处理)。

插件会用这些头做协议/来源同源校验与 302 重定向;当反代携带 Forwarded 头并被识别为代理时,即使位于 LAN/Tailscale 网段也会禁用 allowLan/allowTailscale 免密,改为正常 Token 鉴权。若反代把 Host 改写为 127.0.0.1 且不发送任何 X-Forwarded- 头,插件无法区分「本机浏览器」和「同机反代」;LAN/Tailscale 反代若缺少这些头,也可能被误判为真实局域网客户端,请务必按上述配置修正。

关于配置存储:DSH 自 0.1.7 起(最早见于 v0.1.7-alpha.1 的 release notes,0.2.x 延续)官方已弃用 ~/.dsh/settings.yaml 作为实时配置源——该文件会在启动时被一次性迁移进当前 profile 的 cordis.patch.yml,并改名为 settings.yaml.imported(详见 Q8)。因此本插件不再写入 settings.yaml,而是持久化到上述自有 settings.json;首次启动时会自动从旧 settings.yaml / settings.yaml.imported 迁移一次(把旧长期密码、限频等配置救回)。

🎨 移动端样式片段(可选)

自定义样式片段(样式小插件)与启停状态持久化于 ~/.dsh/remote-mobile/style-snippets.json,可在设置面板图形化管理,也可直接编辑该文件: json { "version": 2,
"presetStates": {
"preset-sidebar": { "pc": false, "mobile": true },
"preset-settings": { "pc": false, "mobile": true },
"preset-main": { "pc": false, "mobile": true }
},
"custom": [
{
"id": "custom-xxx",
"name": "我的样式小插件", "css": "html[data-dsh-mobile] .我的选择器 { ... }", "pcEnabled": false, "mobileEnabled": true } ],
"customOrder": ["custom-xxx"]
}

所有启用片段会拼接为单个 注入主页面与 /auth 登录页,生效与否由视口宽度决定:mobileEnabled 的片段包在 @media (max-width: 900px)(≤900px 生效,PC 拉小窗口同样生效),pcEnabled 的片段包在 @media (min-width: 901px)(宽视口生效),两端都开则全宽度生效(v1 旧格式自动迁移,presetStates 按三个预设分组齐全落盘)。注意:PC 开启侧边栏抽屉预设会改变桌面布局,如不想用可关闭该预设的 PC 开关。

📂 本地文件存储位置

💡 路径提示:在 macOS / Linux 下根目录为 ~/.dsh/;在 Windows 下对应为 C:\Users\<你的用户名>\.dsh\(可在文件资源管理器地址栏直接输入 %USERPROFILE%\.dsh 直达)。

| 文件路径 | 说明 | 安全级别 |
| ~/.dsh/remote-mobile/settings.json | 插件配置:免密开关、长期密码哈希、限频/锁定策略、接入模式与代理监听参数 | 本地落盘,权限 0o600(仅当前用户可读写) |
| ~/.dsh/remote-mobile/devices.json | 已授权设备会话、IP 访问计数与安全审计数据(内含长效 Token,权限 0o600) | 本地落盘,仅当前用户可读写 |
| ~/.dsh/remote-mobile/rsa-keys.json | 服务端 RSA 密钥对文件(含公钥与私钥) | 本地落盘,权限 0o600(仅当前用户可读写) |
| ~/.dsh/remote-mobile/style-snippets.json | 移动端样式片段(内置预设启停状态 + 用户自定义 CSS 小插件) | 本地落盘持久化 |

0o600 是 POSIX 权限;Windows 使用用户目录 ACL,不按 POSIX 权限位表示。

❓ 常见问题 (FAQ)

Q0:启动报错 listen EADDRINUSE: address already in use 0.0.0.0:3080 / :3081?

答:这是 3080(DSH 核心)或 3081(插件安全接入代理)端口被其他进程占用(绝大多数是上一个 dsh web 实例尚未完全退出,例如桌面快捷方式拉起的隐藏实例、或重启时新旧进程交叠),与安装了哪个插件无关。排查步骤:

1. 查找占用端口的进程: macOS / Linux:lsof -nP -iTCP:3080 / lsof -nP -iTCP:3081
* Windows:netstat -ano | findstr 3080 / netstat -ano | findstr 3081(最后一列为 PID)
2. 结束旧实例:
* macOS / Linux:kill <PID>
* Windows:taskkill /F /PID <PID>(或在任务管理器中结束对应 Node 进程)
3. 重新启动 dsh web 即可。
4. 注意:桌面快捷方式(dsh-desktop-launcher 生成的后台实例)与终端手动启动的实例不能同时运行。若只是 3081 被占用,插件默认会自动递增尝试下一个端口(最多 10 个),以设置页二维码/终端 Banner 显示的实际端口为准;3080 被占用通常需要先结束旧 DSH 实例。

另:若日志显示的是 service "remoteWebUiPairing" has been registered 类错误,请升级本插件至最新版本——新版已内置通用共存保护,与其他远程接入类插件并存时可正常启动并自动让出服务,并在设置面板顶部提示冲突详情。

Q1:手机扫码后提示「连接被拒绝」或无法打开页面?

答:请按以下步骤逐一排查:
1. 配置检查:DSH 0.2.1+ 不要在 cordis.patch.yml 中配置 host: '0.0.0.0'(会启动失败);保持 DSH 默认的 127.0.0.1:3080,插件会自动启动 3081 安全接入代理。DSH 0.1.7 ~ 0.2.0 可使用 0.0.0.0;显式使用 direct 模式时需绑定具体网卡 IP。修改配置后必须重启 DSH。
2. 同一局域网:局域网访问时,确保手机与电脑连接在同一个 Wi-Fi / 路由器下,且未开启手机端的独立代理/VPN(避免流量绕过局域网)。
3. Windows 防火墙排查(Windows 用户常见原因):
* 启动 DSH 时如弹出「Windows Defender 防火墙」警报,请务必勾选 「专用网络」 和 「公用网络」 并允许访问;
* 若错过弹窗或依然无法连通,可使用管理员权限打开 PowerShell 放行代理端口(默认 3081;若使用 direct 模式则放行核心端口 3080): powershell New-NetFirewallRule -DisplayName "DSH Remote Proxy 3081" -Direction Inbound -LocalPort 3081 -Protocol TCP -Action Allow
* 检查 Wi-Fi 网络类型:在 Windows「设置 -> 网络和 Internet -> Wi-Fi」中,确保当前 Wi-Fi 网络配置文件类型已设为 「专用网络」(若为「公用网络」,Windows 防火墙会默认拦截局域网入站请求)。
4. 多网卡环境(WSL / 虚拟机):Windows 如安装了 WSL2 或 Hyper-V,可能会生成虚拟网卡 IP(如 172.x.x.x)。请在插件设置面板中确认使用的是物理无线网卡(WLAN / Wi-Fi)对应的局域网 IP 或 Tailscale 二维码;若代理端口曾自动递增,以设置页/终端 Banner 显示的实际端口为准。

Q2:Tailscale 免密直连和局域网免密有什么区别?

答:
* Tailscale 免密:安全性较高。Tailscale 基于 WireGuard 传输加密;默认只有同一 tailnet 或被 ACL/共享授权的设备可连通。
* 局域网免密:存在安全隐患。开启后,任何能访问网关端口的局域网设备(包括访客或蹭网设备)都可能直接控制你的工作区;访客网络隔离或防火墙可能阻断,但不要把它当作安全边界,因此非完全可信环境切勿开启。

Q3:手机端切换或新建工作区能正常使用吗?

答:支持。本插件通过请求隔离技术,使手机端与 PC 桌面端拥有一致的工作区管理与执行能力。若点“选择文件夹”时弹窗出现在 PC 而不是手机,这是 DSH 目录选择器的 auto 判定问题,见 Q12。

Q4:手机端样式还是不满意,想完全自己定制?

答:打开 设置 ⚙️ → 远程与移动端 → 🎨 移动端样式片段,先尝试启停三段内置预设(侧边栏 / 设置面板 / 正文,每段都有独立的 PC 与移动端开关);还不够就在「自定义片段」里粘贴自己的 CSS(例如 .我的选择器 { ... })。保存后手机端刷新页面立即可见,改动全部落在 ~/.dsh/remote-mobile/style-snippets.json,升级插件不会丢失。
小提示:若选择器写成 html[data-dsh-mobile] .xxx,则只在移动端 UA 下生效,窄屏桌面窗口不会命中;希望两端窄屏都生效,请直接写选择器。

Q5:设置页顶部出现黄色横幅「⚠️ 检测到其他远程接入插件」是什么意思?

答:说明当前有另一款远程/Web 接入类插件与本插件同时启用,且对方先占用了配对共享服务。为避免启动冲突,本插件已自动让出该服务(自身的安全门禁与远程接入不受影响),但两套远程功能并存可能出现重复入口。建议只保留其一:
1. 点击横幅中的 「📋 复制诊断与修复信息」,把报告粘贴给 AI 助手,让它按步骤处理(报告已自动识别占用方包名与条目 id,并给出精确的修复配置);
2. 想保留本插件时:按报告指引,在 ~/.dsh/profiles/web/cordis.patch.yml 末尾追加对应的 disabled: true 两行,然后重启 DSH;
3. 横幅可点击 ✕ 临时关闭;冲突解除后刷新页面,横幅就不再出现。

Q6: 为什么建议按需安装单体功能插件,而不建议安装聚合全家桶(如 @linxin666/dsh-web-all)?

答:主要是出于避免功能冲突与环境轻量解耦的考量:
1. 避免移动端入口重复与手势交互冲突:全家桶插件会把十几个独立功能插件的前端代码编译打包进同一个 client.js,它内置的移动端自适应逻辑在模块加载时会自执行。即使在 cordis.patch.yml 中配置了 web-ui-remote-web-ui: disabled: true,浏览器加载脚本后仍可能渲染出小鲸鱼悬浮按钮(#dshRemoteWhale),导致两个插件的移动端悬浮入口重叠,触控手势也互相冲突。
2. 轻量可控与独立升级:按需单独安装所需插件,有助于保持环境轻盈,也便于各个插件独立升级、回滚与维护,避免依赖绑定。推荐按需安装各自独立的单体包,例如: bash

需要哪个装哪个,职责清晰、互不干扰

dsh plugin --profile web add @linxin666/dsh-client-ui-task-board
dsh plugin --profile web add @linxin666/dsh-client-ui-web-ui-settings
dsh plugin --profile web add @linxin666/dsh-pet
dsh plugin --profile web add @linxin666/dsh-ssh
(注:自 v1.6.0 起,dsh-remote-mobile 已内置兼容防护——注入抑制脚本向 sessionStorage 写入 dsh-remote-force-desktop=1 并协同 CSS,避免 #dshRemoteWhale 被重复挂载;但小鲸鱼入口本身仍可能由对方渲染,入口重叠的根源在对方,故仍建议按需安装单体包)。

Q7: 部署在 NAS / 统一网关 / Nginx 反向代理下(如飞牛 fnOS /app/dsh/)有什么注意事项?

答:
1. 子路径与前缀自适应:自 v1.8.0 起,前端已原生适配 ,服务端重定向已支持 X-Forwarded-Prefix 请求头。经网关子路径代理访问时(如 http://<ip>:5666/app/dsh/),各项请求都会自动保持在当前子路径下:
* 设置面板与 SSE 长连接:基于 动态解析 API 路径,保存配置、生成配对码、事件推送均不会丢失 /app/dsh 前缀(1.8.0 之前此处会表现为「点击毫无反应」);
* 独立手机登录页与 302 重定向:登录校验、登录成功后的跳转,以及门禁 / /auth 的重定向都会保留当前子路径,不再跳回宿主根目录;
* 注意:服务端 302 重定向依赖反向代理透传 X-Forwarded-Prefix;若代理未透传该头,重定向将回退到安全默认路径 /auth。
2. 反向代理来源识别与鉴权:
* 插件默认仅信任底层物理 Socket;同机 127.0.0.1 反代携带外部 Host,或任意反代携带 X-Forwarded- 时,会被识别为代理流量,不再走 loopback / allowLan / allowTailscale 免密,必须使用插件 Token(动态配对码或长期密码)鉴权。
请确保反向代理保留外部 Host(不要改写为 127.0.0.1),并透传 X-Forwarded-For、X-Forwarded-Host、X-Forwarded-Proto: https;Caddy / Tailscale Serve 通常会自动处理,Nginx 需显式配置。若 Host 被改写为 127.0.0.1 且没有任何 Forwarded 头,插件无法区分「本机浏览器」与「同机反代」。
* 若希望外部统一域名访问时仍强制要求输入本插件的配对码/密码,请按上述方式配置反代,让插件能够识别真实来源并执行 Token 鉴权;不要依赖 NAS/宿主网关自身的登录来替代插件认证。
* 若你不需要 proxy 模式,也可以使用 direct 模式绑定具体网卡 IP;但 DSH 0.2.1+ 下不能使用 0.0.0.0 通配地址。

Q8: 升级 DSH 0.1.7+(含 0.2.x)后 ~/.dsh/settings.yaml 不见了 / 我的配置丢了?

答:这是 DSH 官方自 0.1.7 起的行为变更(最早见于 v0.1.7-alpha.1 的 release notes,0.2.x 延续),与本插件无关:

- DSH 自 0.1.7 起已不再把 ~/.dsh/settings.yaml 当作实时配置源。启动时它由官方 dsh-settings 的 importLegacyDocument() 一次性导入到当前 profile 的 ~/.dsh/profiles/<profile>/cordis.patch.yml,随后原文件被改名为 settings.yaml.imported(先改名、再导入;若某个 section 找不到对应 entry,则会跳过,仅保留在该文件中,且不会重试)。官方 release notes 原文:「设置改由当前 Profile 的插件配置保存,支持声明过的实时更新字段;旧 settings.yaml 仅尝试导入一次,自定义设置插件需适配。」
- 因此升级后 settings.yaml 消失、只剩 settings.yaml.imported 属正常现象;模型 provider、locale 等应改为在 profile 的 cordis.patch.yml 中查看/编辑。
- 本插件自 v1.9.0 起已改为把自身配置持久化到 ~/.dsh/remote-mobile/settings.json,并在首次启动时自动从旧 settings.yaml / settings.yaml.imported 迁移一次(把长期密码哈希、免密开关、限频阈值等救回),不再依赖 DSH 的 settings.yaml。若你曾发现「长期密码失效 / 限频重置」,升级到 v1.9.0 后会自动恢复。

Q9: DSH 0.2.1+ 启动失败:host "0.0.0.0" is an unspecified (wildcard) address?

答:这是 DSH 0.2.1+ 的官方安全限制,和插件版本无关。该错误发生在插件加载之前,插件安全接入代理此时还没有机会接管,因此必须手动处理:

1. 打开 ~/.dsh/profiles/web/cordis.patch.yml(Windows:%USERPROFILE%\.dsh\profiles\web\cordis.patch.yml);
2. 删除或注释掉之前为远程访问添加的 webserver.host: '0.0.0.0'(如整段 - id: webserver ... host: '0.0.0.0' 仅用于该配置,可整段删除);
3. 重启 dsh web --no-open。DSH 会恢复 127.0.0.1:3080,本插件自动启动 0.0.0.0:3081 安全接入代理,手机按设置页二维码访问即可。

1.10.0+ 在命中该通配地址错误退出后,会追加一条 [dsh-remote-mobile] 启动失败提示,包含具体配置文件路径和处理方法;其他启动错误不会触发。

Q10: 访问 http://127.0.0.1:3080/ 提示 dsh web authentication required; reopen the URL printed by dsh web,但 3081 正常?

答:这是 DSH 官方的 launch-token 鉴权,不是插件的 /auth 登录页。正常情况下,本插件会接管底座 connection 鉴权,让本机回环请求直接放行;如果 3080 根路径出现该提示,而以下两项正常:

- http://127.0.0.1:3080/api/remote-mobile/status 返回 200;
- http://127.0.0.1:3081/ 返回 200;

说明插件已加载、安全接入代理已生效,但当前进程里的 connection 鉴权仍未放行本机根路径。常见原因是 热重载 / 热更新没有重新加载插件代码,或运行的是旧构建。

处理步骤:

1. 确认安装的是 1.10.0+,然后完整停止并重新启动 dsh web(仅刷新浏览器页面不够);
2. 临时可先用 http://127.0.0.1:3081/(插件安全接入代理)访问,或打开 dsh web 启动时打印的带 ?token=... 的 URL 一次,让官方写入 cookie;
3. 若使用反向代理,请按 Q7 保留外部 Host 并透传 X-Forwarded-;
4. 如果重启后仍然出现,请附上 DSH 版本、插件版本与启动日志反馈。

1.10.0 已修复「插件自身为 loopback 请求注入的 127 Forwarded 头被误判为反向代理、导致 3080 根路径被官方 401 拦截」的回归;重启后本机 3080 应恢复正常。

Q11: 设备列表里出现很多 127.0.0.1 / 📱 iPhone (Browser),但我没有用过 iPhone?

答:这些通常不是真实设备,而是 1.10.0 之前测试套件写入的假 session:

- test/gateway.test.js 曾用 store.verify(code, 'iPhone', '127.0.0.1') 做配对测试;
- 当时的 SessionStore 在测试环境只隔离了 settings.json,没有隔离 devices.json;
- 每运行一次 npm test,就会向真实的 ~/.dsh/remote-mobile/devices.json 增加一条 127.0.0.1 / User-Agent: iPhone / 扫码配对码 的 session;
- 真实 iPhone Safari 的 User-Agent 会是很长的 Mozilla/5.0 (iPhone; ...) ... Safari/...,不会只有 iPhone。

1.10.0 已增加测试隔离:测试环境默认使用临时 /tmp/dsh-rm-test-devices-.json,不会再污染真实设备文件。已有的假 session 可以通过「一键撤销所有设备」后重新配对当前设备清理,或停止 DSH 后从 devices.json 中删除对应条目;操作前建议先备份该文件。

Q12: 手机通过 Tailscale / 3081 访问,点「选择文件夹」为什么在 PC 上弹窗,手机没有弹窗?

答:这不是 Tailscale 或手机浏览器的问题,而是 DSH 官方目录选择器的「本机 / 远程」判定导致的:

1. 工作区目录在 PC 上:DSH 的「选择文件夹」选的是运行 DSH 的电脑上的目录,不是手机本地目录。手机浏览器无法直接弹出 PC 的系统文件选择器。
2. auto 只按启动环境判定:dsh-host-directory-picker-auto 在启动时检查 webServer.host。当核心绑定 127.0.0.1、非 SSH 启动且系统有桌面时,它判定为 native,调用 PC 的 macOS / Windows 原生目录选择器。
3. 安全接入代理让远程请求看起来像本机:本插件让 DSH 核心保持 127.0.0.1:3080,由 0.0.0.0:3081 安全接入代理对外服务。手机通过 Tailscale 访问 3081 时,核心收到的仍是回环请求,auto 误判「操作者就坐在 PC 前」,于是把 native 窗口弹在 PC 上;手机端不会渲染这个系统窗口,只会等待。
修复(推荐一键开启,也支持手动配置):

- 方式一(Web 界面一键开启 / 还原,自动幂等去重):打开 DSH Web 界面 -> 「设置」->「远程与移动端」,在顶部醒目提醒横幅(或「接入」页签底部的「移动端添加工作区(目录选择器模式)」区域)点击 「⚡ 一键开启网页目录选择器」。插件会自动在 ~/.dsh/profiles/web/cordis.patch.yml 中安全写入配置并严格去重;后续如需切回电脑原生窗口,也可随时点击 「🔄 恢复 PC 原生目录选择器」 一键还原。
- 方式二(手动编辑配置文件):固定使用应用内 browse 目录选择器。编辑 ~/.dsh/profiles/web/cordis.patch.yml(Windows:%USERPROFILE%\.dsh\profiles\web\cordis.patch.yml),在末尾追加: yaml
- id: directory-picker
disabled: true
- insert:
- id: directory-picker-browse
name: '@deepseek-ai/dsh-host-directory-picker-browse'
- id: ui-directory-picker-browse
name: '@deepseek-ai/dsh-client-ui-directory-picker-browse'

注意:DSH 的 patch 层里 name 是校验已有插件名,不是重命名。不能只把 directory-picker 的 name 改成 browse,否则该 patch 会被跳过。必须先 disabled: true,再用无 id 的根层 insert 插入 browse 后端和浏览器端 UI。

保存后,启用了 HMR 的 DSH 通常会自动重载;否则完整重启 dsh web。手机端强制刷新页面后,点“选择文件夹”会出现应用内目录浏览器(面包屑、目录列表、新建子目录),手机和 PC 都会使用网页选择器。

取舍:这样 PC 本地也不再弹原生窗口。如果希望 PC 用 native、手机用 browse,需要单独建一个 remote profile 或自定义混合选择器;DSH 的 auto 不支持按客户端切换。browse 仍受现有 DSH / 插件鉴权保护,不会绕过认证。

Star History

📄 开源许可证

本项目采用 MIT License 协议开源。
预览分享卡片长什么样 ↓IceApriler/dsh-remote-mobile
分享 · DeepSeek Harness Hub
IceApriler/dsh-remote-mobile

DeepSeek Harness 远程与移动端安全网关插件:不改 DSH 底层代码,安全开放局域网与 Tailscale 连接,扫码认证、RSA 加密、防暴力破解。

这是你的仓库?认领条目

回到顶部