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

多用户服务器门户 AnkoCD/dsh-server-deployment

DeepSeek Harness
AnkoCD70 天前有提交已提供中文文档扫描:含敏感能力

DeepSeek Harness 多用户服务器部署:登录门户 / 每用户独立实例与 OS 级隔离 / 独立 API…

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

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

自动兼容检查未通过:仓库根目录及浅层子目录均无 dsh 插件清单,无法用 dsh 插件安装命令安装。以下命令仍可尝试,但可能失败。
无法用 dsh 插件命令安装(仓库内无 dsh 插件清单)
git clone https://github.com/AnkoCD/dsh-server-deployment.git
按该仓库 README 的原生方式安装;若作者另行发布了 DSH 插件包,请以其文档为准

状态速览

判定依据:实装验证 · 静态安装检查 · 自动扫描 · 仓库维护状态
信任档位:需留意
是什么
dsh 原生插件 · model
装得上吗
作者未把包发布到 npm,只能用源码安装
插件本身没问题,是分发方式不同
安全吗
本站尚未对该插件做风险分级
暂未覆盖,不等同于无风险
还在维护吗
更新放缓:最近一次提交在 70 天前
档位由哪些信号合成

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

验证判据与等级说明 →
社区实装还没有人打卡 · 装过的第一个就是你
安装兼容性检查需源码安装

仓库根目录及浅层子目录均无 dsh 插件清单,无法用 dsh 插件安装命令安装

展开查看检查明细 ↓

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

✗npm 包dsh-server-deployment(未发布到 npm,仅可源码安装)
✓Node 引擎未声明 engines.node
✓dsh CLI 依赖未声明 dsh 版本约束
✗入口文件缺少入口声明

源码安装时若 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) {}
扫描于 2026/11/3(1 天前) · 范围 全包扫描 · 来源 GitHub 仓库

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

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

界面预览

1 张 · 点击查看大图

功能亮点

  • 多用户登录门户
  • 每用户独立实例
  • OS级隔离
展开完整介绍(115 字)↓

DeepSeek Harness 多用户服务器部署:登录门户 / 每用户独立实例与 OS 级隔离 / 独立 API Key / 交付文件抽屉;文件访问经 sudo 助手降权执行(修复 issue #1 TOCTOU)。非本机工具。

README

由 DeepSeek 最新模型翻译生成
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)均为服务器端路径。
展开完整文档(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
预览分享卡片长什么样 ↓AnkoCD/dsh-server-deployment
分享 · DeepSeek Harness Hub
AnkoCD/dsh-server-deployment

DeepSeek Harness 多用户服务器部署:登录门户 / 每用户独立实例与 OS 级隔离 / 独立 API…

这是你的仓库?认领条目

回到顶部