扫码让手机免密连上电脑,并逐台收回权限
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
安全吗
本站尚未对该插件做风险分级
暂未覆盖,不等同于无风险
还在维护吗
更新放缓:最近一次提交在 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🔵 扫描:含敏感能力尚无人工评估展开查看扫描说明与证据 ↓
安全扫描自动 · 静态分析源码
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.orgplugin/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。
预览分享卡片长什么样 ↓
分享 · DeepSeek Harness Hub
kaelorvyn/dsh-mobile
扫码让手机免密连上电脑,并逐台收回权限
相关插件 · 其他
模型破甲清洗HunYiSage/dsh-purge
⚔️ DeepSeek Harness 网络安全红队工具dsh-purge
★ 16,993已汉化DeepSeek 客户端
73
女娲蒸馏alchaincyf/nuwa-skill
输入名人名字,提炼其思维模型与决策方式供你调用
★ 37,308已汉化DSH
72
逆向分析桥接Fanqi89/dsh-rea
REA(Reverse Engineer Anything)MCP 桥接,用来把 REA 逆向工具接入 DSH 插件
★ 51已汉化DSH
72
语音女友beiyege-01/dsh-voice-ai-girlfriend
语音 AI 女友Voice AI girlfriend for DeepSeek Harness:Whisper…
★ 888已汉化DSH
71.6
红队越狱测试套件Minglink/dsh-infinite-gen-5
DeepSeek v4.1 flash 逆向工具无限五代 求 Star 收藏 ⭐欢迎大家提交项目的改进
★ 3,759已汉化DSH
71.4
协同办公空间dream-num/univer-workspace
一个让人类与 AI Agent 共同创作、协作和审阅的开源 Office 工作空间。
★ 2,269已汉化DSH
70.8
这是你的仓库?认领条目