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

手机访问授权 kaelorvyn/dsh-mobile

DeepSeek Harnessv2.10.6
kaelorvyn48 天前有提交已提供中文文档扫描:含敏感能力

扫码让手机免密连上电脑,并逐台收回权限

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

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

自动兼容检查有告警:npm 同名包「dsh-pocket」归属 shaobeichen/dsh-pocket,装到的可能不是本插件;安装清单取自 plugin/package.json。
安装命令 · 需先装 dsh CLI 引擎
dsh plugin --profile web add kaelorvyn/dsh-mobile
该包不可直接 npm 安装,改从 GitHub 源安装(明细见下方安装检查)

状态速览

判定依据:实装验证 · 静态安装检查 · 自动扫描 · 仓库维护状态
信任档位:需留意
是什么
dsh 原生插件 · other
装得上吗
实装验证未通过(unknown),装前请到仓库确认最近更新与 issue
L4 · 真实安装等级说明 →
安全吗
本站尚未对该插件做风险分级
暂未覆盖,不等同于无风险
还在维护吗
更新放缓:最近一次提交在 48 天前
档位由哪些信号合成

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

验证判据与等级说明 →
⚠︎ 实装验证未通过(unknown · 2026/11/4) ——可能是验证环境差异,装前建议到 GitHub 仓库确认最近更新与 issue。
社区实装还没有人打卡 · 装过的第一个就是你
安装兼容性检查⚠ 装前注意

npm 同名包「dsh-pocket」归属 shaobeichen/dsh-pocket,装到的可能不是本插件;安装清单取自 plugin/package.json

展开查看检查明细 ↓

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

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

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

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

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

