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 一律写「远程服务器」。
5.6 KiB
5.6 KiB
33 · 实例权限默认档位改为「完全权限」(复用官方 DSH_PERMISSION_MODE)
- 日期:2026-09-11
- 触发:档案 32 的 P0(dsh
workspace-write沙箱在本机不可用 → 拒绝执行任何 shell)+用户决策「方案 A」+追问「假如用户就是想授权 agent 执行、不想反复点确认,最佳方案是什么」+「可以复用官方的权限设置逻辑」 - 状态:✅ 已实施(commit 见 git),配置链路已核验;末段 UI 点击验收待人工
TL;DR|结论:实例权限默认档位改为**「完全权限」**(复用官方
DSH_PERMISSION_MODE),避免 agent 每步都弹确认。 关键:方案 A:复用官方权限设置链路,不改官方包;配置链路已核验。 状态:✅ 已实施(末段 UI 点击验收待人工)
一、方案选型
| 方案 | 做法 | 结论 |
|---|---|---|
| 裸改内置预设 | 直接用 danger-full-access 预设 |
❌ 该预设把 approval 绑成 never({sandbox:'danger-full-access', approval:'never'}),会静默摘掉全部确认 |
| 自造自定义预设 | profile cordis.patch.yml 给 permission-presets 行加 config.presets 新增一档 |
可行(已核实 patch 支持 config 覆盖),但要给每个用户铺 profile、且 config 是整体替换 |
| ✅ 采用:官方 env 开关 | DSH_PERMISSION_MODE=danger-full-access |
一条环境变量:官方 dsh-base/cordis.patch.yml:217/233 直接读它决定 sandbox mode 与 approval policy;新用户自动生效(env 随 spawn 注入,无需铺 profile);且完全复用官方逻辑(预设表 / 设置 UI 都不动) |
官方逻辑原文(@deepseek-ai/dsh-base/cordis.patch.yml):
mode: !!js process.env.DSH_PERMISSION_MODE ?? 'workspace-write'
policy: !!js "(process.env.DSH_PERMISSION_MODE ?? 'workspace-write') === 'danger-full-access' ? 'never' : 'ask'"
→ 设为 danger-full-access 时:沙箱模式 = 完全权限,审批策略 = never(一次都不弹确认,正是「授权一次、别反复点」要的效果)。
二、为什么这样是安全的(隔离边界上移)
| 层 | 谁提供 | 状态 |
|---|---|---|
| 内层 | dsh 自带沙箱(read-only/workspace-write,靠 Landlock/bubblewrap) |
本机不可用(内核 5.10 无 Landlock、bwrap 后端在平台容器内探测失败)→ 受限模式 fail-closed 拒绝执行 |
| 外层 | 平台 systemd-run --scope + bwrap + setpriv |
正常,真正的边界:mount 路径隔离(只暴露用户根)+ uid 隔离 + cgroup(512M/150%/128) |
源码依据:SANDBOX_UNAVAILABLE 的定义即「a requested confined mode when no backend is usable」——danger-full-access 不是受限模式,不需要后端;其报错文案本身也把 danger-full-access 列为官方逃生口径。→ 改档位不会引入新的暴露面,只是不再叠加一个坏的冗余层。
三、实现(2 处,均在本仓库自研编排器内,不碰官方程序)
| 文件 | 改动 |
|---|---|
src/supervisor/spawn.ts |
ALLOWED_ENV 增加 DSH_PERMISSION_MODE(否则被 scrubEnv 丢弃 —— 这是本项目的已知卡点) |
src/supervisor/orchestrator.ts |
baseEnv() 注入 DSH_PERMISSION_MODE: process.env.DSH_PERMISSION_MODE ?? 'danger-full-access'(运维可用同名 env 覆盖,改档位不必改码) |
npm run build 通过;systemctl restart dshs 后实例 respawn。
四、验证
| 项 | 结果 |
|---|---|
| 实例进程 env | ✅ pid=240941/240943/240944 全部 DSH_PERMISSION_MODE=danger-full-access |
| 是否存在用户级覆盖 | ✅ 无:admin 与 guest 的 settings.yaml 都没有 permission: 键;全盘 yaml/yml/json 除会话记录外无命中 → 配置派生的默认即生效 |
| 新用户是否自动生效 | ✅ 是(env 在 spawn 时注入,与 profile 无关) |
| 官方逻辑映射 | ✅ 源码核实(见上 yaml 片段) |
| 受限模式才需后端 | ✅ 源码核实(SANDBOX_UNAVAILABLE = confined mode 专用) |
| 末段 UI 实测 | ⚠️ 未完成:headless Chrome 能进实例、UI 显示访问模式,但「新建会话 / 发送消息」的脚本化点击未成功创建会话(dsh 会话在发首条消息时才落盘)。留给人工点击一次确认 |
五、重要副作用(必须知道)
权限档位是「会话级播种」的,不跟随后续默认变化。
会话创建时会写入 3 条种子事件(实测旧会话 session-00341dea):
permission/preset {preset:"workspace-write"}
sandbox/mode {mode:"workspace-write"}
approval/policy {policy:"ask"}
→ 已存在的旧会话仍显示「工作区内修改」(实例 UI 就是这个表现),只有新建的会话才拿到「完全权限」。 因此:给用户的话术应是「新开一个会话」,而不是「刷新页面」。
六、风险与后续
- 审批确认被关闭:
danger-full-access在官方语义下等价于approval: never。平台容器仍是硬边界,但失去了「危险操作人工确认」这层交互护栏。 - 未覆盖的残留风险(与本次改动无关,但关掉审批后更值得关注):
- 实例共享宿主网络(可连 3080/8888/32022 等宿主监听端口)
- 出网无白名单(档案 14 只封了云元数据 + 观测外联)
- 建议后续:① 补出网白名单(nftables);② 若要保留「危险操作确认」又想免去反复点击,可评估在 profile patch 里新增自定义预设(
sandbox: danger-full-access+approval: ask)——但那属于方案 A2,本次未采用。