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

远程工作区 flymysql/dsh-remote

DeepSeek Harnessv0.8.54
flymysql0 天前有提交已提供中文文档扫描:含敏感能力

远程办公助手,面向 DeepSeek Harness(DSH):连接 SSH(密钥或密码),选远程工作区,用 rw_* 工具操作,再通过 SFTP 镜像成本地真实的 DSH 工作区。

65.6/100
综合分
232
GitHub 星标
20,765
npm 周下载
—/5
用户评分

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

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

状态速览

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

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

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

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

✓npm 包dsh-remote @ 0.8.54
✓Node 引擎未声明 engines.node
✓dsh CLI 依赖未声明 dsh 版本约束
✓入口文件main/exports/bin 已声明

验证方式:npm registry 存在性 + package.json 静态校验 · 最后检查 2026/10/29 17:39:18

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

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

可执行外部命令读写/删除本地文件写入剪贴板(不计风险)发起外部网络请求
查看扫描证据(共 159 处,展示前 104 条)
  • 读取本地文件
    lib/index.js:340
    const meta = JSON.parse(readFileSync(path.join(plain, '.dsh-remote-meta.json'), 'utf8'))
  • 读取本地文件
    lib/index.js:345
    if (!existsSync(plain)) return plain
  • 写入本地文件
    lib/index.js:355
    mkdirSync(dir, { recursive: true })
  • 写入本地文件
    lib/index.js:358
    writeFileSync(path.join(dir, '.dsh-remote-meta.json'), JSON.stringify(meta, null, 2))
  • 写入本地文件
    lib/index.js:364
    mkdirSync(to, { recursive: true })
  • 读取本地文件
    lib/index.js:365
    for (const e of readdirSync(from, { withFileTypes: true })) {
  • 写入本地文件
    lib/index.js:369
    else try { copyFileSync(s, d) } catch { }
  • 读取本地文件
    lib/index.js:382
    if (legacy === target || !existsSync(legacy) || existsSync(target)) return
  • 写入本地文件
    lib/index.js:384
    mkdirSync(path.dirname(target), { recursive: true })
  • 写入本地文件
    lib/index.js:386
    renameSync(legacy, target)
  • 读取本地文件
    lib/index.js:451
    if (existsSync(marker) && readFileSync(marker, 'utf8').trim() === LOADED_VERSION) rmSync(marker, { force: true })
  • 读取本地文件
    lib/index.js:451
    if (existsSync(marker) && readFileSync(marker, 'utf8').trim() === LOADED_VERSION) rmSync(marker, { force: true })
  • 删除本地文件
    lib/index.js:451
    if (existsSync(marker) && readFileSync(marker, 'utf8').trim() === LOADED_VERSION) rmSync(marker, { force: true })
  • 执行外部命令来自配置/变量
    lib/index.js:3188
    execFile(bin, args, { timeout: 15000, maxBuffer: 1 << 20 }, (err) => {
  • 执行外部命令来自配置/变量
    lib/index.js:3205
    execFile(bin, args, { timeout: PICK_TIMEOUT_MS, maxBuffer: 1 << 20 }, (err, stdout, stderr) => {
  • 发起网络请求
    lib/client.js:770
    const res = await fetch(apiPath(path), opts)
  • 发起网络请求
    lib/client.js:780
    const res = await fetch(apiPath(path), opts)
  • 写入剪贴板
    lib/client.js:949
    return navigator.clipboard.writeText(text).then(() => true).catch(() => false)
  • 读取本地文件
    lib/hostkey.js:42
    const j = JSON.parse(readFileSync(file, 'utf8'))
  • 写入本地文件
    lib/hostkey.js:61
    try { mkdirSync(path.dirname(knownHostsFile), { recursive: true }) } catch {}
  • 写入本地文件
    lib/hostkey.js:62
    writeFileSync(knownHostsFile, JSON.stringify(kh, null, 2))
  • 读取本地文件
    lib/ignore.js:196
    const text = readFileSync(ignoreFile, 'utf8')
  • 读取本地文件
    lib/sshconfig.js:78
    const readFile = io.readFile || ((p) => readFileSync(p, 'utf8'))
  • 读取本地文件
    lib/sshconfig.js:79
    const listDir = io.listDir || ((p) => readdirSync(p))
  • 读取本地文件
    lib/sshconfig.js:545
    const text = readFileSync(filePath, 'utf8')
  • 写入本地文件
    lib/sshconfig.js:696
    const mkdir = io.mkdir || ((p) => mkdirSync(p, { recursive: true }))
  • 写入本地文件
    lib/sshconfig.js:697
    const write = io.write || ((p, data) => writeFileSync(p, data, 'utf8'))
  • 写入本地文件
    lib/sshconfig.js:698
    const chmod = io.chmod || ((p, mode) => { try { chmodSync(p, mode) } catch { } })
  • 执行外部命令来自配置/变量
    lib/credential.js:47
    child = spawn(bin, args, { stdio: ['pipe', 'pipe', 'pipe'] })
  • 写入本地文件
    lib/credential.js:140
    mkdirSync(secretsDir || '.', { recursive: true })
  • 写入本地文件
    lib/credential.js:141
    writeFileSync(joinPath(secretsDir || '.', acc + '.bin'), b64, 'utf8')
  • 读取本地文件
    lib/credential.js:235
    const b64 = readFileSync(joinPath(secretsDir || '.', acc + '.bin'), 'utf8').trim()
  • 删除本地文件
    lib/credential.js:256
    try { unlinkSync(joinPath(secretsDir || '.', acc + '.bin')) } catch { }
  • 写入本地文件
    lib/sync.js:73
    if (!existsSync(lp)) mkdirSync(lp, { recursive: true })
  • 读取本地文件
    lib/sync.js:73
    if (!existsSync(lp)) mkdirSync(lp, { recursive: true })
  • 读取本地文件
    lib/sync.js:97
    if (existsSync(lp)) {
  • 读取本地文件
    lib/sync.js:98
    try { lk = localKey(statSync(lp)) } catch { lk = null }
  • 写入本地文件
    lib/sync.js:118
    writeFileSync(lp, buf)
  • 写入本地文件
    lib/sync.js:119
    try { utimesSync(lp, new Date(r.mtime * 1000), new Date(r.mtime * 1000)) } catch {}
  • 读取本地文件
    lib/sync.js:150
    const entries = readdirSync(lDir, { withFileTypes: true }).filter(
  • 读取本地文件
    lib/sync.js:174
    try { l = statSync(lp) } catch { return }
  • 读取本地文件
    lib/sync.js:199
    const buf = readFileSync(lp)
  • 写入本地文件
    lib/sync.js:206
    try { utimesSync(lp, new Date(rmt * 1000), new Date(rmt * 1000)) } catch {}
  • 写入本地文件
    lib/sync.js:234
    mkdirSync(localDir, { recursive: true })
  • 写入本地文件
    lib/sync.js:236
    writeFileSync(tmp, JSON.stringify(state, null, 1))
  • 读取本地文件
    lib/instructions.js:169
    try { lk = existsSync(lp) ? localKeyOf(statSync(lp)) : null } catch { lk = null }
  • 读取本地文件
    lib/instructions.js:169
    try { lk = existsSync(lp) ? localKeyOf(statSync(lp)) : null } catch { lk = null }
  • 写入本地文件
    lib/instructions.js:180
    mkdirSync(path.dirname(lp), { recursive: true })
  • 写入本地文件
    lib/instructions.js:181
    writeFileSync(lp, f.content)
  • 写入本地文件
    lib/instructions.js:184
    try { utimesSync(lp, new Date(f.mtime * 1000), new Date(f.mtime * 1000)) } catch { }
  • 读取本地文件
    lib/instructions-dynamic.js:225
    const lst = statSync(lp)
  • 写入本地文件
    lib/instructions-dynamic.js:229
    mkdirSync(path.dirname(lp), { recursive: true })
  • 写入本地文件
    lib/instructions-dynamic.js:230
    writeFileSync(lp, buf)
  • 写入本地文件
    lib/instructions-dynamic.js:231
    try { utimesSync(lp, new Date((st.mtime || 0) * 1000), new Date((st.mtime || 0) * 1000)) } catch { }
  • 读取本地文件
    lib/remote-shell.js:210
    try { return existsSync(candidate) } catch { return false }
  • 读取本地文件
    lib/binding.js:51
    if (!local || !root || !existsSync(root)) return { mirrorDir, remotePath, machine }
  • 读取本地文件
    lib/binding.js:55
    hostDirs = readdirSync(root)
  • 读取本地文件
    lib/binding.js:61
    if (!statSync(hostPath, { throwIfNoEntry: false })?.isDirectory?.()) continue
  • 读取本地文件
    lib/binding.js:64
    entries = readdirSync(hostPath)
  • 读取本地文件
    lib/binding.js:70
    if (!existsSync(metaPath)) continue
  • 读取本地文件
    lib/binding.js:76
    const meta = JSON.parse(readFileSync(metaPath, 'utf8'))
  • 读取本地文件
    lib/forwards.js:40
    const j = JSON.parse(readFileSync(this.file, 'utf8'))
  • 写入本地文件
    lib/forwards.js:49
    mkdirSync(path.dirname(this.file), { recursive: true })
  • 写入本地文件
    lib/forwards.js:50
    writeFileSync(this.file, JSON.stringify({ defs: this.defs }, null, 2))
  • 读取本地文件
    lib/update.js:69
    const pkg = JSON.parse(readFileSync(path.join(selfDir(), 'package.json'), 'utf8'))
  • 发起网络请求
    lib/update.js:120
    res = await fetch(REGISTRY_URL, { signal: controller.signal, headers: { accept: 'application/json' } })
  • 写入本地文件
    lib/update.js:197
    mkdirSync(tmpPkg, { recursive: true })
  • 写入本地文件
    lib/update.js:204
    mkdirSync(path.dirname(dest), { recursive: true })
  • 写入本地文件
    lib/update.js:205
    writeFileSync(dest, f.data)
  • 读取本地文件
    lib/update.js:210
    const newPkg = JSON.parse(readFileSync(path.join(tmpPkg, 'package.json'), 'utf8'))
  • 读取本地文件
    lib/update.js:215
    if (!existsSync(newIndex) || statSize(newIndex) < 100) throw new Error('tarball missing lib/index.js')
  • 读取本地文件
    lib/update.js:227
    const bytes = readFileSync(src)
  • 写入本地文件
    lib/update.js:229
    try { mkdirSync(path.dirname(dest), { recursive: true }) } catch {}
  • 读取本地文件
    lib/update.js:236
    if (existsSync(src)) install(src, path.join(installLib, name))
  • 读取本地文件
    lib/update.js:240
    if (existsSync(src)) install(src, path.join(dir, rel))
  • 写入本地文件
    lib/update.js:242
    writeFileSync(path.join(dir, '.dsh-remote-updated'), String(targetVersion))
  • 删除本地文件递归
    lib/update.js:247
    try { rmSync(tmpRoot, { recursive: true, force: true }) } catch {}
  • 写入本地文件
    lib/update.js:280
    writeFileSync(tmp, data)
  • 删除本地文件
    lib/update.js:288
    try { rmSync(tmp, { force: true }) } catch {}
  • 读取本地文件
    lib/telemetry.js:71
    const existing = readFileSync(file, 'utf8').trim()
  • 写入本地文件
    lib/telemetry.js:79
    mkdirSync(home, { recursive: true })
  • 写入本地文件
    lib/telemetry.js:80
    writeFileSync(file, `${created}\n`, 'utf8')
  • 发起网络请求
    lib/telemetry.js:147
    await fetch(heartbeatUrl(), {
  • 读取本地文件
    lib/telemetry.js:194
    return UUID_RE.test(readFileSync(path.join(home, ID_FILE), 'utf8').trim())
  • 读取本地文件
    lib/registry.js:19
    const j = JSON.parse(readFileSync(file, 'utf8'))
  • 写入本地文件
    lib/registry.js:44
    try { mkdirSync(path.dirname(file), { recursive: true }) } catch {}
  • 读取本地文件
    lib/registry.js:49
    const prev = JSON.parse(readFileSync(file, 'utf8'))
  • 写入本地文件
    lib/registry.js:58
    writeFileSync(file, JSON.stringify(payload, null, 2))
  • 发起网络请求
    lib/http-transport.js:21
    async fetch(request) {
  • 读取本地文件
    lib/pool.js:408
    key = readFileSync(keyPath)
  • 写入本地文件
    lib/openssh.js:134
    try { mkdirSync(dir, { recursive: true }) } catch { }
  • 写入本地文件
    lib/openssh.js:139
    writeFileSync(js, 'process.stdout.write((process.env.DSH_REMOTE_SSH_SECRET || "") + "\\n")\n')
  • 写入本地文件
    lib/openssh.js:140
    writeFileSync(cmd, '@echo off\r\n"' + process.execPath + '" "' + js + '"\r\n')
  • 写入本地文件
    lib/openssh.js:148
    writeFileSync(sh, '#!/bin/sh\nprintf \'%s\\n\' "$DSH_REMOTE_SSH_SECRET"\n')
  • 写入本地文件
    lib/openssh.js:149
    try { chmodSync(sh, 0o700) } catch { }
  • 读取本地文件
    lib/openssh.js:170
    try { accessSync(binary, fsConstants.X_OK); return true } catch { return false }
  • 读取本地文件
    lib/openssh.js:178
    try { accessSync(path.join(dir, binary + ext), fsConstants.X_OK); return true } catch { }
  • 执行外部命令来自配置/变量
    lib/openssh.js:250
    const child = spawn(this.sshBinary, args, {
  • 执行外部命令来自配置/变量
    lib/openssh.js:517
    child = spawn(sshBinary, args, { stdio: ['pipe', 'pipe', 'pipe'], env: secret ? askpassEnv(secret) : undefined })
  • 执行外部命令来自配置/变量
    lib/openssh.js:666
    const child = spawn(sshBinary, args, { stdio: ['pipe', 'pipe', 'pipe'], env: secret ? askpassEnv(secret) : undefined })
  • 写入本地文件
    lib/routes-fs.js:233
    mkdirSync(path.dirname(local), { recursive: true })
  • 读取本地文件
    lib/remote-shell-child.cjs:33
    return JSON.parse(readFileSync(file, 'utf8'))
  • 读取本地文件
    lib/remote-shell-child.cjs:46
    const exists = deps.exists || ((p) => existsSync(p))
  • 执行外部命令来自配置/变量
    lib/remote-shell-child.cjs:252
    const child = spawn(file, args, { stdio: 'inherit' })
扫描于 2026/11/3(0 天前) · 范围 入口闭包 + 全包扫描 · 来源 npm 发布包

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

依赖的 DSH / Cordis 模块
@deepseek-ai/schemastery@deepseek-ai/cordis@deepseek-ai/dsh-llm@deepseek-ai/dsh-scope@deepseek-ai/dsh-session@deepseek-ai/dsh-timeout@deepseek-ai/dsh-tools@deepseek-ai/dsh-commands@deepseek-ai/dsh-host-webserver@deepseek-ai/dsh-system-prompt@deepseek-ai/dsh-client-ui-renderer@deepseek-ai/dsh-client-locale
给这个插件打分还没有人投票↓
用户评分
还没有人投票,来当第一个

界面预览

4 张 · 点击查看大图

功能亮点

  • 连接SSH远程主机
  • 选择远程工作区
  • SFTP镜像为本地工作区
v0.8.672026-10-31
· 修两个数据级 bug:远端指令注入的消息没有 id(整份会话打不开);远程会话的 .md 链接读本地镜像(ENOENT) #75 — 远端工作区指令注入的回退分支产出的 user 消息没有 id 拿不到核心 createUserMessage 工厂时,回退对象是 { role:'user', content, source } —— 没有 id。 核心只在恢复会话时校验(assertMessageEventShape 要求 message.id 为非空字符串),写入时不报错, 于是一条注入消息静默落盘,下次打开会话时整份历史拒绝加载(历史加载失败,130+ 轮会话全废)。 回退分支现在生成 id: randomUUID()(与工厂自身的身份源一致)。不改为直接 import dsh-llm: 该包不在本插件依赖里(pnpm 隔离布局 resolve 不到),且插件按设计要在没有 llm 服务的最小组合里也能跑。 回归测试:a message built WITHOUT a factory still carries an id。 #74 — 远程会话里 .md 链接打开本地镜像而不是远程文件 canOpen 曾明确把 .md/.markdown 让给内置 Markdown 预览(#64:源码墙不如渲染),但内置预览读的是 本地镜像 —— 没同步过的文件直接 ENOENT,什么都打不开。现在: · 远程会话的 md 链接由 remote-link 认领,走 SFTP 打开(本地会话 cwd 不是镜像,仍让位给内置预览); · 查看器检测到 md 时用宿主自带的 MarkdownText 渲染(#64 的「渲染」诉求保住),老构建没有该组件 才回退源码。 测试:canOpen 决策翻转用例 + 渲染级用例(证明走 MarkdownText 分支而非源码墙)。 另:测试 harness 补了一个已知短板 —— 跨组件挂载时 hook state 现在可 resetState() 重置 (sweep 测试里 link body 曾读到设置页的 state 槽残留)。
v0.8.662026-10-30
· 去掉「远端 dsh 体检与部署」;审计日志可以在本机打开 ① 移除远程 dsh 部署 / 体检能力(按要求整体删掉,不留半可用状态) · 设置页那张「远端 dsh 体检与部署」卡片(体检 / 部署并验证 / 让 AI 排查 / 任务进度)整块删除。 · host 侧对应动作全部删除:/web-attach 的 probe / install / install-status / install-cancel / investigate;GET 响应也不再带 deploys/deploy (现在只保留隧道本身:open / close)。 · 删掉 rw_deploy_probe 工具、lib/web-deploy.js、lib/deploy-tasks.js、 lib/web-deploy-skill.js,以及 webInstallPrefix / webInstallVersion / webInstallRegistry 三个配置项(工具总数 22 → 21)。 · 远端必须有可用的 dsh:用 webAttachCommand(全局或按机器)指向它,README 已改。 ② 审计日志能打开日志文件 日志在「运行 DSH 的那台机器」上,浏览器打不开它 —— 所以由宿主执行并回报结果: · POST /dsh-remote/audit {action:'open'} —— 用本机默认程序打开日志(文件不存在则先创建); · POST /dsh-remote/audit {action:'reveal'} —— 在文件管理器里定位它; · 面板新增「打开日志文件」「在文件夹中显示」两个按钮,并且始终显示日志的完整路径, 这样即便宿主没有桌面环境(服务器 / CI),用户也能复制路径自己打开; 失败时给的是「无法自动打开(原因)—— 可以直接复制上面的路径」,不是一句空错。 实测(隔离实例 3213,真 GUI 点击):点「打开日志文件」→ Notepad 真的被拉起、日志文件被创建、 面板显示 已打开:D:\tmp\dsh-it3\remote-workspaces\audit.log;点「在文件夹中显示」→ 资源管理器定位到该文件、面板显示 已在文件管理器中定位:…。 ③ 修「配置里明明有主机,列表却是空的」 用户报告:面板显示 没在 C:\Users\…\.ssh\config 里找到主机,但文件里 3 个 Host 都在。 根因是 UI 把请求失败谎报成「你的配置没有主机」:loadSshHosts() 的 .catch 直接把列表设成空, 于是 401/500(或宿主半热重载后路由/字段不一致)都会渲染成「去改 ssh 配置吧」—— 让人去修一个没坏的文件。 现在两种状态分开:「读取失败:(这不是你的 ssh 配置问题,文件根本没读到;一直失败说明插件版本与服务端不一致,重启 DSH)」 vs「没在 里找到主机」。 (另注:用户那台实例的进程是 10-25 启动的,而修复版代码是 10-30 写入磁盘 —— 他看到的空列表来自那个时间差,重启即对齐。) 测试:新增 test/audit-open-and-no-deploy.test.js(6 条:工具不再注册、五个部署动作都被 action must be open | close 拒绝、GET 不再返回部署状态、打开日志会创建文件并回报路径、 未知动作 400、无桌面环境时给 501 + 路径);删除 5 个部署相关测试文件;全套 842 条通过。
v0.8.652026-10-30
· 「选目录」提交后多报一条 not a directory (or unreachable)(真机 GUI 测试抓出) 用一个隔离实例(独立 DSH_HOME、端口 3212、插件 0.8.64)在真实 GUI 里走完整流程时暴露: 选远程目录 → 「设为远程工作区」后工作区确实建好了,但面板同时报 not a directory (or unreachable): D:\tmp\...\remote-workspaces\...。 根因是双重提交:DirPicker.commitPath() 在 /mirror 成功后调用 onPicked(res.localMirror), 把本地镜像路径当成"用户选的路径"交回去;设置页拿到它又 POST 了一次 /mirror,于是远端去 stat 一个 Windows 路径。修法:commitPath 同时交出远端路径 onPicked(localMirror, remotePath) (侧边栏只取第一个参数,不受影响),设置页用它做提示并停止二次提交。 测试 +1(917 条):提交目录不许再 POST 一次 /mirror,且仍要刷新注册表、仍要说"已打开"。
数据来自仓库的 GitHub Release,可能与插件实际版本不一致。

README

由 DeepSeek 最新模型翻译生成
中文文档已移到仓库首页 → Moved The Chinese README is now this repository's main README, so this file is kept only so that older links to README.zh.md (npm pages, blog posts, mirrors, browser bookmarks) keep working. - 中文文档 → \README.md\(仓库首页) - English docs → \README.en.md\
预览分享卡片长什么样 ↓flymysql/dsh-remote
分享 · DeepSeek Harness Hub
flymysql/dsh-remote

远程办公助手,面向 DeepSeek Harness(DSH):连接 SSH(密钥或密码),选远程工作区,用 rw_* 工具操作,再通过 SFTP 镜像成本地真实的 DSH 工作区。

回到顶部