DeepSeek Harness 多用户服务器部署:登录门户 / 每用户独立实例与 OS 级隔离 / 独立 API…
数据截至 2026/9/17 · 周下载取自 npm 全网(含 CI / 镜像),非本站安装数;综合分由星标、活跃度与兼容性检查合成。
git clone https://github.com/AnkoCD/dsh-server-deployment.git状态速览
判定依据:实装验证 · 静态安装检查 · 自动扫描 · 仓库维护状态档位由哪些信号合成
本站实装验证(真实安装 L4 覆盖全站;L5 为运行时冒烟或实机截图,覆盖范围小于 L4) · 验证所用 dsh 版本 · 静态安装检查 · 风险分级 · 仓库维护状态。 下方各区块是它的证据明细。
安装兼容性检查需源码安装仓库根目录及浅层子目录均无 dsh 插件清单,无法用 dsh 插件安装命令安装
展开查看检查明细 ↓
以下结论由程序自动检查 npm 包、engines 声明与入口文件得出,未做人工实机验证——能装不等于用着没问题。
源码安装时若 pnpm 拦截 prepare 脚本,按其提示在 pnpm-workspace.yaml 的 allowBuilds 中放行后重跑。
验证方式:npm registry 存在性 + package.json 静态校验 · 最后检查 2026/11/2 06:34:08
安全扫描自动 · 静态分析源码v2.7.0🔵 扫描:含敏感能力尚无人工评估展开查看扫描说明与证据 ↓
静态扫描发现插件具备触达本地系统的能力:执行外部命令、读写/删除本地文件、读取剪贴板或密钥类环境变量(至少其一)。这是能力清单,不是危险判定——具体能力项与证据见下方,安装前请自行判断是否需要。
查看扫描证据(共 73 处,展示前 63 条)
- 读取本地文件bin/dsh-file-list.js:23
try { st = fs.statSync(target); } catch (e) { fail(4); } - 读取本地文件bin/dsh-file-list.js:27
entries = fs.readdirSync(target, { withFileTypes: true }) - 读取本地文件bin/dsh-file-list.js:33
try { const s = fs.statSync(p); size = s.size; mtime = Math.floor(s.mtimeMs); } catch (err) {} - 写入本地文件gateway/_smoke.js:11
fs.mkdirSync(path.join(USERS_DIR, 'tester'), { recursive: true }); - 写入本地文件gateway/_smoke.js:12
fs.mkdirSync(path.join(USERS_DIR, 'nokey'), { recursive: true }); - 写入本地文件gateway/_smoke.js:21
fs.writeFileSync(USERS_FILE, JSON.stringify(users)); - 读取密钥环境变量gateway/_smoke.js:26
process.env.SECRET_FILE = path.join(TMP, 'secret'); - 发起网络请求gateway/_smoke.js:58
const r = http.request({ host: '127.0.0.1', port: 3998, method: method, path: pathname, headers: headers }, (res) => { - 读取本地文件gateway/_smoke.js:121
const db = JSON.parse(fs.readFileSync(USERS_FILE, 'utf8')); - 读取密钥环境变量gateway/_unit.js:15
process.env.SECRET_FILE = path.join(TMP, 'secret'); - 读取本地文件gateway/_unit.js:85
const raw = fs.readFileSync(path.join(dir, 'cred', '.credentials.yaml'), 'utf8'); - 写入本地文件gateway/_unit.js:101
if (calls === 1) fs.writeFileSync(file, JSON.stringify({ version: 1, users: { bob: { flag: true } } })); - 写入本地文件gateway/_unit.js:112
fs.mkdirSync(path.join(ws, 'sub'), { recursive: true }); - 写入本地文件gateway/_unit.js:113
fs.writeFileSync(path.join(ws, 'b.txt'), 'hello'); - 写入本地文件gateway/_unit.js:114
fs.writeFileSync(path.join(ws, 'a.txt'), 'x'); - 写入本地文件gateway/_unit.js:115
fs.writeFileSync(path.join(ws, '.credentials.yaml'), 'secret'); - 执行外部命令来自配置/变量gateway/_unit.js:116
const run = (dir) => spawnSync(process.execPath, [path.join(__dirname, '..', 'bin', 'dsh-file-list.js'), home, dir || ''], { encoding: 'utf8' }); - 执行外部命令固定命令gateway/auth.js:27
const r = spawnSync('openssl', ['passwd', '-apr1', '-salt', salt], { - 读取本地文件gateway/credentials.js:11
const raw = fs.readFileSync(credentialsPath(usersDir, user), 'utf8'); - 写入本地文件gateway/credentials.js:24
fs.mkdirSync(dir, { recursive: true, mode: 0o700 }); - 读取本地文件gateway/credentials.js:26
try { lines = fs.readFileSync(file, 'utf8').split(/ ? /); } catch (e) {} - 写入本地文件gateway/credentials.js:39
fs.writeFileSync(file, out.join('\n') + '\n', { mode: 0o600 }); - 写入本地文件gateway/credentials.js:40
try { fs.chmodSync(file, 0o600); fs.chmodSync(dir, 0o700); } catch (e) {} - 读取密钥环境变量gateway/server.js:21
const SECRET_FILE = process.env.SECRET_FILE || '/opt/deepseek-harness/gateway/secret'; - 读取本地文件gateway/server.js:61
const s = fs.readFileSync(SECRET_FILE, 'utf8').trim(); - 写入本地文件gateway/server.js:66
fs.mkdirSync(path.dirname(SECRET_FILE), { recursive: true, mode: 0o700 }); - 写入本地文件gateway/server.js:67
fs.writeFileSync(SECRET_FILE, s + '\n', { mode: 0o600 }); - 读取本地文件gateway/server.js:84
usersCache = JSON.parse(fs.readFileSync(USERS_FILE, 'utf8')); - 发起网络请求gateway/server.js:142
const req = http.request({ - 发起网络请求gateway/server.js:323
const req = https.request(opts, (res) => { - 执行外部命令固定命令gateway/server.js:464
child = spawn('sudo', ['-n'].concat(args), { stdio: ['ignore', 'pipe', 'pipe'] }); - 读取本地文件gateway/server.js:621
const loaded = JSON.parse(fs.readFileSync(CWD_STATE_FILE, 'utf8')); - 写入本地文件gateway/server.js:634
try { fs.writeFileSync(CWD_STATE_FILE, JSON.stringify(out) + '\n', { mode: 0o600 }); } catch (e) {} - 发起网络请求gateway/server.js:722
const upstreamReq = http.request({ - 读取本地文件gateway/server.js:951
const data = fs.readFileSync(asset.file); - 执行外部命令固定命令gateway/server.js:1191
child = spawn('sudo', ['-n', UPLOAD_HELPER, home, dirAbs, name], { stdio: ['pipe', 'ignore', 'pipe'] }); - 执行外部命令固定命令gateway/server.js:1290
const child = spawn('sudo', ['-n', FILE_READ_HELPER, home, abs], { stdio: ['ignore', 'pipe', 'ignore'] }); - 写入本地文件gateway/server.js:1460
try { fs.writeFileSync(PORT_STATE_FILE, JSON.stringify({ port: PORT, at: Date.now() }) + '\n', { mode: 0o600 }); } - 读取本地文件gateway/store.js:14
try { return JSON.parse(fs.readFileSync(file, 'utf8')); } - 读取本地文件gateway/store.js:19
try { return fs.statSync(file).mtimeMs; } catch (e) { return -1; } - 写入本地文件gateway/store.js:37
try { fs.mkdirSync(path.dirname(file), { recursive: true, mode: 0o700 }); } catch (e) {} - 写入本地文件gateway/store.js:38
fs.writeFileSync(tmp, JSON.stringify(db, null, 2) + '\n', { mode: 0o640 }); - 执行外部命令固定命令gateway/store.js:39
if (o.chownOwner) { try { execFileSync('chown', [o.chownOwner + ':' + o.chownOwner, tmp]); } catch (e) {} } - 删除本地文件gateway/store.js:40
if (mtimeOf(file) !== m0) { try { fs.unlinkSync(tmp); } catch (e) {} continue; } - 写入本地文件gateway/store.js:41
fs.renameSync(tmp, file); - 删除本地文件gateway/store.js:44
try { fs.unlinkSync(tmp); } catch (e2) {} - 执行外部命令固定命令gateway/userctl.js:36
try { execFileSync('id', [name], { stdio: 'ignore' }); return true; } catch (e) { return false; } - 执行外部命令固定命令gateway/userctl.js:41
execFileSync('useradd', ['--system', '--no-create-home', '--home', homeOf(name), '--shell', '/usr/sbin/nologin', osu]); - 执行外部命令固定命令gateway/userctl.js:46
try { execFileSync('userdel', [osu]); } catch (e) {} - 读取本地文件gateway/userctl.js:91
if (fs.existsSync(file)) return; - 写入本地文件gateway/userctl.js:92
fs.writeFileSync(file, SEARCH_PATCH, { mode: 0o644 }); - 写入本地文件gateway/userctl.js:100
try { fs.mkdirSync(USERS_DIR, { recursive: true }); fs.chmodSync(USERS_DIR, 0o711); } catch (e) {} - 写入本地文件gateway/userctl.js:101
fs.mkdirSync(path.join(home, 'workspace'), { recursive: true, mode: 0o700 }); - 读取本地文件gateway/userctl.js:103
if (!fs.existsSync(settings)) { - 读取本地文件gateway/userctl.js:104
if (fs.existsSync(SETTINGS_SRC)) fs.copyFileSync(SETTINGS_SRC, settings); - 写入本地文件gateway/userctl.js:107
fs.writeFileSync(settings, def); - 执行外部命令固定命令gateway/userctl.js:112
execFileSync('chown', ['-R', osu + ':' + osu, home]); - 写入本地文件gateway/userctl.js:113
try { fs.chmodSync(home, 0o700); } catch (e) {} - 执行外部命令固定命令gateway/userctl.js:123
try { execFileSync('setfacl', ['-b', '-R', home]); } catch (e) {} - 写入本地文件gateway/userctl.js:175
fs.writeFileSync(file, unitFor(name, port), { mode: 0o644 }); - 执行外部命令固定命令gateway/userctl.js:176
execFileSync('systemctl', ['daemon-reload']); - 删除本地文件gateway/userctl.js:182
try { fs.unlinkSync('/etc/systemd/system/' + unit); } catch (e) {} - 删除本地文件gateway/userctl.js:334
try { fs.rmSync(homeOf(user), { recursive: true, force: true }); } catch (e) {}
静态扫描只反映源码中出现的调用,不构成安全背书;人工评估与自动扫描相互独立、互不覆盖。
给这个插件打分还没有人投票↓
界面预览
1 张 · 点击查看大图功能亮点
- 多用户登录门户
- 每用户独立实例
- OS级隔离
展开完整介绍(115 字)↓
DeepSeek Harness 多用户服务器部署:登录门户 / 每用户独立实例与 OS 级隔离 / 独立 API Key / 交付文件抽屉;文件访问经 sudo 助手降权执行(修复 issue #1 TOCTOU)。非本机工具。
README
由 DeepSeek 最新模型翻译生成展开完整文档(README 剩余 1 节)
DeepSeek Harness 服务器部署(dsh-server-deployment)
给 DeepSeek Harness(DSH)Web 端加一层多用户门户的零依赖 Node 网关:登录认证、每用户独立 DSH 实例与 OS 级数据隔离、每用户独立 API Key,还有内置的交付文件抽屉(下载 / 上传 / 当前工作区自动定位)。隔离以 OS 账号为边界,而不是靠网关代码——文件访问经 sudo 助手 runuser 降权为 dsh-<name> 用户执行(修复 issue #1 TOCTOU 竞态),网关对用户目录零权限;完整机制见 docs/multi-user-isolation.md。
部署定位(重要):本项目是服务器端部署方案——网关、每用户 DSH 实例与文件助手全部跑在远程服务器上,多个用户通过浏览器(公网域名 + HTTPS)访问各自的会话与交付文件;它不是本机 / 桌面工具,无需在用户电脑上安装任何软件。文档中的示例路径(如 /opt/deepseek-harness、/etc/systemd/system)均为服务器端路径。
特性
- 登录门户:自研暗色登录页(漆面 + 金箔风格),scrypt 口令(兼容旧版 APR1)、HMAC 签名会话 Cookie(HttpOnly / Secure / SameSite=Lax)、登录限流(IP + 账号两级)、CSRF 双提交校验。
- 用户隔离:每个用户一个独立 DSH 实例(独立端口),以独立系统账号 dsh-<name> 运行,DSH_HOME 指向其 0700 私有目录;userctl.js 一条命令完成建号 / 改密 / 删号 / 预置 Key。
- 每用户独立 API Key:登录后无 Key 自动引导 /setup 填写,经回环 RPC 写入该用户私有的 .credentials.yaml(0600,属主仅本人)。
- 回环特权接口修复:网关向后端呈现 Host: 127.0.0.1:<port> 并剥离浏览器信任标记,DSH 钉在回环的 settings / credentials / agentPreset 等特权接口在公网访问下同样可用。
- 交付文件抽屉(文件管理):主界面右下角一颗可拖动胶囊「🗂 文件管理」(2026-08 起由「交付文件」+「上传文件」双胶囊合并而来),白色抽屉内嵌文件浏览器——目录浏览、下载(attachment + 中文文件名)、多文件上传(100MB 上限);自动定位到当前对话所在工作目录(嗅探会话 RPC 追踪 cwd,持久化恢复)。胶囊在 SPA 弹窗(设置面板/模态)打开时自动隐藏,避免遮挡。
- 安全边界:所有用户文件访问经 sudoers 固定路径的助手脚本——root 仅校验参数并降权,文件操作以 dsh-<name> 用户自身身份执行(修复 issue #1 的 TOCTOU 竞态);网关进程对用户目录零权限;隐藏文件(含 .credentials.yaml)不可下载;SPA 注入尊重 prefers-reduced-motion、无玻璃拟态/渐变装饰。
架构
浏览器 ──https──▶ 反向代理(TLS, 例: OpenResty) ──▶ dsh-gateway(:3100) ──▶ 每用户 DSH 实例(:3101+) │ │ │ 会话/限流/CSRF/路由 │ 以独立 OS 账号 dsh-<name> 运行 │ Key 引导/抽屉注入 │ DSH_HOME=<用户私有目录 0700> └──────────┬──────────┘ └─ 文件访问走 sudo 助手: dsh-file-{list,stat,read,put}
网关默认端口为 3100(server.js 默认值、systemd 单元与 nginx 示例已统一;可用环境变量 PORT 覆盖,覆盖后需同步反代配置——回环防护会自动读取网关启动时写入的 state-port.json,无需手动改 GW_PORT);每用户实例自 3101 递增,由 userctl 分配。
多用户与数据隔离的完整说明见 docs/multi-user-isolation.md。
目录结构
gateway/ # 网关本体(零依赖 Node) server.js # 登录/会话/限流/CSRF/反代/SPA注入/文件抽屉/上传下载接口 auth.js # scrypt + APR1 口令校验 credentials.js # .credentials.yaml 读写(仅 userctl 使用) store.js # users.json 乐观并发读改写(网关与 userctl 共用) userctl.js # 用户管理:OS 账号/端口/实例/Key _smoke.js # 网关冒烟测试(本地即可运行,无需 DSH) _unit.js # 纯逻辑单元测试(node gateway/_unit.js,无需 root) static/ # 登录前可访问的静态资源(manifest/favicon) bin/ # 主机端入口与 root 助手 dsh-users.sh # userctl 的 sudo 入口
dsh-file-{list,stat,read,put}[.js]
units/ # systemd 单元模板(网关 + 每用户实例由 userctl 生成) nginx/ # TLS 反向代理示例配置(已占位化域名)
快速部署(概览)
1. 安装 DSH(npm 包)并准备 Node 运行时;按 units/ 配置网关 systemd 服务(User=<服务账号>,仅监听 127.0.0.1)。
2. 用 sudo bin/dsh-users.sh add <用户> 建号(自动创建 OS 账号、分配端口、生成并启动实例)。
3. 安装 root 助手并配置 sudoers(固定路径白名单):
bash
install -o root -g root -m 0755 bin/dsh-file-* /opt/deepseek-harness/bin/
/etc/sudoers.d/dsh-upload:
<服务账号> ALL=(root) NOPASSWD: /opt/deepseek-harness/bin/dsh-file-put, /opt/deepseek-harness/bin/dsh-file-stat, /opt/deepseek-harness/bin/dsh-file-read, /opt/deepseek-harness/bin/dsh-file-list
仅在调大上传上限时需要(助手默认 110000000 字节 ≈ 105MB):
Defaults!/opt/deepseek-harness/bin/dsh-file-put env_keep += "DSH_UPLOAD_MAX_BYTES"
升级或自检时可在服务器上按以下清单验证助手(把 <user> 换成真实用户名):
bash
H=/opt/deepseek-harness/users/<user>
sudo -n /opt/deepseek-harness/bin/dsh-file-list "$H" '' # JSON 目录列表
printf 'BYTES 6\nhello\n' | sudo -n /opt/deepseek-harness/bin/dsh-file-put "$H" "$H/workspace" t.txt # v2 长度协议;短流 exit=6 不落盘
sudo -n /opt/deepseek-harness/bin/dsh-file-stat "$H" "$H/workspace/t.txt" # 输出 6
sudo -n /opt/deepseek-harness/bin/dsh-file-read "$H" "$H/workspace/t.txt" # 输出 hello
sudo -n /opt/deepseek-harness/bin/dsh-file-read "$H" /etc/passwd; echo "exit=$?" # exit=3(越界拒绝)
ps -ef | grep -E 'runuser.dsh-' # 子进程应为 dsh-<user> 而非 root
4. 按 nginx/dsh-https-1145.conf 配置 TLS 反向代理(替换 server_name 为你的域名并挂证书)。
5. 登录后首次使用会引导填写 DeepSeek API Key(仅写入用户私有目录)。
环境变量:网关与 userctl 均可用环境变量覆盖默认的 /opt/deepseek-harness 路径:
| 变量 | 作用方 | 默认 |
| DSH_BASE_DIR | userctl / dsh-users.sh 派生路径的安装前缀 | /opt/deepseek-harness |
| DSH_USERS_DIR、DSH_USERS_FILE、DSH_SETTINGS_SRC、DSH_NODE_BIN、DSH_DSH_BIN | userctl 细粒度覆盖 | 由 BASE_DIR 派生 |
| USERS_FILE、SECRET_FILE、USERS_DIR | 网关 | /opt/deepseek-harness/... |
| UPLOAD_HELPER、FILE_STAT_HELPER、FILE_READ_HELPER、FILE_LIST_HELPER | 网关调用助手的绝对路径 | /opt/deepseek-harness/bin/dsh-file-(自定义前缀时必须同步改 sudoers 与这四个变量) |
| HOST、PORT(默认 3100)、SESSION_TTL、COOKIE_SECURE、DEEPSEEK_BASE_URL、UPLOAD_MAX_MB、MAX_IP_ATTEMPTS、MAX_USER_ATTEMPTS、WINDOW_MS、LOCK_MS、SNIFF_BUFFER_CONCURRENCY(并发缓冲的 history 响应上限,默认 4) | 网关 | 见 gateway/server.js |
| DSH_UPLOAD_MAX_BYTES | dsh-file-put(root 助手,经 sudoers env_keep 传入) | 110000000(调大 UPLOAD_MAX_MB 时需同步) |
| DSH_TRUSTED_HOST | userctl(实例 --trusted-host) | 127.0.0.1:1145 |
bin/dsh-users.sh 与 bin/dsh-file-list 已按自身位置自定位:任意目录检出即可直接运行(dsh-users.sh 首次调用自动重提权为 root;node 解析相对脚本位置,缺失时回退 PATH)。自定义安装前缀时 systemd 单元用上面的 sed 命令生成;网关 systemd 单元还支持 EnvironmentFile=-/etc/default/dsh-gateway,可在该文件里统一注入上述环境变量。
交付文件抽屉的行为细节
- 自动定位:网关嗅探代理流量中的 session.history(打开会话)与 session.list(每会话含 cwd),记住当前对话目录并持久化到 state-cwd.json;打开「文件管理」即列出该目录(目录失效自动回退工作区)。
- 嵌入与关闭:抽屉以同源 iframe 内嵌(X-Frame-Options: SAMEORIGIN);页面内「返回应用」运行时检测 iframe 环境,发 postMessage('dshgw-close') 关闭抽屉而非导航,杜绝嵌套打开。
- 上传:原始字节体 POST /__gw/upload?dir=&name=,助手降权为 dsh-<name> 落盘(root 仅校验参数),文件属主天然为本人,同名覆盖,超限 413。
安全注意事项
- 安装树完整性(最关键):/opt/deepseek-harness 整棵树——含 DSH monorepo 源码(packages/、apps/、node_modules/)与 .agents/ 技能库——不得带 group/other 写位。所有租户实例共享执行这份代码,任何可写点都是跨租户注入点(改共享代码或技能文件 → 以其他租户身份执行 → 窃取其 API Key)。部署/升级后必须自检:find /opt/deepseek-harness -not -path '/users' -perm /022 | wc -l 输出 0(users/ 用户目录除外)。
- gateway/ 目录保持 root:dsh-gateway 0770:网关需要在其内做 tmp+rename 原子写(users.json / secret / state-cwd.json);目录内代码文件(server/userctl/auth/credentials/static)归 root:root 0644。bin/ 全部归 root:root。
- 网关以专用系统账号 dsh-gateway(无 shell)运行,切勿用 ubuntu 等自带 NOPASSWD sudo 的云镜像账号运行网关;其 sudo 能力仅限 /etc/sudoers.d/dsh-upload 白名单中的四个文件助手。网关 systemd 单元不可设置 NoNewPrivileges=yes(会阻断 sudo 调 root 助手,上传/下载/列表全部失效)。
- 回环租户隔离(bin/dsh-loopback-guard + units/dsh-loopback-guard.service):DSH 实例的特权接口按「Host 头是回环」放行,而所有实例同处 127.0.0.1--任何租户的智能体都能伪造 Host 直连他人端口窃取 API Key。防护为 iptables OUTPUT 链:每个 dsh-<name> 只能连自己的实例端口,其他租户端口与网关端口被 REJECT,root/网关账号不受影响。网关端口解析顺序为 GW_PORT 环境变量 > 网关启动时写入的 state-port.json > 默认 3100。userctl 增删用户自动刷新规则(经 iptables-restore --noflush 单次提交,刷新期间无防护空窗);规则按目标端口逐条 REJECT(不可按 uid 全量拒绝,否则会掐断内核回包路径)。
- 每用户实例带 systemd 资源限制(TasksMax/MemoryMax/CPUQuota,可经 DSH_MEM_MAX/DSH_CPU_QUOTA 调整)与内核加固项。
- 文件助手(dsh-file-put/read/stat/list)root 仅做参数字符串校验与身份切换,所有文件操作经 runuser -u dsh-<name> 以用户自身身份执行(修复 issue #1 的 TOCTOU 竞态);助手内的 realpath 前缀校验仅保留退出码语义,不再是安全边界。依赖 util-linux 的 runuser。上传助手为 v2 长度协议(BYTES <n> 头 + 精确字节校验):网关流式转发请求体,中断/超时/超限的上传会以 exit 6 拒绝提交,不会留下截断文件。
- 用户凭据文件必须保持仅属主可读(0600):DSH 启动时会强制检查(assertOwnerOnly)。本网关的 root 助手模型天然满足,不要给用户目录添加任何 ACL 读取授权(曾因此触发实例拒绝启动)。
- 网关与所有实例仅监听 127.0.0.1,公网只暴露 TLS 反代;反代必须覆盖(非追加)X-Forwarded-For 为 $remote_addr(nginx/dsh-https-1145.conf 模板已按此安全默认值配置),否则攻击者可伪造 XFF 绕过网关 IP 限流。
- 登录限流为 IP + 账号两级;账号锁定(5 次/15 分钟)本身可被滥用作 DoS,仅靠 IP 级限流与强密码缓解。改密会使 pwdVer 递增,所有已签发会话立即失效(含无版本号的旧令牌)。
- /logout 仅接受 POST(带 CSRF 双提交校验),防跨站登出。
- 升级 DSH 后如需客户端行为修补(如 settings 持久化作用域),请自行评估,本仓库不修改 npm 包。
- 使用时微软的密码自动填充会给左侧工作区目录带来问题,尽量关闭自动填充。
License
MIT预览分享卡片长什么样 ↓
DeepSeek Harness 多用户服务器部署:登录门户 / 每用户独立实例与 OS 级隔离 / 独立 API…
相关插件 · 模型接入
这是你的仓库?认领条目