chore(docs): 文档库并入代码仓(R4 选 a)+ 索引/台账跟进
1) dsh-server-docs/ 从工作区(原 E:\...\aliyun-dsh-server\dsh-server-docs)**整体并入本仓**,
保留目录名 ⇒ 仓库内 dsh-server-docs/... 的相对引用天然继续有效;旧目录(含其 .git)已归档到
工作区 _中间产物_待清理/,未随本提交带入。
2) .gitattributes:新增 `dsh-server-docs/** -text` —— 原文档库是 `* -text` + autocrlf=false,
必须保持纯 LF,否则会被本仓的 CRLF 规则翻掉。
3) 活引用里的绝对路径已全部改到新位置(docs 的 INDEX / README / scripts / skills + 用户级 skills
+ ~/.workbuddy/settings.json 的 hooks);历史档案(04-调整方案/、archive/)按「只增不改」未动。
⚠️ hooks 路径改动需「完全重启会话」才生效(配置是会话启动快照)。
4) 交接单/T08:新增 §16「生产整体切换执行记录」(形态 / 落地动作 / **4 个只有真上线才暴露的真 bug** /
验收证据 / 回滚命令 / 残留项);台账 T08 行 → 已完成并归档;03-路线图 §二 登记 T08 收尾项。
5) 统一称谓:**「本机」只指跑 WorkBuddy 的开发机**,47 / 106 一律写「远程服务器」。
This commit is contained in:
1 parent
c70d5d860e
commit
5ad755116e
173 files changed
+27632
No files matched your search
@@ -0,0 +1,90 @@
|
||||
# 17 · 工作区选择器暴露面核查("选择工作区目录"为何能看到 `/`)
|
||||
|
||||
- 日期:2026-09-10
|
||||
- 触发:用户以 admin 登录 dsh 会话后点「添加工作区」→ 目录选择器显示 `/`(`dev`/`etc`/`lib64`/`proc`/`tmp`/`usr`/`var`),询问是否有问题
|
||||
- 结论一句话:**不是越权漏洞**(跨租户与系统目录的读/写均被拦住),但确有两个真问题 —— ① 工作区选择器**无根目录白名单**;② 沙箱写边界 = **会话 cwd**,用户把自家目录加为工作区后即可写自家 profile(含平台管理的 `cordis.patch.yml`)。次要项:`/etc/nftables-dsh-egress.nft` 为 644 可读。
|
||||
- 状态:核查完成 → 收敛项 H1-H4 待确认后执行
|
||||
|
||||
> **TL;DR**|**结论**:「添加工作区」为何能看到 `/`:**不是越权漏洞**(跨租户与系统目录读/写均被拦),但确有两个真问题。
|
||||
> **关键**:① picker **无根目录白名单**;② 沙箱写边界 = **会话 cwd**(用户把自家目录加为工作区后可改平台策略文件)→ 分别由档案 18 与写保护 B1 处置。
|
||||
> **状态**:🔍 核查完成(结论已落地)
|
||||
---
|
||||
|
||||
## 一、现象与机制还原(源码 + 进程实测)
|
||||
|
||||
截图里的 7 项**不是宿主根**,而是 bwrap **合成根**。实际 spawn 命令行(`pgrep -f "dsh --profile web"` 实测):
|
||||
|
||||
```
|
||||
bwrap --ro-bind /usr /usr --ro-bind /lib64 /lib64 --ro-bind /etc /etc
|
||||
--dev /dev --proc /proc
|
||||
--bind <userRoot>/tmp /tmp
|
||||
--bind <userRoot> <userRoot>
|
||||
--unshare-pid --chdir <userRoot>/ws
|
||||
-- setpriv --reuid 114801 --regid 114801 --clear-groups \
|
||||
dsh --profile web --host 127.0.0.1 --port 36699
|
||||
```
|
||||
|
||||
bwrap 新建 mount namespace 并从**空根**合成:只 bind 上述路径,`--unshare-pid` 隔离进程视角。`/var` 等中间目录是为 bind 目标自动补的父目录 → 因此 `/` 下只见 7 项(`dev etc lib64 proc tmp usr var`),**不含宿主的 `/opt` `/root` `/home` `/www`**。截图与实测完全一致 ⇒ 没有额外泄漏。
|
||||
|
||||
> ⚠️ 一个诊断陷阱:`pgrep -f "dsh --profile web"` 会先命中 bwrap 包装进程(root),在其 namespace 内以 uid 0 探测会得出"全部可读写"的**假象**。必须取 `ps -eo user,args` 中 user 为 `dsh-*` 的那个子进程。
|
||||
|
||||
## 二、实测结果
|
||||
|
||||
以实例 uid `114801`(= admin `cce6d1cd-…`)的 mount/pid namespace 只读探测:
|
||||
|
||||
| 类别 | 项 | 结果 |
|
||||
|---|---|---|
|
||||
| **敏感文件可读** | `/etc/passwd` | ⚠️ **READABLE** —— 泄露 `admin`、`www`、`dsh-cce6d1cdb376430480f0`、**`dsh-eeccbc638afc46bdb663`(另一租户账号名,含 UUID 前 20 位)** 及各自 home 路径 |
|
||||
| | `/etc/shadow` | ✅ 不可读 |
|
||||
| | `/etc/dshs.env` | ✅ 不可读(600 有效) |
|
||||
| | `/etc/nginx/nginx.conf`、`/etc/ssh/sshd_config` | ✅ 不可读 |
|
||||
| | `/etc/nftables-dsh-egress.nft` | ⚠️ **可读(644)** —— 泄露出网护栏策略(无凭据) |
|
||||
| **写权限**(`test -w`) | `/`、`/etc`、`/usr`、`/var`、`/opt` | ✅ 全 ro(bwrap ro-bind 硬保护,内核级) |
|
||||
| | `/tmp` | ✅ 可写,但为**每用户私有**(`<userRoot>/tmp` bind;`dsh-spill-*`、`node-addon-*` 均属本 uid) |
|
||||
| | `<userRoot>`(自家根,rw bind) | ⚠️ **可写** —— 含 `home/profiles/web/*`、`patches/`、`settings.yaml` |
|
||||
| **跨租户** | `/opt`、`/opt/dsh`、`/opt/dshs`、`/root`、`/home`、`/home/admin` | ✅ 全部 denied |
|
||||
| | `/var/lib/dshs/users/*` 他人目录 | ✅ 不可见(700 + 非本人) |
|
||||
| **进程视角** | `/proc` | ✅ 仅见自身(`--unshare-pid`) |
|
||||
|
||||
## 三、风险判定
|
||||
|
||||
| 级别 | 问题 | 说明 |
|
||||
|---|---|---|
|
||||
| **P1** | **工作区写边界可被用户自行扩大** | `dsh-sandbox-policy` 的写边界 = **会话 cwd**;用户用「添加工作区」把 `<userRoot>` 或 `home/profiles/web` 加为工作区后,agent 即可写自家 profile:改 `cordis.patch.yml` → **绕过 `ensure-role-profile-patch.cjs` 的角色裁剪**(普通用户可自行恢复「模型设置」分区)、改 `settings.yaml` / patch 集合 → 破坏平台对 profile 的管理语义。属**租户内自我提权/策略绕过**,不跨租户 |
|
||||
| **P2** | 工作区选择器无根白名单 | 上游 `dsh-host-directory-picker-browse` 配置**只有 `maxEntries`(默认 1000),无根目录字段** → 用户可浏览并选择 `/usr`、`/etc`、`/tmp`、自家根。因 `/usr`/`/etc` 为 ro-bind 故**写不进去**,但会扩大**只读**暴露面(读 `/etc/passwd`、`/usr` 下 dsh 源码与 npm 包) |
|
||||
| **P2** | `/etc/passwd` 泄露另一租户账号名 | 账号名含对端 UUID 前 20 hex;home 路径一并泄露(`/home` 亦曾可列,见档案 14)。不足以直接越权(目录 700),但属租户 ID 泄露,配合门户 `/u/<id>/dsh/` 路由可做探测 |
|
||||
| **P3** | `nftables-dsh-egress.nft` 644 可读 | 仅泄露"封了元数据端点 + 观测外联"这一策略事实,无凭据 |
|
||||
|
||||
**结论**:隔离本体有效(跨租户 + 系统目录读写均被拦),截图不是漏洞;真正要收敛的是 **P1(写边界)+ P2(选择器范围 / passwd)**。
|
||||
|
||||
## 四、根因(上游机制,非配置失误)
|
||||
|
||||
1. **picker 无白名单**:`@deepseek-ai/[email protected]` 的配置契约仅 `maxEntries`;两个原语 `directoryPicker/list` / `createDirectory` 直接"从宿主文件系统作答",无 root 约束。
|
||||
2. **写边界 = 会话 cwd**:`@deepseek-ai/dsh-sandbox-policy` 的 `Config` 仅 `mode` + `workspaceRoot`,而 `workspaceRoot` 的 JSDoc 明确为"**无 cwd 的会话 / 无 agent 调用的 fallback**;正常 agent 调用使用其**会话 cwd**"。⇒ **把 `workspaceRoot` 钉死不能解决**(只影响 fallback),必须从 **bwrap 层**或 **picker 层**收敛。
|
||||
3. 本部署生效值:`mode: workspace-write`(`--dump-config` 实测:`process.env.DSH_PERMISSION_MODE ?? 'workspace-write'`,环境未设该变量),`approval: ask`。
|
||||
|
||||
## 五、收敛建议
|
||||
|
||||
| # | 措施 | 收益 | 代价 / 风险 | 建议 |
|
||||
|---|---|---|---|---|
|
||||
| **H1** | `chmod 600 /etc/nftables-dsh-egress.nft` | 消除 P3 策略泄露 | 极低(1 行,可逆) | ✅ **立即执行** |
|
||||
| **H2** | **bwrap 精修**:对平台管理物加 `--ro-bind` **覆盖**(bwrap 后 bind 覆盖先 bind)——如 `--ro-bind <userRoot>/patches <userRoot>/patches`、角色 patch 文件 | 即使 cwd 落在自家目录内,平台管理文件也**写不动**(内核级) | 中:需先验证 dsh 是否会重写这些文件(若 `ensure-role-profile-patch.cjs` 在 spawn 前由 root 重写,则 ro 覆盖可行;若需实例自身写则需改为"启动期 `--patch` 注入角色补丁"替代 profile 文件) | ✅ 推荐,需先做一次写行为验证 |
|
||||
| **H3** | **picker 白名单**:自建 host 插件覆盖 `directoryPicker/list` seam,根固定为 `<userRoot>/ws` | 从源头消除 P2(连读也不给) | 中高:新增 host 插件(参考 `poc/business-plugins` 先例);需验证 seam 覆盖优先级 | 🔄 v2 评估 |
|
||||
| **H4** | `/etc/passwd` 账号名**去 UUID 化**(uid 分配改顺序号,账号名不含用户 ID) | 收敛 P2 租户 ID 泄露 | 中:改动 uid 分配 + 存量账号迁移 | 🔄 可选,视租户规模 |
|
||||
| **H5** | 若接受现状:把"工作区选择器可见沙箱根 + 写边界随会话 cwd"记为**已知边界**(同档案 14 风格) | 0 成本 | — | 兜底选项 |
|
||||
|
||||
## 六、回滚
|
||||
|
||||
| 项 | 回滚 |
|
||||
|---|---|
|
||||
| H1 | `chmod 644 /etc/nftables-dsh-egress.nft` |
|
||||
| H2 | 移除 orchestrator 中新增的 `--ro-bind` 参数并重启实例 |
|
||||
| H3 | 卸载自建插件(profile bundles 回退) |
|
||||
| H4 | 恢复原 uid 分配逻辑(存量账号不受影响) |
|
||||
|
||||
## 七、红线遵守
|
||||
|
||||
- R1:不触发 dsh 升级;所有建议基于当前 `0.1.2-rc.1` 既有机制。
|
||||
- R2:**不改官方 dsh 主程序与缓存** —— H2 改的是编排器 spawn 参数,H3 走 profile 层插件,均不触碰 `@deepseek-ai/dsh`。
|
||||
- R3:本核查**全程只读**(仅 `test -r`/`test -w`/`ls -ld`,未创建或修改任何文件)。
|
||||
- 截图与结论同源可复现;诊断脚本须避开 bwrap 包装进程(见 §一 陷阱)。
|
||||
Reference in new issue
Block a user