Files
dsh_shenxian/dsh-server-docs/04-调整方案/37a-guest会话取证与平台缺陷清单.md
T
admin 5ad755116e 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 一律写「远程服务器」。
2026-09-15 18:47:13 +08:00

168 lines
15 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 37a · guest 会话取证与平台缺陷清单(MCN短视频创作)
- 日期:2026-09-11
- 触发:用户要求「看看 guest 用户的 MCN短视频创作 工作区的对话记录,能发现哪些问题」
- 状态:**🔍 取证完成(本档只记录缺陷,不含代码改动)**
- 关联:档案 32(同一方法,mcntimo 工作区)、档案 33(沙箱默认档位已改)、档案 21(guest 卡顿排查,服务端侧)、档案 23(实例内能力限制核查)
> **TL;DR**|**结论**:从 guest 的「MCN短视频创作」会话反查平台缺陷清单(**只记录缺陷,不含代码改动**)。
> **关键**:含 P0/P1 分级与证据;方法可复用于其他工作区(同档案 32)。
> **状态**:🔍 取证完成
---
## 一、取证范围
| 项 | 值 |
|---|---|
| 用户 | `guest` = `4092b965-2f68-4977-9989-68b3966f7df0`(uid 100002,`dsh-eeccbc638afc46bdb663`)|
| 工作区 | `ws/MCN短视频创作`(workspace id `7f9fd72c-1cf3-49f3-bb72-ba3de28a16fe`)|
| 主会话 | `session-bd54c7cc-7620-4cf3-9bff-9084bcc5ae88`,**4190 行 / 2.8 MB 明文 / 1.3 MB zstd** |
| 副会话 | `session-b83ae69b-...`,5 行(空会话,见 P2-3)|
| 时间跨度 | 2026-09-10 12:12 → 09-11 14:52(CST)|
| 规模 | 12 turns / 106 steps / **111 tool calls**(bash 100、web_search 2、web_fetch 3、write 3、edit 3)|
| 任务 | 单一诉求:**获取抖音热门短视频榜单**,以及围绕它的能力追问 |
**产出物(工作区,agent 手写脚本抓取)**:
`scripts/douyin_hot.py`、`scripts/douyin_hot_videos.py`、`scripts/probe_shape.py`;
`data/douyin_hot_2026-09-10_2148.{md,json}`(97 行)、`data/douyin_hot_2026-09-11_1447.{md,json}`(52 行)、`data/douyin_videos_2026-09-10_2154.{md,json}`(75 行)。
> 产出**质量本身可用**(含话题名/热度/播放量/最高排名/首次出现时间/搜索链接)。问题不在产出,在下述**代价、路径与平台缺口**。
---
## 二、新增 P0
### P0-1 · 存量会话不会被迁移到新档位 —— 全平台 10/10 会话仍卡在 `workspace-write`
档案 33 把**新会话**默认档位改为 `danger-full-access`(env 注入已落地:`spawn.ts` ALLOWED_ENV + `orchestrator.ts` baseEnv,已核实)。
但权限档位是**会话级播种**(会话创建时一次性写入 `permission/preset` / `sandbox/mode` / `approval/policy` 三条种子事件,**resume 不重播**)。实测全平台:
```
总会话=10 workspace-write=10 danger-full-access=0
```
即 **10 个存量会话(含 admin 自己在 mcntimo / mcnworkspace / maiyamcn 的)全部仍是 `workspace-write`**,只要它们被 resume 并执行 shell,就会撞上「sandbox backend is unusable → 拒绝执行」。guest 这条会话就是**已经真实踩中**的样本。
- **缺口**:平台**没有任何 UI/文案提示**「老会话需新开一个会话才生效」。用户唯一能得到这条信息的渠道是让 AI 去分析自己的会话日志(本次就是这么发现的)。
- **候选方向**:① 会话列表给旧档位会话打标记 + 一键「在新档位继续」;② 门户 `enter` 时对旧档位会话做一次性重播种(改会话数据,需评估);③ 最低成本:产品文案层提示。
### P0-2 · 平台零技能投放:用户在 MCN 工作区里「无技能可用」
| 位置 | 实况 |
|---|---|
| guest `$DSH_HOME/home/skills/` | **0 项** |
| admin `$DSH_HOME/home/skills/` | **0 项** |
| 全员共享只读层 `/var/lib/dshs/bundled-skills/` | **0 项** |
| 各工作区 `ws/*/` 内容 | guest 只有 `data/` + `scripts/`(无 SKILL.md、无素材库、无项目结构)|
| agent preset | `standard`(无业务技能)|
平台已建成技能机制(档案 10 共享只读层 + 档案 11 管理面 API/静态页),但**一个技能都没投放**。后果在会话里体现得非常直接:
- 用户开的是「MCN短视频创作」工作区,却**没有任何 MCN 技能**被加载 → agent 只能从零 hand-roll 抖音抓取脚本、反复试错(100 次 bash 中相当一部分是抓取路径试错)。
- 用户第 7 问「**为什么需要写代码,不能获取后直接展示内容吗**」、第 9 问「有安装网络搜索工具吗 比如 deepseek 搜索」——都是**「本该有技能/数据源,却没投放」**的症状。
- **应投放而未投放**:MCN 短视频创作技能(脚本/分镜/账号分析)、RedFox 数据源(日榜/飙升榜/账号诊断,本平台已有 API Key 与 7 赛道口径)、`mcn-data-insight` 系列技能。
> 这是本次取证**最重的产品结论**:不是能力不够,是**已有能力没有接到用户面前**。
---
## 三、修正档案 32 的两条结论
### 修正 1 · 「沙箱不可用」不是恒定状态,**同一会话内会翻转**
档案 32 结论为「bwrap 已装但 dsh 后端探测仍判不可用」,并附「实例内嵌套 bwrap 失败」。本次拿到**同一会话内前后对比**的强证据:
| 时间 | 同一会话内的实测 | bash |
|---|---|---|
| 09-10 12:12–12:13(turn 1)| `id` / `uname` / `read /etc/passwd` / `touch /tmp` / mounts 全部正常返回 | ✅ **可用** |
| 09-10 13:47:03 与 13:47:28(turn 2)| `Error: sandbox mode "workspace-write" ... no sandbox backend is usable on this host; refusing to run the command unconfined.` | ❌ **全拒** |
二者之间只隔一次 `session/end-seed`(13:46:07)→ 会话 resume(13:46:59),**无用户侧改动**。
另:本次在服务器以 guest uid 模拟实例内做**嵌套 bwrap 实测 → `NESTED_BWRAP_OK`(bubblewrap 0.4.0)**,与档案 32 的「失败」相反。
→ 结论修订为:**「后端可用性」是时序/环境相关,不是确定性不可用**(与实例 respawn、平台 bwrap 挂参变化有关,同期平台确实改过实例内挂载:turn 1 `$HOME` 只读 → 09-11 变可写)。
→ 因为档案 33 已让新会话绕开沙箱,**该问题当前不阻塞新会话**;但对存量会话仍是硬阻塞(见 P0-1),**根因仍未定位**。
### 修正 2 · 沙箱报错**丢失根因细节**,并把「关掉沙箱」写进了报错文案
`@deepseek-ai/dsh-sandbox/lib/index.js:185` 的报错**支持**附加 `Runner failure: ${detail}`。但本次会话日志中 **`Runner failure` 命中 0 次** —— 模型拿到的只有那句脱敏后的通用文案。
后果链(会话内实际发生):
1. 模型无法判断根因 → 只能猜 → 主动发起提权申请:
`approval/asked` reason = *"The sandbox backend is unavailable on this host so every shell command is refused; full access is the only mode that lets me run curl to fetch the Douyin hot list you asked for."*
2. 用户 7 秒内点了「允许一次」(`allowed-once`)。
3. 6 秒后用户**自己又跑了一次** `/permission danger-full-access`(`command/run`,`source: user`)→ `approval/policy` 同步变为 `never`。
→ **官方沙箱的 fail-closed 设计是对的,但报错文案("otherwise switch the consumer to danger-full-access")等于手把手教模型申请降级安全档位**;再叠加「根因细节丢失」,模型没有别的选择。这与档案 32 的判断一致,本次补上**机制性证据**。
---
## 四、P1
| # | 问题 | 证据(会话内) |
|---|---|---|
| P1-1 | **模型被切到实验模型,且切换事件无法审计** | `model/selection` → `deepseek-v4-flash-vision-exp`,`reasoningEffort` `high`→`max`(13:47:50 / 13:47:53,turn 2 中途)。`model/selection` 事件**无 `source` 字段** → 无法判定是用户切、平台切还是自动切。后续整段会话跑在实验模型上 |
| P1-2 | **智能体把「用户手动提权」误报为「平台能力增强」** | turn 11 用户问「看看现在能力有增强吗」→ agent 结论「能力确实有增强…核心是 bash 从『完全不可用』变成『可用』🟢 **最大增强**」。实际是 turn 2 **用户自己**跑了 `/permission danger-full-access`(`command/run` source=user,就在同一份日志里)|
| P1-3 | **联网抓取路径不可持续,全靠「绕」** | ① 抖音官方视频接口要签名 → `Unsupported path(Janus)` / `HTTP 200 空 body`;② `tophub.today` → `403 检测到异常访问行为`;③ 最终靠**伪装 `Baiduspider` UA 拿 SSR 页**再正则扒 HTML(脆弱的爬虫 hack),并退回 GitHub 第三方镜像(**时效差 3 小时**,agent 自陈「镜像最后更新 18:49,我抓的是官方 21:47」)|
| P1-4 | **平台 web_search 可用但没人知道** | 官方 `dsh-web-search-deepseek` 已装且实测可用(`web/deepseek-search-llm-request` 3 次)。但前 8 轮 agent 一直用 curl/python 硬抓,**第 9 轮用户主动问「有安装网络搜索工具吗」才开始用**。111 次工具调用里 `web_search` 只有 **2 次** |
| P1-5 | **用户感知「卡」的原始证据(补充档案 21)** | 用户第 13 问「感觉对话过程卡吗」→ agent 用日志算出:**69 步、平均 7.8s/步、中位 5.0s、最慢 81.1s、模型总生成 8m58s**;单次 bash 最长 **1m19s**;turn 3 单轮 4m50s。档案 21 已证**服务端全链路不慢**(TTFT 1.0–3.5s),本次补上「用户侧体感来自哪 69 步 / 哪 111 次工具调用」的实测数据 —— **卡顿主因是工具调用轮次过多,而轮次过多的直接原因是「技能未投放 + 数据源未接入」**(P0-2 / P1-3),不是网络 |
| P1-6 | **会话导出产物用户打不开** | 用户点过 `/export`(`command/done` → *"Session log download requested."*)。格式是 **`.jsonl.zstd` 追加式多帧**,`zstdDecompressSync` 只解第一帧;**连 agent 自己都踩了这个坑**(`流式解压失败: Unknown frame descriptor` → 改用魔数 `28 B5 4D FD` 逐帧解出 1632 帧)。用户拿到的是一个无法阅读的文件 |
| P1-7 | **环境基线太弱,MCN 场景几乎不可用(量化档案 23 的「可优化」项)** | agent 自统计 **35 处失败/受限**,含:Python **3.6.8 + pip 9.0.3**(al8 自带)、`rg` / `jq` / **`ffmpeg`** 缺失、**无任何浏览器**(chromium/chrome/firefox 全 MISSING,Playwright 落地需 300MB + **11 个系统库缺失**且 `/usr` 只读无 sudo 装不上)、`/usr`+`/etc` 只读、无 sudo。档案 23 已把这些列为「可优化」,本次量化了它们**实际烧掉的轮次**(100 次 bash 中约 1/3 耗在补环境与绕路径)|
| P1-8 | **过度承诺后自我推翻,浪费用户轮次** | agent 先推荐装 Playwright,实测后自我否定:「**我之前推荐它,根本没算过这笔账**」;并承认「**我上一条的说法确实站不住脚**」(把第三方镜像数据当成实时数据)|
---
## 五、P2
| # | 问题 | 证据 |
|---|---|---|
| P2-1 | 会话列表出现**空会话残留** | `session-b83ae69b`:仅 4 条事件(preset/mode/policy + end-seed),**0 个 turn**,13:29 创建、21:26 才结束,挂 8 小时 |
| P2-2 | **平台缓存污染用户工作区** | `ws/.npm/_cacache`、`ws/.npm/_logs`、`ws/.cache/pip/`、`ws/workspace-scoped-picker-0.1.4.tgz` 均落在工作区根部,会出现在门户 `#/files` 列表里 |
| P2-3 | 工作区根存在**平台 bundle `.tgz`** | `ws/workspace-scoped-picker-0.1.4.tgz`(09-11 15:00 更新)。档案 35 已修 `ws-cleanup` 误删,**建议确认该 picker 包也在豁免口径内**(当前豁免逻辑按「profile 的 `file:` 引用」动态判定,需回归验证)|
| P2-4 | 工具报错噪音,指向不存在路径 | `rg: /opt/dshs: No such file`、`rg: /var/log: No such file`(实例内看不到平台目录,agent 反复试)|
| P2-5 | **文档内部不一致:README 档案清单落后 8 个档案** | `docs/README.md` 的「档案清单」表**止于 `28-用户数据清理策略`**,`29`–`35` 全部缺失;而 `docs/INDEX.md` 是全的(到 `04-35`)。README 自称「档案清单只在本文件维护」,与该「单一来源约定」**已失效**(本次只在 INDEX + docs-status 维持,未擅自重排 README)|
---
## 六、安全观察(平台**挡住了**,但值得记录)
guest(普通租户)在会话中**主动做了横向/越权探测**,全部被平台层挡住:
| 探测动作 | 结果 |
|---|---|
| `ls /var/lib/dshs/users/` | `Permission denied` ✅ |
| `touch /etc/dsh-probe`、`touch /usr/local/lib/node_modules/@deepseek-ai/dsh/probe` | `Read-only file system` ✅ |
| `ls /root`、`/home/admin`、`/home/www`、`/home/dsh-cce6d1cdb376430480f0` | 不存在 / 不可访问 ✅(bwrap 只暴露自己的用户根)|
| `sudo` | 不可用(`/etc/sudo.conf` 属主 uid 65534、未 setuid)✅ |
| `ps -eo user,pid,cmd` | 可看到进程列表(bwrap 只 `--unshare-pid`,**未做 PID 视图隔离**)⚠️ |
| `curl http://127.0.0.1:3080/` (门户)| **返回 200,可读到门户首页 HTML** ⚠️(仅静态首页,无数据泄漏)|
| 内网端口扫描 `3080/80/443/888/8765/58888/32022` | 可探测,返回状态码与 Server 头 ⚠️ |
| `env \| grep -iE 'api_key\|token\|secret'`、读 `$DSH_HOME/.credentials.yaml` | agent 主动做了脱敏打印;**扫描会话全文:0 处明文凭据** ✅ |
| 起本地监听 `python3 -m http.server 39777` | **成功**(127.0.0.1 绑定 OK)+ `pip install` / `npm` 出网均可用 ⚠️(与既有「实例共享宿主网络、出网未全封」结论一致)|
**结论**:数据面与文件面隔离**有效**;**网络面(内网侧)与审计面是缺口** —— 上述探测**没有任何阻断、限速或审计上报**。
---
## 七、建议行动项(按优先级)
| # | 行动 | 说明 |
|---|---|---|
| 1 | **投放技能到 `bundled-skills`**(P0-2)| 最高性价比:MCN 短视频创作 + `mcn-data-insight` 系列。技能机制已就绪、watch 即时生效、无需重启 |
| 2 | **存量会话档位提示/迁移**(P0-1)| 10/10 会话受害;最低成本先做 UI 标记 +「新开一个会话」文案 |
| 3 | **接 RedFox 数据源到平台**(P1-3/P1-4)| 替代「伪装爬虫 UA 扒抖音」这条不可持续路径;已有 API Key 与 7 赛道口径 |
| 4 | **补环境基线**(P1-7,延续档案 23)| 最低集:`rg` / `jq` / `ffmpeg` / Python 3.8+;评估基线镜像方案 |
| 5 | **会话导出给可读格式**(P1-6)| 导出时转 `.md` 或解开的 `.jsonl` |
| 6 | **`model/selection` 补 `source` 字段**(P1-1)| 属官方行为,只做记录与观测;先确认实验模型是否为预期 |
---
## 八、取证方法补充(相对档案 32 的新坑)
1. **服务器 `/tmp` 下存在 `package.json`(`"type":"module"`)** → 在 `/tmp` 放分析脚本必须用 **`.cjs`** 扩展名,否则 `require is not defined in ES module scope`。(本次踩坑)
2. 会话日志里 **`tool/result` 的报错常被 `|| echo` 兜住**,表面看不出失败 → 分析时要按「错误特征词」扫全文并统计**位置分布**,而不是只看退出码。
3. `request/header` 每条含完整 system prompt + 工具表,**体量大**(占 2.8MB 相当比例)→ 提取时按 `type` 过滤,勿全量 dump。
4. 分析脚本**本地写好 → scp → 远端 `node` 执行**最稳(内层引号转义会在 `ssh '...'` 里坏掉)。