可执行外部命令读写/删除本地文件写入剪贴板(不计风险)发起外部网络请求
查看扫描证据(共 65 处,展示前 60 条)
  • 读取本地文件
    android/auth-hop.js:9
    const pin = fs.readFileSync(path.join(os.homedir(), '.dsh', 'dsh-pocket', 'token'), 'utf8').trim();
  • 写入本地文件
    plugin/client/build.mjs:45
    await mkdir(dirname(outputPath), { recursive: true });
  • 写入本地文件
    plugin/client/build.mjs:46
    await writeFile(outputPath, wrapped, 'utf8');
  • 写入剪贴板
    plugin/client/client.js:2523
    await navigator.clipboard.writeText(text);
  • 请求外部主机registry.npmjs.org
    plugin/client/client.js:4225
    const meta = await (await fetch("https://registry.npmjs.org/dsh-pocket/latest", { cache: "no-store" })).json();
  • 发起网络请求
    plugin/client/client.js:4225
    const meta = await (await fetch("https://registry.npmjs.org/dsh-pocket/latest", { cache: "no-store" })).json();
  • 读取本地文件
    plugin/lib/devices.mjs:23
    const j = JSON.parse(readFileSync(FILE, 'utf8'));
  • 写入本地文件
    plugin/lib/devices.mjs:31
    mkdirSync(dirname(FILE), { recursive: true });
  • 写入本地文件
    plugin/lib/devices.mjs:33
    writeFileSync(tmp, JSON.stringify({ invites: list }, null, 2), 'utf8');
  • 写入本地文件
    plugin/lib/devices.mjs:34
    renameSync(tmp, FILE);
  • 读取本地文件
    plugin/lib/index.js:38
    return JSON.parse(readFileSync(pkgPath, 'utf8')).version;
  • 写入本地文件
    plugin/lib/index.js:57
    mkdirSync(dirname(p), { recursive: true });
  • 写入本地文件
    plugin/lib/index.js:58
    writeFileSync(p, fresh, { mode: 0o600 });
  • 读取本地文件
    plugin/lib/index.js:64
    const existing = readFileSync(p, 'utf8').trim();
  • 读取本地文件
    plugin/lib/index.js:184
    const raw = JSON.parse(await readFile(restartNoticePath(), 'utf8'));
  • 写入本地文件
    plugin/lib/index.js:191
    return mkdir(dirname(restartNoticePath()), { recursive: true })
  • 写入本地文件
    plugin/lib/index.js:192
    .then(() => writeFile(restartNoticePath(), JSON.stringify({ at: Date.now(), pid: process.pid }), 'utf8'));
  • 删除本地文件
    plugin/lib/index.js:201
    await rm(restartNoticePath(), { force: true }).catch(() => {});
  • 执行外部命令固定命令
    plugin/lib/index.js:223
    const child = spawn('dsh', ['plugin', '--profile', profile, 'update', 'dsh-pocket', '--latest', '-w'], {
  • 读取本地文件
    plugin/lib/index.js:278
    const raw = readFileSync(join(process.env.DSH_HOME ?? join(homedir(), '.dsh'), 'settings.yaml'), 'utf8');
  • 读取本地文件
    plugin/lib/service.mjs:84
    const v = readFileSync('/proc/version', 'utf8').toLowerCase();
  • 执行外部命令来自配置/变量
    plugin/lib/service.mjs:120
    execFile(candidates[i], [], { timeout: 5000, windowsHide: true }, (err, stdout) => {
  • 写入本地文件
    plugin/lib/service.mjs:249
    await mkdir(dirname(autoStatePath), { recursive: true });
  • 写入本地文件
    plugin/lib/service.mjs:250
    await writeFile(autoStatePath, JSON.stringify({ at: Date.now() }), 'utf8');
  • 删除本地文件
    plugin/lib/service.mjs:258
    try { await rm(autoStatePath, { force: true }); } catch { }
  • 读取本地文件
    plugin/lib/service.mjs:388
    const raw = await readFile(autoStatePath, 'utf8');
  • 读取本地文件
    plugin/lib/settings.mjs:27
    const raw = JSON.parse(readFileSync(settingsPath(), 'utf8'));
  • 写入本地文件
    plugin/lib/settings.mjs:35
    mkdirSync(dirname(settingsPath()), { recursive: true });
  • 写入本地文件
    plugin/lib/settings.mjs:36
    writeFileSync(settingsPath(), JSON.stringify(s, null, 2), { mode: 0o600 });
  • 删除本地文件
    plugin/lib/settings.mjs:115
    rmSync(settingsPath(), { force: true });
  • 读取本地文件
    plugin/lib/settings.mjs:186
    const text = readFileSync(join(homedir(), '.cloudflared', 'config.yml'), 'utf8');
  • 写入本地文件
    plugin/lib/tunnel.mjs:105
    const out = createWriteStream(dest);
  • 发起网络请求
    plugin/lib/tunnel.mjs:132
    try { head = await fetch(url, { method: 'HEAD', signal }); } catch { head = null; }
  • 发起网络请求
    plugin/lib/tunnel.mjs:138
    const res = await fetch(url, { signal });
  • 写入本地文件
    plugin/lib/tunnel.mjs:140
    await pipeline(Readable.fromWeb(res.body), createWriteStream(dest));
  • 发起网络请求
    plugin/lib/tunnel.mjs:148
    const probeRes = await fetch(url, { signal, headers: { Range: `bytes=0-${probeBytes - 1}` } });
  • 写入本地文件
    plugin/lib/tunnel.mjs:156
    const w = createWriteStream(dest);
  • 发起网络请求
    plugin/lib/tunnel.mjs:162
    const restRes = await fetch(url, { signal, headers: { Range: `bytes=${probeBytes}-${len - 1}` } });
  • 写入本地文件
    plugin/lib/tunnel.mjs:164
    await pipeline(Readable.fromWeb(restRes.body), createWriteStream(dest, { flags: 'a' }));
  • 删除本地文件
    plugin/lib/tunnel.mjs:168
    await rm(dest, { force: true }).catch(() => {});
  • 删除本地文件
    plugin/lib/tunnel.mjs:170
    await rm(dest, { force: true }).catch(() => {});
  • 发起网络请求
    plugin/lib/tunnel.mjs:186
    const res = await fetch(url, { signal, headers: { Range: `bytes=${p.start}-${p.end}` } });
  • 写入本地文件
    plugin/lib/tunnel.mjs:188
    await pipeline(Readable.fromWeb(res.body), createWriteStream(p.file));
  • 删除本地文件
    plugin/lib/tunnel.mjs:192
    await Promise.all(parts.map((p) => rm(p.file, { force: true }).catch(() => {})));
  • 发起网络请求
    plugin/lib/tunnel.mjs:210
    res = await fetch(TUNA_BOTTLES, { signal: AbortSignal.timeout(20_000) });
  • 读取本地文件
    plugin/lib/tunnel.mjs:267
    const st = await stat(tmpFile);
  • 删除本地文件
    plugin/lib/tunnel.mjs:274
    await rm(tmpFile, { force: true }).catch(() => {});
  • 写入本地文件
    plugin/lib/tunnel.mjs:292
    await rename(tmpFile, extracted).catch(async () => {
  • 执行外部命令固定命令
    plugin/lib/tunnel.mjs:302
    const child = spawn('tar', ['-xzf', tmpFile, '-C', extractDir], { stdio: 'ignore' });
  • 读取本地文件
    plugin/lib/tunnel.mjs:318
    try { if ((await stat(direct)).isFile()) found = direct; } catch { }
  • 读取本地文件
    plugin/lib/tunnel.mjs:325
    try { if ((await stat(bin)).isFile()) { found = bin; break; } } catch { }
  • 删除本地文件递归
    plugin/lib/tunnel.mjs:334
    await rm(extractDir, { recursive: true, force: true }).catch(() => {});
  • 删除本地文件
    plugin/lib/tunnel.mjs:339
    await rm(tmpFile, { force: true }).catch(() => {});
  • 执行外部命令来自配置/变量
    plugin/lib/tunnel.mjs:346
    execSync(process.platform === 'win32' ? 'where cloudflared' : 'command -v cloudflared', { stdio: 'ignore' });
  • 读取本地文件
    plugin/lib/tunnel.mjs:370
    await access(explicit);
  • 读取本地文件
    plugin/lib/tunnel.mjs:391
    await access(bin);
  • 执行外部命令来自配置/变量
    plugin/lib/tunnel.mjs:452
    const child = spawn(bin, ['--no-autoupdate', 'tunnel', 'run', '--protocol', 'http2'], {
  • 执行外部命令来自配置/变量
    plugin/lib/tunnel.mjs:542
    const child = spawn(bin, ['--no-autoupdate', 'tunnel', '--url', `http://127.0.0.1:${port}`, '--protocol', 'http2'], {
  • 发起网络请求
    plugin/lib/web-rpc.js:71
    async fetch(request) {
  • 发起网络请求
    plugin/lib/web-rpc.js:172
    const response = await fetchHandler.fetch(request);
扫描于 2026/11/4(0 天前) · 范围 全包扫描 · 来源 GitHub 仓库
涉及外部主机:registry.npmjs.org

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

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

功能亮点

  • 扫码免密连接手机
  • 查看手机访问权限
  • 逐台收回访问权限

把 DeepSeek Harness 装进手机:扫码即连、免密码,电脑上可查看并逐台收回手机访问权限。含 dsh-pocket 魔改版插件 (GPL-2.0) 与 Android App。

README

由 DeepSeek 最新模型翻译生成
DSH Mobile 把 DeepSeek Harness(DSH) 装进手机:扫码即连,免输密码;电脑上能看见「哪些手机连过」并逐台收回权限。 本仓库 = 两部分: | 目录 | 是什么 | | plugin/ | dsh-pocket 的魔改版(GPL-2.0 分支)。DSH 插件,在电脑上跑一个反向代理,把本机 DSH 暴露到局域网 / 公网,并负责鉴权。 | | android/ | DSH Android App。一个 WebView 壳 + 自带相机扫码,扫电脑上的二维码后自动绑定并选路连接。 | 它解决什么问题 原生 dsh-pocket 的远程访问:一个总密码给所有设备。带来的麻烦是—— - 想让第二台手机连进来,得把密码告诉对方; - 某台手机丢了 / 不想让它连了,只能整体换密码,所有设备都得重连; - 密码轮换后,之前发出去的链接全部失效。 这套改动把授权模型换成 「一张邀请码 = 一台手机」: - 电脑上那个二维码里装的是邀请码,不是总密码; - 手机扫了就用它连,不用输密码; - 删掉哪张邀请码,用它的那台手机立刻失效,下次打开 App 自动回到扫码界面; - 总密码仍然保留,给电脑自己的浏览器等场景用。 一个二维码,两种用法 二维码内容形如: https:///?
展开完整文档(README 剩余 1 节)
DSH Mobile

把 DeepSeek Harness(DSH) 装进手机:扫码即连,免输密码;电脑上能看见「哪些手机连过」并逐台收回权限。

本仓库 = 两部分:

| 目录 | 是什么 |
| plugin/ | dsh-pocket 的魔改版(GPL-2.0 分支)。DSH 插件,在电脑上跑一个反向代理,把本机 DSH 暴露到局域网 / 公网,并负责鉴权。 |
| android/ | DSH Android App。一个 WebView 壳 + 自带相机扫码,扫电脑上的二维码后自动绑定并选路连接。 |

它解决什么问题

原生 dsh-pocket 的远程访问:一个总密码给所有设备。带来的麻烦是——

- 想让第二台手机连进来,得把密码告诉对方;
- 某台手机丢了 / 不想让它连了,只能整体换密码,所有设备都得重连;
- 密码轮换后,之前发出去的链接全部失效。

这套改动把授权模型换成 「一张邀请码 = 一台手机」:

- 电脑上那个二维码里装的是邀请码,不是总密码;
- 手机扫了就用它连,不用输密码;
- 删掉哪张邀请码,用它的那台手机立刻失效,下次打开 App 自动回到扫码界面;
- 总密码仍然保留,给电脑自己的浏览器等场景用。

一个二维码,两种用法

二维码内容形如:

https:///?dshbind=1&pp=&lp=&li=&lport=

- 手机浏览器扫 → 就是个普通网址,正常打开 DSH 网页,照旧要输密码(dshbind 参数被无视);
- DSH App 扫 → 认出参数,把公网地址和局域网地址一起存下来,免密码连上,并自动选择更快的局域网。

二维码同时带公网和局域网入口,所以在家和在外面扫的是同一个码。

手机端

- 自带相机扫码:全屏取景,用系统 Camera + ZXing 本地解码,不依赖任何扫码 SDK。
- 支持从相册选码:二维码在截图里、聊天记录里、另一台设备上时,直接选图识别。走系统相册,不需要任何存储权限。
- 自动选路:并发探测局域网和公网入口,谁先通用谁;网络刚切换(比如 WiFi 断掉改走流量)导致的瞬时失败会自动重试一次。
- 连不上时给说人话的原因:失败页会列出每个入口的探测结果(超时 / 密码不对 / 端口连不上 / 域名解析不了),而不是一句「连不上」。
- 全面屏适配(沉浸式状态栏)、针对手机窄屏做了界面压缩。

电脑端(插件面板)

DSH → 设置 → 手机访问,只有三块:

┌ 扫码连接手机 ───────────────┐
│ [ 二维码 ] │
├ 手机授权 ─────────────────┤ │ PJH110 最近连接 09-11 22:30 [删除] │
│ [再加一台] │
├ 修改密码 ──────────────────┤ │ 密码 •••••••• [修改] │ └───────────────────────────┘

- 手机授权:列出每台扫码连过的手机(型号 + 最近连接时间),逐台删除。
- 只有一个密码:局域网和公网共用同一个,改的时候两份一起改。

安装

1. 电脑端:安装插件

把 plugin/ 覆盖到你的 dsh-pocket 安装位置(通常是 ~/.dsh/profiles//node_modules/dsh-pocket/),然后重启 DSH。

前端包 client/client.js 已经构建好,开箱即用。改了 client/index.jsx 的话重新构建:

cd plugin/client && node build.mjs

2. 公网入口(可选)

插件会自动发现公网域名:它读取 ~/.cloudflared/config.yml,找出 service 指向本插件端口(默认 3081)的那条 hostname。

所以用命名隧道(Cloudflare Named Tunnel)时,不需要在插件里重复配一遍域名:

tunnel:
credentials-file:
ingress:
- hostname: dsh.example.com
service: http://127.0.0.1:3081
originRequest:
【关键】不设这个,cloudflared 会把 Host 改成 127.0.0.1:3081,插件会按来源判定成「局域网」,于是公网访问却要局域网密码。
httpHostHeader: dsh.example.com
- service: http_status:404

3. 手机端:编译 App

cd android
./gradlew assembleRelease

需要 JDK 17+ 和 Android SDK(minSdk 24、targetSdk 34)。产物在 app/build/outputs/apk/release/。

注意:仓库里的 applicationId 是 cn.kaelorvyn.dsh。要做成自己的,改 app/build.gradle 里的 namespace / applicationId 即可。

4. 配对

1. 电脑上打开 DSH → 设置 → 手机访问,会显示一个二维码;
2. 手机打开 App,扫这个码(扫不上就点「从相册选择」);
3. 自动连上。以后直接开 App 就行 —— 在家走局域网,在外面走公网,不用管。

安全说明

- 邀请码是 64 位随机值,存在 ~/.dsh/dsh-pocket/devices.json;该文件不在本仓库。
- 邀请码只授予「访问你这台电脑上的 DSH」的权限,不等同于 DSH 本身的凭据。
- 公网入口始终要求鉴权(这条是 fail-closed 的:除了 loopback / 私网 Host,一切陌生域名都按公网处理)。
- 删掉邀请码后,用它的手机在下一次请求就会被拒;已建立的登录 cookie 也因为绑定进程级密钥而失效。
- 请不要把 ~/.cloudflared/ 里的凭据文件、devices.json、settings.json 提交到任何仓库。

与上游的差异

上游:shaobeichen/dsh-pocket(GPL-2.0,版本 2.10.6)。

本分支新增 / 改动:

新增
- lib/devices.mjs —— 邀请码存储(创建 / 列出 / 撤销 / 记录最近使用)。
- client/index.jsx 里的二维码面板(客户端用 qrcode 直接画,主机端不用加渲染接口)。
- Android App 全部代码(android/,原创)。

改动
- lib/proxy.mjs —— 接上早已预留但没人提供的 auth.getAltTokens() 扩展点(让邀请码和总密码一起参与鉴权),并新增 auth.onAuthenticated() 回调用于记录「哪台设备用了哪张邀请码」。
- lib/index.js —— 提供 getAltTokens / onAuthenticated;新增 devices.* RPC。
- lib/web-rpc.js —— 新增 devices.list / devices.create / devices.revoke。
- lib/settings.mjs —— 新增 detectPublicHost(),从 cloudflared 配置里自动发现公网域名。
- client/index.jsx —— 面板大幅精简(删掉宣传 / star / 反馈 / 更新横幅 / 恢复出厂 / 局域网开关 / 手动选地址 / 公网隧道配置等),只留「二维码 + 手机授权 + 改密码」;密码合并为一个。
- client/api.js —— 新增三个 RPC 端点常量。

更详细的改动记录见 NOTICE.md。

许可

GPL-2.0(继承上游 dsh-pocket)。见 LICENSE。
预览分享卡片长什么样 ↓kaelorvyn/dsh-mobile
分享 · DeepSeek Harness Hub
kaelorvyn/dsh-mobile

扫码让手机免密连上电脑,并逐台收回权限

这是你的仓库?认领条目

回到顶部