boardx / boardx/workspacex

试跑接真执行:模型写 pptxgenjs 脚本 → 沙箱执行 → 产出真实 .pptx(spike 已验证可行)

Open
#1,575 0 comments 0 reactions 0 assignees View on GitHub
Dominant language
TypeScript
Stars
0
Forks
0
Avg merge
1h 7m
Merged PRs (30d)
969

Description

## 人类裁决

#1570 ③ 给了两条路(正名 / 真执行),人类 2026-08-18 选 **(b) 真执行**:
> 「(b) 真执行 —— 接一条带工具和沙箱的执行路径(形态接近既有的 apps/deep-agent-service)。
> 范围大得多,但这才是真正跑通 pptx。选一个聪明的办法,可以跑通」

本 issue 记录**动工前先做的 spike**:不写任何生产代码,先把最不确定的一环用真实模型 +
真实执行验证掉。**结论:可行,且已经真的产出了一个能打开的 3 页 .pptx。**

## spike 实测证据(全部可复现,非推测)

### ① 创建路径不需要 Python / LibreOffice —— 这是能"聪明"的根本原因

pptx SKILL.md 的决策表第一行逐字写着:

| **Create** a new deck | Write a `pptxgenjs` script |

`pptxgenjs` 是**纯 npm 库**,而本仓就是 Node monorepo。Python(`python-pptx`)与
LibreOffice(`soffice`)在该 skill 里只用于:编辑存量 deck、转旧 `.ppt`、视觉 QA 渲染、
缩略图。**"从零做一个 deck"这条主路径只要 Node + pptxgenjs。**

⇒ 首个切片不必建通用多语言沙箱,范围可以砍掉一大半。

### ② 一次性生成**不可靠**,错误回喂的修复循环**必需**(决定架构形状)

拿真实 `qwen3.8-max` + 真实 20KB SKILL.md 当 system prompt:

| 轮次 | exit | 产物 | tokens |
|---|---|---|---|
| 第 1 次 | 1(失败) | 无 | 6758 |
| 第 2 次 | **0** | **有** | 8539 |

第 1 次的失败原因很典型:脚本用了 `pres.ShapeType.RECTANGLE`,而 pptxgenjs v4 里
这个属性不存在 ⇒ `Missing/Invalid shape parameter`。

⚠ **这条否掉了"模型写脚本 → 执行一次 → 收工"的形态**:必须把 stdout/stderr 回喂给模型
让它自己修。实测**第 2 次即收敛**。这正是 tool-loop / agentic 形态存在的理由,也印证了
人类"形态接近 deep-agent-service"的判断。

### ③ 产出的是真 .pptx,不是"看起来像"

```
out/deck.pptx: Zip archive data
zip 完整性: OK 条目数: 47 大小: 64501 B
presentation content-type 存在: True
幻灯片: ppt/slides/slide1.xml, slide2.xml, slide3.xml
所有 slide XML 解析通过
第1页文字: ['WorkspaceX', '重新定义团队协作的一站式智能工作平台']
第2页文字: ['核心卖点', '无缝集成', '一键连接主流工具…', 'AI 驱动洞察', …]
第3页文字: ['联系我们', '邮箱:hello@workspacex.com', …]
```

三页结构与用户需求逐条对应(标题页 / 三个卖点 / 联系方式),不是空壳。

### ④ Node 权限模型能挡住什么(三条反证,都实测拒绝)

以 `--experimental-permission --allow-fs-read= --allow-fs-write=` 运行:

| 攻击 | 结果 |
|---|---|
| 写 `$HOME/PWNED.txt` | `ERR_ACCESS_DENIED`(Use --allow-fs-write) |
| `child_process.execSync('id')` | `ERR_ACCESS_DENIED`(Use --allow-child-process) |
| 读 `/etc/passwd` | `ERR_ACCESS_DENIED`(Use --allow-fs-read) |

### ⑤ ⚠⚠ Node 权限模型**挡不住网络** —— 这条决定架构,不是细节

```
node --experimental-permission … evil-net.js
→ NET OK — 未被挡, status 200 # fetch('https://example.com') 成功
```

Node 的权限模型**没有网络维度**。也就是说:单靠它,一个被注入的 skill 写出的脚本
可以把 workdir 里读得到的任何东西 POST 出去。

⇒ **单靠 Node 权限模型不构成沙箱**。网络必须在**容器层**关(`--network none`)。
这正是"独立容器化服务"(deep-agent-service 那种形态)是**必需**而非可选的原因——
不是为了架构好看,是因为进程内根本关不掉这个洞。

## 由证据推出的架构(细节见 design delta)

**两层隔离,各挡各的、互不替代**:

| 层 | 手段 | 挡住 |
|---|---|---|
| L1 容器 | 独立容器 + `network: none` + 只读 rootfs + 非 root + tmpfs workdir + mem/CPU/pids 上限 | **网络外泄**(Node 关不掉的那个)、逃逸、资源耗尽 |
| L2 进程 | Node 权限模型(`--allow-fs-read/write` 白名单、不给 child-process/worker) | 文件越界、起子进程(纵深防御) |

形态逐条对齐既有 `apps/deep-agent-service`(独立服务、自己的容器、HTTP 契约、
测试用 loopback 替身),复用已经在跑的部署机制(`workspacex-deep-agent` 容器先例)。

pptxgenjs **预装进镜像**——skill 自己就写着「preinstalled — do not run `npm install` first」,
预装后 skill 的原文指令逐字成立,且容器不需要出网装包(与 `network: none` 自洽)。

## 首个切片范围(建议)

1. `apps/skill-sandbox`:Node 服务,`POST /run { script, timeoutMs } → { exitCode, stdout, stderr, files[] }`,pptxgenjs 预装。
2. Dockerfile + compose/部署:`network: none`、非 root、只读 rootfs、tmpfs workdir、资源上限。
3. `apps/api`:`SkillSandboxPort`(application)+ `HttpSkillSandbox`(infrastructure)+ kernel 接线。
4. 试跑改造:模型调用带 `run_script` 工具 → 执行 → 失败回喂重试(上限 N 次)→ 成功则产物入 `ObjectStore`(`putOnce/get/head` 已存在)→ 界面给下载。
5. loopback 替身(同 `loopback-model-provider.ts` 纪律),让门控确定性可跑。

## 需要人类签核

按 ADR-023,这是新能力、要过设计签核(UI / 用例 / API 契约三件一处签)。
design delta 已写好待签:`phases/phase-01-run-a-project/design-deltas/skill-sandbox-execution/`。

**其中有一条是真正的人类决策,不是技术细节**:执行模型生成的代码这件事本身扩大了攻击面
(恶意 skill 可从 GitHub 导入)。上面两层隔离是我能给的最强边界,但"允许模型在我们的
基础设施上跑代码"是一个产品/风险取舍,应该由人明确批准,而不是由 agent 默认。

Contributor guide

No contributing guide indexed for this repository

Research direction

Start with phases/phase-01-run-a-project/design-deltas/skill-sandbox-execution/ and the existing apps/deep-agent-service pattern, then inspect apps/skill-sandbox and the apps/api application and infrastructure ports. Confirm ADR-023 approval before implementation. Done means the sandbox contract, container and Node permission restrictions, deterministic loopback tests, retry loop, ObjectStore output, and download path are covered.

Written by the indexing model from the issue text.

Assessment

Tech stack
docker, node.js, typescript
Domain
api, backend-api-design, infrastructure, security
Issue type
Feature
Difficulty
5/5
Estimated time
Over a week
Activity status
Active
Clarity
Mostly clear
Newbie friendliness
38/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.