Files
dsh_shenxian/dsh-server-docs/04-调整方案/33-实例权限默认档位改为完全权限.md
T

73 lines
5.6 KiB
Markdown
Raw Normal View History

# 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`):
```yaml
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 就是这个表现),只有**新建的会话**才拿到「完全权限」。
因此:给用户的话术应是「**新开一个会话**」,而不是「刷新页面」。
## 六、风险与后续
1. **审批确认被关闭**:`danger-full-access` 在官方语义下等价于 `approval: never`。平台容器仍是硬边界,但失去了「危险操作人工确认」这层交互护栏。
2. **未覆盖的残留风险**(与本次改动无关,但关掉审批后更值得关注):
- 实例**共享宿主网络**(可连 3080/8888/32022 等宿主监听端口)
- **出网无白名单**(档案 14 只封了云元数据 + 观测外联)
3. **建议后续**:① 补出网白名单(nftables);② 若要保留「危险操作确认」又想免去反复点击,可评估在 profile patch 里新增自定义预设(`sandbox: danger-full-access` + `approval: ask`)——但那属于方案 A2,本次未采用。