让团队成员用浏览器登录共享一套服务,各自拥有独立会话与文件
30.8/100
综合分
1
GitHub 星标
—
npm 周下载
—/5
用户评分
数据截至 2026/10/25 · 周下载取自 npm 全网(含 CI / 镜像),非本站安装数;综合分由星标、活跃度与兼容性检查合成。
安装命令 · 需先装 dsh CLI 引擎
dsh plugin --profile web add sunbigfly/dsh-multi-user-service该包不可直接 npm 安装,改从 GitHub 源安装(明细见下方安装检查)
状态速览
判定依据:实装验证 · 静态安装检查 · 自动扫描 · 仓库维护状态信任档位:已验证
是什么
dsh 原生插件 · model
安全吗
本站尚未对该插件做风险分级
暂未覆盖,不等同于无风险
还在维护吗
活跃:最近一次提交在 6 天前
档位由哪些信号合成
本站实装验证(真实安装 L4 覆盖全站;L5 为运行时冒烟或实机截图,覆盖范围小于 L4) · 验证所用 dsh 版本 · 静态安装检查 · 风险分级 · 仓库维护状态。 下方各区块是它的证据明细。
社区实装还没有人打卡 · 装过的第一个就是你
安全扫描自动 · 静态分析源码v2.7.0🔵 扫描:含敏感能力尚无人工评估展开查看扫描说明与证据 ↓
安全扫描自动 · 静态分析源码
v2.7.0静态扫描发现插件具备触达本地系统的能力:执行外部命令、读写/删除本地文件、读取剪贴板或密钥类环境变量(至少其一)。这是能力清单,不是危险判定——具体能力项与证据见下方,安装前请自行判断是否需要。
读写/删除本地文件写入剪贴板(不计风险)发起外部网络请求
查看扫描证据(共 119 处,展示前 89 条)
- 发起网络请求client.js:35
const send = init => (transportFetch ? transportFetch(ENDPOINT, init) : fetch(target, init)); - 写入剪贴板client.js:1128
if (globalThis.navigator?.clipboard?.writeText) await globalThis.navigator.clipboard.writeText(url); - 发起网络请求member-host.mjs:87
const response = await contentApi.fetch(new Request(new URL('/api/' + method, ownerOrigin), { method: 'POST', headers: { 'content-type': 'application/ - 读取本地文件carrier/backup.mjs:36
if (!this.path.startsWith('/') || Buffer.byteLength(this.path) > 100 || await realpath(dirname(this.path)) !== dirname(this.path)) throw new Error('Ba - 读取本地文件carrier/backup.mjs:38
const stat = await lstat(this.path); - 删除本地文件carrier/backup.mjs:42
await unlink(this.path); - 读取本地文件carrier/backup.mjs:58
await chmod(this.path, 0o600); this.stat = await lstat(this.path); - 写入本地文件carrier/backup.mjs:58
await chmod(this.path, 0o600); this.stat = await lstat(this.path); - 读取本地文件carrier/backup.mjs:64
try { const current = await lstat(this.path); if (current.ino === this.stat?.ino) await unlink(this.path); } catch (error) { if (error.code !== 'ENOEN - 删除本地文件carrier/backup.mjs:64
try { const current = await lstat(this.path); if (current.ino === this.stat?.ino) await unlink(this.path); } catch (error) { if (error.code !== 'ENOEN - 读取本地文件carrier/administration.mjs:19
const exists = async path => { try { return await lstat(path); } catch (error) { if (error.code === 'ENOENT') return null; throw error; } }; - 读取本地文件carrier/administration.mjs:27
if (await realpath(root) !== root) reject('ADMIN_FILE_DENIED', 'The member directory is not canonical.', 403); - 读取本地文件carrier/administration.mjs:48
const entries = await opendir(at(child)); - 读取本地文件carrier/administration.mjs:54
const stat = await lstat(at(handle, name)); - 删除本地文件carrier/administration.mjs:55
if (!stat.isDirectory()) return unlink(at(handle, name)); - 读取本地文件carrier/administration.mjs:57
try { for (const entry of await readdir(at(child))) await removeEntry(child, entry); } - 删除本地文件carrier/administration.mjs:59
await rmdir(at(handle, name)); - 写入本地文件carrier/administration.mjs:64
try { await mkdir(at(held, RECYCLE), { mode: 0o700 }); } catch (error) { if (error.code !== 'EEXIST') throw error; } - 读取本地文件carrier/administration.mjs:71
const stat = await lstat(at(source, sourceName)); - 写入本地文件carrier/administration.mjs:74
await mkdir(at(target, targetName), { mode: stat.mode & 0o777 }); - 写入本地文件carrier/administration.mjs:181
await rename(at(location.handle, location.name), at(bin, id)); - 删除本地文件carrier/administration.mjs:374
if (job.revision === null) { await link(at(job.parent, job.temporary), at(job.parent, job.name)); await unlink(at(job.parent, job.temporary)); } - 写入本地文件carrier/administration.mjs:375
else await rename(at(job.parent, job.temporary), at(job.parent, job.name)); - 写入本地文件carrier/administration.mjs:418
if (r.operation === 'files.mkdir') { await mkdir(at(location.handle, location.name), { mode: 0o700 }); return { created: true }; } - 写入本地文件carrier/administration.mjs:426
await rename(at(location.handle, location.name), at(target.handle, target.name)); return { renamed: true }; - 删除本地文件carrier/administration.mjs:440
try { await job.file.close(); await unlink(at(job.parent, job.temporary)).catch(error => { if (error.code !== 'ENOENT') throw error; }); } - 读取本地文件carrier/setup.mjs:63
if ((await realpath(parent)).replaceAll('\\', '/') !== parent) reject('INVALID_INPUT', 'Recommended directory parent must be canonical.'); - 写入本地文件carrier/setup.mjs:64
try { await mkdir(this.recommendedRoot, { mode: 0o750 }); } catch (error) { if (error.code !== 'EEXIST') throw error; } - 读取本地文件carrier/setup.mjs:65
const info = await lstat(this.recommendedRoot); - 读取本地文件carrier/member.mjs:82
try { target = await realpath(lexical); } - 读取本地文件carrier/member.mjs:106
try { realHome = memberPath(await realpath(home)); } catch { return false; } - 读取本地文件carrier/member.mjs:107
try { realTarget = memberPath(await realpath(candidate)); } catch (error) { return ['ENOENT', 'ENOTDIR'].includes(error?.code); } - 读取本地文件carrier/member.mjs:137
const root = await realpath(frontendDist); - 发起网络请求carrier/member.mjs:246
const response = await api.fetch(new Request(target, { method: 'POST', headers: { 'content-type': 'application/json' }, body }), peer); - 读取本地文件carrier/member.mjs:307
try { canonicalBase = await realpath(base); } catch { bad('MEMBER_FILE_DENIED', 'Member file base is unavailable', 403); } - 发起网络请求carrier/member.mjs:357
const response = await api.fetch(new Request(new URL('/api/settings/describe', origin), { method: 'POST', headers: { 'content-type': 'application/json - 发起网络请求carrier/member.mjs:587
const response = await api.fetch(new Request(target, init), authority); - 读取本地文件carrier/member.mjs:795
const memberSource = await readFile(fileURLToPath(new URL('../native-client.js', import.meta.url)), 'utf8'); - 读取本地文件carrier/member-boundary.mjs:45
if (typeof home !== 'string' || !home.startsWith('/') || canonical(home) !== home || !lstatSync(home).isDirectory()) failure('MEMBER_BOUNDARY_DENIED', - 读取本地文件carrier/member-boundary.mjs:46
const info = lstatSync(home); - 写入本地文件carrier/member-boundary.mjs:54
try { mkdirSync(temp, { mode: 0o700 }); } catch (error) { if (error.code !== 'EEXIST') throw error; } - 读取本地文件carrier/member-boundary.mjs:55
if (!lstatSync(temp).isDirectory() || lstatSync(temp).isSymbolicLink() || canonical(temp) !== temp) failure('MEMBER_BOUNDARY_DENIED', 'The private tem - 读取本地文件carrier/workspaces.mjs:39
try { ids = JSON.parse(await readFile(posix.join(home, ARCHIVED), 'utf8')); if (!Array.isArray(ids)) throw new Error('Invalid archive list'); } - 读取本地文件carrier/workspaces.mjs:54
const parsed = JSON.parse(await readFile(posix.join(home, META), 'utf8')); - 写入本地文件carrier/workspaces.mjs:62
try { await writeFile(posix.join(home, META), body, 'utf8'); } catch { } - 读取本地文件carrier/workspaces.mjs:67
try { entries = await readdir(home, { withFileTypes: true }); } catch { return []; } - 读取本地文件carrier/workspaces.mjs:114
const parsed = JSON.parse(await readFile(posix.join(root, ARCHIVED), 'utf8')); - 删除本地文件carrier/workspaces.mjs:127
catch (error) { await unlink(temporary).catch(() => {}); throw error; } - 写入本地文件carrier/workspaces.mjs:129
try { await rename(temporary, posix.join(root, ARCHIVED)); } - 删除本地文件carrier/workspaces.mjs:130
catch (error) { await unlink(temporary).catch(() => {}); throw error; } - 读取本地文件carrier/workspaces.mjs:139
const parsed = JSON.parse(await readFile(posix.join(root, ATTACHMENTS), 'utf8')); - 写入本地文件carrier/workspaces.mjs:149
try { await writeFile(posix.join(root, ATTACHMENTS), `${JSON.stringify(ids, null, 2)}\n`, 'utf8'); } catch { } - 写入本地文件carrier/workspaces.mjs:164
await mkdir(root, { recursive: true }); - 写入本地文件carrier/workspaces.mjs:171
await mkdir(path); - 写入本地文件carrier/workspaces.mjs:191
await mkdir(root, { recursive: true }); - 读取本地文件carrier/workspaces.mjs:289
const read = await readdir(target, { withFileTypes: true }); - 写入本地文件carrier/access.mjs:21
appendFileSync(TRACE, `${JSON.stringify({ at: new Date().toISOString(), action: request?.action, resources: request?.resources, args: request?.args == - 读取本地文件carrier/file-ranges.mjs:26
|| realpathSync(path).replaceAll('\\', '/') !== path || !statSync(path).isDirectory()) invalid(); - 读取本地文件carrier/home-runtime.mjs:21
try { info = await lstat(current); } - 写入本地文件carrier/home-runtime.mjs:25
await mkdir(current, { mode: 0o700 }).catch(error => { if (error.code !== 'EEXIST') throw error; }); - 读取本地文件carrier/home-runtime.mjs:26
info = await lstat(current); - 读取本地文件carrier/home-runtime.mjs:28
if (!info.isDirectory() || info.isSymbolicLink() || (await realpath(current)).replaceAll('\\', '/') !== current) fail('UNSAFE_PROJECT_ROOT'); - 读取本地文件carrier/home-runtime.mjs:30
const info = await lstat(directory); - 删除本地文件递归carrier/home-runtime.mjs:64
} finally { await rm(directory, { recursive: true, force: true }); } - 读取本地文件carrier/cli-management.mjs:73
try { await access(path,constants.X_OK); if ((await stat(path)).isFile()) { command=await realpath(path); break; } } catch {} - 读取本地文件carrier/cli-management.mjs:73
try { await access(path,constants.X_OK); if ((await stat(path)).isFile()) { command=await realpath(path); break; } } catch {} - 读取本地文件carrier/cli-management.mjs:73
try { await access(path,constants.X_OK); if ((await stat(path)).isFile()) { command=await realpath(path); break; } } catch {} - 写入本地文件carrier/execution-tools.mjs:82
const path = join(temp, 'arguments.json'); await writeFile(path, JSON.stringify(args.arguments), { mode: 0o600, flag: 'wx' }); - 删除本地文件carrier/execution-tools.mjs:89
await rm(join(temp, 'arguments.json'), { force: true }); - 写入本地文件carrier/cli-tools.mjs:27
await mkdir(path, { recursive: true, mode: 0o700 }); - 读取本地文件carrier/cli-tools.mjs:28
const info = await lstat(path); - 读取本地文件carrier/cli-tools.mjs:29
if (!info.isDirectory() || info.isSymbolicLink() || await realpath(path) !== path || (info.mode & 0o077)) fail('CLI state requires a canonical, owner- - 读取本地文件carrier/cli-tools.mjs:33
try { const info = await lstat(path); if (!info.isSymbolicLink() && (info.mode & 0o777) === mode && await readFile(path, 'utf8') === text) return; } - 读取本地文件carrier/cli-tools.mjs:33
try { const info = await lstat(path); if (!info.isSymbolicLink() && (info.mode & 0o777) === mode && await readFile(path, 'utf8') === text) return; } - 写入本地文件carrier/cli-tools.mjs:36
try { await writeFile(temporary, text, { mode, flag: 'wx' }); await rename(temporary, path); } - 写入本地文件carrier/cli-tools.mjs:36
try { await writeFile(temporary, text, { mode, flag: 'wx' }); await rename(temporary, path); } - 删除本地文件carrier/cli-tools.mjs:37
finally { await rm(temporary, { force: true }); } - 读取本地文件carrier/cli-tools.mjs:51
if (!statSync(command).isFile() || !(statSync(command).mode & 0o111)) fail('CLI command is not an executable file.'); - 读取本地文件carrier/cli-tools.mjs:97
try { previous = JSON.parse(await readFile(join(home, 'commands.json'), 'utf8')); } - 删除本地文件carrier/cli-tools.mjs:100
await rm(join(bin, id), { force: true }); await rm(join(configs, id + '.json'), { force: true }); - 读取本地文件carrier/execution-catalog.mjs:24
const info = await stat(path); - 读取本地文件carrier/execution-catalog.mjs:26
return readFile(path, 'utf8'); - 读取本地文件carrier/execution-catalog.mjs:55
try { await access(command, constants.X_OK); await access(join(root, 'tools')); return [{ id: 'tool-runner', kind: 'tool-runner', root, command }]; } - 读取本地文件carrier/execution-catalog.mjs:62
try { await access(join(path, 'package.json')); return path; } catch {} - 读取本地文件carrier/execution-catalog.mjs:63
try { await access(join(path, 'pyvenv.cfg')); return path; } catch {} - 读取本地文件carrier/execution-catalog.mjs:74
const interpreter = await realpath(match[1]); - 发起网络请求native-client.js:58
function createMemberApi({ csrf, fetcher = (...args) => fetch(...args), onExpired = () => {} }) { - 发起网络请求native-client.js:202
const snapshot = await api.request('snapshot'); - 发起网络请求native-client.js:223
await api.request('logout');
扫描于 2026/11/1(0 天前) · 范围 入口闭包 + 发布文件清单 · 来源 GitHub 仓库
静态扫描只反映源码中出现的调用,不构成安全背书;人工评估与自动扫描相互独立、互不覆盖。
依赖的 DSH / Cordis 模块
@deepseek-ai/dsh-api-gateway@deepseek-ai/cordis@deepseek-ai/schemastery给这个插件打分还没有人投票↓
用户评分
还没有人投票,来当第一个
DSH 多用户服务:在公司或团队服务器上运行一套 DSH,供多人通过浏览器登录;成员拥有独立会话与文件,管理员统一管理账号、权限、用量与并发。服务端业务扩展,适配 DSH 0.2.1-rc.2,支持 Linux/WSL。
README
由 DeepSeek 最新模型翻译生成DSH 多用户服务一台服务器,一套 DSH,为整个团队提供服务。
插件 v0.2.3 · DSH 0.2.1-rc.2 · Linux / WSL 服务器
在公司或团队的服务器上运行一套 DSH,让多人通过浏览器登录使用。每位成员拥有自己的会话和文件空间,管理员统一管理账号、权限与用量额度。
这是为 DSH 增加多用户管理能力的服务端业务扩展。模型、Skills、MCP、Agent 预设和工具插件由服务器统一配置,再按成员授权使用。
功能
- 账号管理:创建或邀请成员,管理账号状态与登录安全。
- 权限分配:按成员分配模型、工具、Skills、MCP、Agent 预设和目录访问范围。
- 独立空间:成员管理自己的会话与文件,管理员可统一管理成员内容。
- 用量控制:查看成员用量,设置并发任务数及每日、每月 tokens 额度。
- 服务维护:支持隔离执行、文件回收站和备份,界面沿用 DSH 原生风格。
可用于公司内网或已配置的公网入口;成员需要能访问服务器,并使用管理员创建或邀请的账号登录。
开始使用
1. 准备服务器
服务器使用 Linux 或 WSL,需已安装:
- DSH 0.2.1-rc.2、Node.js 22+。
- Python 3、bubblewrap(bwrap)、用户级 systemd。
成员只需浏览器,无需安装 DSH。
展开完整文档(README 剩余 1 节)
DSH 多用户服务一台服务器,一套 DSH,为整个团队提供服务。 插件 v0.2.3 · DSH 0.2.1-rc.2 · Linux / WSL 服务器 在公司或团队的服务器上运行一套 DSH,让多人通过浏览器登录使用。每位成员拥有自己的会话和文件空间,管理员统一管理账号、权限与用量额度。 这是为 DSH 增加多用户管理能力的服务端业务扩展。模型、Skills、MCP、Agent 预设和工具插件由服务器统一配置,再按成员授权使用。 功能 - 账号管理:创建或邀请成员,管理账号状态与登录安全。 - 权限分配:按成员分配模型、工具、Skills、MCP、Agent 预设和目录访问范围。 - 独立空间:成员管理自己的会话与文件,管理员可统一管理成员内容。 - 用量控制:查看成员用量,设置并发任务数及每日、每月 tokens 额度。 - 服务维护:支持隔离执行、文件回收站和备份,界面沿用 DSH 原生风格。 可用于公司内网或已配置的公网入口;成员需要能访问服务器,并使用管理员创建或邀请的账号登录。 开始使用 1. 准备服务器 服务器使用 Linux 或 WSL,需已安装: - DSH 0.2.1-rc.2、Node.js 22+。 - Python 3、bubblewrap(bwrap)、用户级 systemd。 成员只需浏览器,无需安装 DSH。当前版本已在 DSH 0.2.1-rc.2 验证,其他 DSH 版本尚未验证。 2. 安装 已有 DSH 服务器:从市场安装。 1. 在服务器本机的 DSH 插件页打开 实验性插件 → 自定义插件索引。 2. 在 发现 → 添加索引源 填入: https://raw.githubusercontent.com/sunbigfly/dsh-multi-user-service/main/market/index.json 3. 搜索 DSH 多用户服务,安装并启用。 4. 按 已有服务接入 配置访问入口,继续使用这一份 DSH。 独立部署:从源码启动服务器。 git clone https://github.com/sunbigfly/dsh-multi-user-service.git cd dsh-multi-user-service npm ci --ignore-scripts python3 ops/install-service.py ~/.local/bin/dsh-lan 安装脚本配置 lan profile 和后台服务;启动后输出访问地址,默认使用本机局域网 IP。 3. 初始化管理员 1. 在服务器本机已授权的 DSH 页面中操作。独立部署的本机启动链接位于 ~/.local/share/dsh-lan/dsh-lan.log。 2. 在 DSH 设置中配置可用模型。 3. 打开侧栏 多用户服务,初始化管理员账号,并在 共享环境 检查执行隔离。 首次初始化需要服务器本机的 DSH 授权。以后可直接使用管理员账号登录服务入口。 4. 添加成员 1. 打开 多用户服务 → 成员管理,选择 新建账号 或 邀请成员。 2. 先填写信息,再配置权限,最后创建账号或生成邀请。 3. 在 我的账号 中复制成员入口,发送给成员。成员登录后即可使用自己的会话与文件空间。 | 入口 | 用途 | | http(s)://服务器地址/ | 管理员登录 | | http(s)://服务器地址/lan/ | 成员登录、接受邀请 | 实际协议、地址和端口以部署配置为准;域名与反向代理配置见 配置与运维。 常用命令 dsh-lan status # 查看状态 dsh-lan restart # 重启 dsh-lan stop # 停止 安装包名为 @sunbigfly/dsh-multi-user-service。dsh-lan 命令和 /lan/ 路径沿用原标识。 旧包 @sunbigfly/dsh-lan-workspace 升级到新包:先停止服务,在原 profile 卸载旧包后安装新包,再启动服务;独立部署可重新运行 python3 ops/install-service.py 自动迁移安装引用。账号、会话与文件的存储位置不变。 MIT License
预览分享卡片长什么样 ↓
分享 · DeepSeek Harness Hub
sunbigfly/dsh-multi-user-service
让团队成员用浏览器登录共享一套服务,各自拥有独立会话与文件
相关插件 · 模型接入
API 中继安全审计toby-bridges/api-relay-audit
面向 AI API 中转与 LLM 代理的本地安全审计。
★ 897已汉化DSH
69.4
免费搜索插件DDDMUC/dsh-free-search
DeepSeek Harness 免费搜索插件 —— 无需 API key,零成本,多引擎可切换。 一个给…
★ 761已汉化DSH
68.7
免费联网搜索liustack/modsearch
在官方 App 里模型能联网,切到 API 就不能了。ModSearch 把联网补回来:网页搜索、X…
★ 863已汉化DSH
68.5
OpenCode 免费模型FishBottle7/opencode2dsh
在 DSHDeepSeek Harness里原生使用 OpenCode Zen 的免费匿名模型。
★ 379已汉化DSH
67.8
AI 模型网关tingly-dev/tingly-box
Tingly Box Web UI 演示
★ 373已汉化DSH
67.4
WorkBuddy 账号池XDTrees/dsh-xdpool
把你能白嫖的模型额度接进 DSH 同一个池:WorkBuddy 多账号自动容错、十三家扫码登录、76…
★ 359已汉化DSH
67.1