diff --git a/README.md b/README.md index b80d756..18eeee1 100644 --- a/README.md +++ b/README.md @@ -1,15 +1,158 @@ # Agent Studio Mini -Training-grade Agent Harness built with Pi Coding Agent SDK, Fastify, SSE, React, and assistant-ui. +一个面向课堂演示的可观察 Agent Harness。它把 Pi Coding Agent SDK 放在 Fastify 服务端,通过 HTTP + SSE 将 Session、流式回复、Skills、工具调用、审批与图片附件映射到 assistant-ui 三栏工作台。 -The project is under active construction. Verified setup, security guidance, and demo instructions are added with the completed implementation. +> 截图占位说明:仓库不提交与本机配置绑定的截图。启动后打开 `http://127.0.0.1:5173`,即可看到左侧 Skills、中央 Streaming Chat、右侧 Event Timeline 的完整界面。 -## Verified dependency baseline +## 架构 -- Node.js 22.22.3 (Pi 0.85.1 requires Node.js 22.19 or newer) -- pnpm 11.7.0 -- TypeScript 5.9.3 (the newest release supported by typescript-eslint 8.69) -- Pi Coding Agent SDK 0.85.1, using `typebox` 1.3.7 -- assistant-ui React 0.15.18 +```mermaid +flowchart TD + UI[assistant-ui Composer / Thread / Tool UI] + API[Fastify API] + REG[SessionRegistry] + PI[Pi AgentSession] + EVT[EventStore / ApprovalBroker] + RES[ResourceLoader / Tools] + VIEW[Timeline / Approval] + CORE[Skills / Workspace / Model] -The original brief allowed Node.js 20+ and referred generically to the current TypeBox package. The installed Pi release has a higher Node.js floor and directly uses the `typebox` package rather than `@sinclair/typebox`; this project follows the installed public types. + UI -->|HTTP + SSE| API + API --> REG + REG --> PI + API --> EVT + PI --> RES + EVT --> VIEW + RES --> CORE +``` + +```mermaid +sequenceDiagram + participant U as 用户 + participant W as assistant-ui + participant H as Harness + participant P as Pi AgentSession + U->>W: 发送文字和可选图片 + W->>H: POST message + H-->>W: 202 + runId + H->>P: prompt(text, images) + P-->>H: 文本、Skill read、Tool 事件 + H-->>W: SSE HarnessEvent + P->>H: save_report_draft + H-->>W: approval.required + U->>H: Approve 或 Deny + H-->>P: 审批结果 + P-->>H: tool/result + message end + H-->>W: run.completed +``` + +职责边界:assistant-ui 只负责交互和渲染;Harness 是 Session、事件、附件、模型与审批的权威;Pi 负责 Agent loop、模型调用、ResourceLoader 与工具执行。浏览器不直接持有 Pi Session,也不能自行把写工具标为已批准。 + +更详细的组件和数据流见 [docs/architecture.md](docs/architecture.md),事件对应关系见 [docs/event-mapping.md](docs/event-mapping.md)。 + +## 目录 + +```text +apps/server Fastify API、SSE、启动装配 +apps/web React + assistant-ui 三栏工作台 +packages/shared 浏览器与服务端共享的 Zod 协议 +packages/harness Pi adapter、Session、事件、审批、附件和安全边界 +.agents/skills 示例 ads-analysis Skill +fixtures 确定性的模拟广告账户数据 +docs 架构、安全、事件、课堂演示和生产路线 +``` + +## 环境与安装 + +- Node.js 22.19 或更高版本。Pi 0.85.1 的实际要求高于原始需求中的 Node.js 20。 +- pnpm 11.7.0。 + +```bash +pnpm install +cp .env.example .env +pnpm dev +``` + +Web 默认地址是 `http://127.0.0.1:5173`,Fastify 默认监听 `http://127.0.0.1:3001`。根命令会同时启动两者,Vite 将 `/api` 和 `/health` 代理到 Fastify。 + +## 配置模型 + +模型解析优先级为:当前 Session 的 Settings 配置、服务器环境变量、明确的 `MODEL_NOT_CONFIGURED` 错误。没有“第一个可用模型”的隐式回退。 + +环境变量示例: + +```dotenv +AGENT_PROVIDER=openai +AGENT_MODEL=gpt-5-mini +LLM_API=openai-responses +OPENAI_API_KEY= +LLM_BASE_URL= +AGENT_MODEL_SUPPORTS_IMAGES=true +``` + +Anthropic 使用 `AGENT_PROVIDER=anthropic`、`LLM_API=anthropic-messages` 和 `ANTHROPIC_API_KEY`。自定义兼容服务可设置 `LLM_BASE_URL`;默认仅接受安全的 HTTPS 地址,开发环境可接受 localhost,内网地址仅在明确设置 `ALLOW_PRIVATE_LLM_BASE_URLS=true` 后允许。 + +也可以在页面右上角打开 Settings,填写 Provider、API 类型、Base URL、API Key、Model 和图片能力。保存后配置只属于当前 Session。连接测试在当前实现中安全地返回“首次运行验证”,不会编造一次真实调用成功。 + +API Key 从同源请求提交后只驻留服务器进程内存,并在 Pi `InMemoryCredentialStore` 中注入;不会写入 localStorage、sessionStorage、IndexedDB、SSE、事件、响应或用户目录。GET 只返回脱敏的 `keyHint`。刷新页面会新建 Session;删除 Session、清除凭证或重启进程都会丢失该内存配置。 + +## 图片链路 + +Composer 支持选择、拖拽和粘贴 JPEG、PNG、WebP,默认单图最大 5 MiB、每条消息最多 4 张。上传采用 multipart 流,服务端同时校验文件名扩展、MIME、magic bytes、尺寸与像素数,再用 Sharp 自动旋转和重新编码。规范化文件仅保存在当前 Session 临时目录。 + +浏览器状态和 SSE 只保存 `attachmentId` 与安全元数据。真正调用 Pi 时,服务端才读取规范化图片并构造 `ImageContent[]`。如果显式模型是 text-only,发送图片会返回 `MODEL_DOES_NOT_SUPPORT_IMAGES`,不会静默丢图或切换模型。 + +## 课堂 Demo + +1. 打开页面并配置模型。 +2. 点击“填入演示任务”,得到:`请分析 demo-account 最近 7 天广告数据,并生成一份简短报告草稿。` +3. 可选:附加一张广告素材截图,并补充“请结合图片解释创意表现,但不要从图片猜测账户指标”。 +4. 点击发送。POST 会立即返回 202;中央区域流式显示回复,左右两栏显示 Skill 和事件。 +5. 观察 `mock_ads_metrics` 只读工具卡。数据来自 `fixtures/ads-account.json`,不是实时广告平台。 +6. `save_report_draft` 出现时选择 Approve 或 Deny。Approve 只会在当前 Session 工作区原子写入固定的 `report.md`;Deny 或超时不会创建文件。 +7. 删除或重置 Session 后,附件、报告和内存凭证一起清理。 + +完整 10–15 分钟讲解脚本见 [docs/classroom-demo.md](docs/classroom-demo.md)。 + +## Skill 渐进披露 + +启动时 `SkillCatalog` 使用 Pi `DefaultResourceLoader` 发现 Skills,并向页面只展示名称、描述、来源、安全显示路径和 diagnostics。Skill 正文不会塞入初始前端状态。 + +当 Pi 的 `read` 工具成功读取一个已索引的规范化 `SKILL.md` 路径时,Harness 推导 `skill.requested` 和 `skill.loaded`。它们不是 Pi 原生事件:requested 表示已识别目标 Skill,loaded 只在 read 成功后发出。示例 Skill 要求在分析任何账户指标前调用 `mock_ads_metrics`。 + +## Approval 与安全边界 + +- 工具策略:`read`、`grep`、`find`、`ls`、`mock_ads_metrics` 允许;`save_report_draft` 必须审批;其余工具拒绝。 +- 审批状态和超时由服务器 `ApprovalBroker` 决定。重复或冲突决策被拒绝。 +- Session 工作区经过 realpath、路径包含关系、符号链接和保护路径检查;报告只能写固定文件。 +- Base URL 做协议、userinfo、fragment、端口、DNS、私网、loopback、link-local 和云 metadata 检查。 +- 上传图片不作为静态资源公开,也不把 base64 放入事件。 + +Pi 没有内置 Sandbox,Project Trust 也不是 Sandbox。Pi 和 Extension 继承当前 Node 进程的操作系统权限。本项目的路径校验、工具 allowlist 和审批是应用层约束,不能替代容器、VM、系统用户或网络隔离。完整说明见 [docs/security-boundaries.md](docs/security-boundaries.md)。 + +## 验证 + +```bash +pnpm lint +pnpm typecheck +pnpm test +pnpm build +``` + +最近一次完整测试包含 117 条测试:shared 13、harness 68、server 16、web 20。默认测试使用可编程 Fake AgentSession,不需要真实 API Key、真实模型或外网。最终交付前还会重新运行以上四个命令并以实际结果为准。 + +## 当前依赖与 API 核对 + +- TypeScript 5.9.3,typescript-eslint 8.69。 +- Pi Coding Agent SDK / Pi AI 0.85.1,当前公开 API 使用 `ModelRuntime.create`、`registerProvider`、`InMemoryCredentialStore`、`setRuntimeApiKey`、`createAgentSession`、`DefaultResourceLoader` 与 `defineTool`。 +- Pi 0.85.1 的 schema 包是 `typebox` 1.3.7,不是 `@sinclair/typebox`。 +- assistant-ui React 0.15.18,外部 Harness 状态通过 `useExternalStoreRuntime` 映射,附件通过自定义 `AttachmentAdapter` 上传。 +- React 19.2.8、Fastify 5.12.3、Vite 8.2.2、Zod 4.5.4、Sharp 0.35.4。 + +实现只使用安装包导出的公开类型;没有修改用户目录下的 Pi 配置,也没有依赖已废弃的 Runtime 示例。 + +## 已知限制与生产化 + +课堂版是单进程、单实例、内存 EventStore / ApprovalBroker / ModelConnectionStore;刷新会创建新 Session,进程退出不会恢复运行,报告和附件只是本机临时文件。它没有用户身份、跨设备同步、分布式事件总线或操作系统级隔离。 + +生产化需要 Redis/Postgres 持久化、对象存储、KMS/Secret Manager、身份鉴权、授权与租户隔离、CSRF 和速率限制、容器或 microVM Sandbox、出站网络策略、不可篡改审计日志、分布式锁与可重放 SSE。路线见 [docs/production-roadmap.md](docs/production-roadmap.md)。 diff --git a/docs/architecture.md b/docs/architecture.md new file mode 100644 index 0000000..e766631 --- /dev/null +++ b/docs/architecture.md @@ -0,0 +1,78 @@ +# 架构与数据流 + +## 设计目标 + +Agent Studio Mini 把课堂上最容易被隐藏的 Agent 行为变成可观察事件:模型流式输出、Skill 渐进加载、工具生命周期、写操作审批和 Session 清理。浏览器是投影,服务端 Harness 才是状态权威。 + +```mermaid +flowchart LR + subgraph Browser + Composer + Thread[assistant-ui Thread] + ToolUI[Tool UI / Approval Dialog] + Timeline + Store[HarnessClientStore] + end + + subgraph Fastify + Routes[HTTP Routes] + SSE[SSE Connection] + end + + subgraph Harness + Registry[SessionRegistry] + Session[HarnessSession] + Events[EventStore] + Broker[ApprovalBroker] + Attachments[AttachmentStore] + Models[ModelConnectionStore] + Skills[SkillCatalog / Inference] + Workspace[SafeWorkspace] + end + + subgraph Pi + Agent[AgentSession] + Loader[DefaultResourceLoader] + Runtime[ModelRuntime] + Tools[Pi Tools + Extension] + end + + Composer -->|multipart / JSON| Routes + Routes --> Registry --> Session --> Agent + Store -->|EventSource| SSE --> Events + Events --> Store --> Thread + Store --> Timeline + Store --> ToolUI + Session --> Attachments + Session --> Models --> Runtime + Session --> Skills + Agent --> Loader + Agent --> Tools --> Workspace + Tools --> Broker --> ToolUI +``` + +## 一次消息的生命周期 + +1. Web 创建 Session,读取 Skill Catalog,并建立 SSE。 +2. 图片由 `AttachmentAdapter` 先上传,Composer 只保留 `attachmentId`。 +3. Web 提交文本和 attachment IDs;Server 校验后立即返回 `202 + runId`。 +4. `HarnessSession` 显式解析当前模型,转换图片,创建或复用对应模型指纹的 Pi Session。 +5. Pi 事件由 `PiEventAdapter` 串行映射。异步 Skill 路径识别完成之前不会越过后续 read-completed 或 `run.completed`。 +6. `InMemoryEventStore` 分配单调 sequence;SSE 支持 cursor replay、heartbeat 和断线清理。 +7. Web reducer 去重并拒绝 sequence gap,映射为 assistant-ui 外部 Runtime、工具卡、审批框和时间线。 +8. run 完成、取消或失败后释放订阅;删除 Session 时取消审批、终止 run、删除附件和工作区。 + +## 核心所有权 + +| 状态 | 权威组件 | Web 的角色 | +| --- | --- | --- | +| Session / active run | `SessionRegistry` / `HarnessSession` | 发起命令、展示结果 | +| Event sequence | `EventStore` | 去重、检测缺口、重连 | +| Approval | `ApprovalBroker` | 提交一次决策、等待事件确认 | +| API Key | `ModelConnectionStore` + Pi 内存凭证 | 仅在 password input 中临时持有 | +| Attachment bytes | `AttachmentStore` | 上传阶段持有 File/object URL | +| Tool write target | `SafeWorkspace` | 不参与路径选择 | + +## 进程与存储模型 + +课堂版只有一个 Node 进程。EventStore、ApprovalBroker 和模型配置均在内存;Session 工作区位于 `.agent-studio-sessions/`。这种结构便于展示边界和清理行为,但不提供横向扩展、崩溃恢复或多租户隔离。 diff --git a/docs/classroom-demo.md b/docs/classroom-demo.md new file mode 100644 index 0000000..628d240 --- /dev/null +++ b/docs/classroom-demo.md @@ -0,0 +1,49 @@ +# 课堂演示脚本(10–15 分钟) + +## 演示前准备(1 分钟) + +运行 `pnpm dev`,打开 `http://127.0.0.1:5173`。准备一个可用模型配置;若演示图片,确认 `supportsImages` 已开启并准备一张 JPEG、PNG 或 WebP。 + +先指出三栏:左侧是被发现的 Skills,中间是 assistant-ui 对话与工具卡,右侧是服务器事件时间线。强调页面不是 Agent 本体,而是 Harness 状态的投影。 + +## 1. 未配置模型(1 分钟) + +展示 Composer 的明确禁用提示。解释系统不会偷偷选择任意可用模型,也不会把图片悄悄丢弃。打开 Settings,填写 Provider、API、Model、可选 Base URL 和 API Key。 + +指出 API Key 字段刷新后不会恢复,GET 响应也只能看到末四位提示。 + +## 2. Skill 渐进披露(2 分钟) + +点击“填入演示任务”,不要立刻发送: + +> 请分析 demo-account 最近 7 天广告数据,并生成一份简短报告草稿。 + +先看左侧只有 `ads-analysis` 的名称、描述和 diagnostics,没有把正文放进浏览器。发送后观察 `read`、`skill.requested` 和 `skill.loaded`。说明两个 Skill 事件由 Harness 根据规范路径和 read 成功状态推导,不是模型自报。 + +## 3. 只读工具与流式回复(2–3 分钟) + +观察中央文字逐段增加,右侧 sequence 单调增长。`mock_ads_metrics` 工具卡展示 account、days、spend、revenue 和 ROAS。 + +明确数据来自仓库 fixture,不连接真实广告平台。Skill 约束模型先取工具结果再做数字结论,这体现“指令披露 + 结构化工具”的组合。 + +## 4. 服务器审批(2–3 分钟) + +当 `save_report_draft` 请求出现时,Dialog 显示风险、固定目标、approval ID 和剩余时间。先讲清浏览器按钮不是权限边界,Broker 的状态才是权威。 + +第一轮点击 Deny:工具显示未执行,Session 目录不产生 `report.md`。 + +第二轮重发任务并点击 Approve:只有这次会在当前 Session 临时目录写入固定 `report.md`。重复点击被 UI 锁定,冲突决策被 Server 拒绝。也可等待超时展示 expired 不写文件。 + +## 5. 图片多模态(2 分钟) + +附加一张广告截图,输入: + +> 请结合这张素材解释可能的创意问题,并用 demo-account 最近 7 天数据验证建议。不要从图片猜测账户指标。 + +展示上传、缩略图、预览与移除。说明浏览器/SSE 只保留 attachmentId,Server 规范化文件后才转换为 Pi `ImageContent[]`。若将模型设置为 text-only,系统会明确阻止发送图片。 + +## 6. 重连与清理(1–2 分钟) + +说明 SSE 使用 sequence 和 Last-Event-ID 重放,重复事件会去重,缺口会触发全量重建。点击重置或删除 Session,展示 run、审批、附件、报告与内存凭证一并清理。 + +最后强调:当前的 SafeWorkspace、工具策略和审批是教学用应用边界;Pi 没有内置 Sandbox,生产环境仍要用容器或 microVM 隔离。 diff --git a/docs/event-mapping.md b/docs/event-mapping.md new file mode 100644 index 0000000..141eaf2 --- /dev/null +++ b/docs/event-mapping.md @@ -0,0 +1,55 @@ +# 事件映射 + +## 三层映射 + +Pi 发出的事件首先经过 `PiEventAdapter` 变成经过裁剪和脱敏的 `HarnessEvent`。Web 的 reducer 再把有序事件投影为 assistant-ui 消息、工具和运行状态。Pi 原始对象不会直接进入浏览器。 + +| Pi / Harness 来源 | HarnessEvent | Web / assistant-ui 状态 | +| --- | --- | --- | +| Session 创建 | `session.started` | 建立初始 timeline | +| HTTP message input | `user.message` | user message,附件仅为 IDs | +| Harness 开始 run | `run.started` | `isRunning=true`,显示停止按钮 | +| `message_update.text_delta` | `assistant.delta` | 按稳定 messageId 追加文本 | +| assistant `message_end` | `assistant.completed` | assistant message complete | +| `tool_execution_start` | `tool.requested` | 创建工具调用 part | +| `tool_execution_end` 成功 | `tool.completed` | 工具卡显示 result | +| `tool_execution_end` 失败 | `tool.failed` | 工具卡显示安全错误 | +| Broker 请求 | `approval.required` | 打开服务器审批 Dialog | +| Broker 决策 / 超时 / 取消 | `approval.resolved` | 关闭 pending 状态 | +| AttachmentStore 成功 | `attachment.uploaded` | 保存安全附件元数据 | +| AttachmentStore 拒绝 | `attachment.rejected` | timeline / 错误状态 | +| ModelStore 配置 | `model.configured` | 更新安全模型摘要 | +| Harness 正常结束 | `run.completed` | run 和消息标为 complete | +| Harness 取消 | `run.cancelled` | 消息 incomplete/cancelled | +| Harness 失败 | `run.failed` | 消息 incomplete/error | +| Session 删除 | `session.ended` | 连接回到 idle | + +## Skill 推导事件 + +`skill.requested` 和 `skill.loaded` 不是 Pi SDK 的原生事件。Harness 按下面条件推导: + +```mermaid +flowchart LR + A[Pi read start] --> B[realpath] + B --> C{是否匹配 SkillIndex} + C -->|否| D[只保留普通 tool 事件] + C -->|是| E[skill.requested] + E --> F{同一 toolCallId read 成功} + F -->|是| G[skill.loaded] + F -->|否| H[不发 loaded] +``` + +真实路径匹配避免仅凭用户提供的字符串伪造 Skill 活动。事件由 Session 队列串行处理,因此异步 `realpath` 不会导致 read end 越过 read start。 + +## 顺序、重放与去重 + +- 每个 Session 的 sequence 从 1 单调递增。 +- SSE 初次连接可用 `afterSequence`,重连可用 `Last-Event-ID`。 +- Server 先重放 cursor 之后的事件,再订阅新事件,并发送 heartbeat。 +- Web 忽略 sequence 小于等于当前值的重复事件。 +- sequence 出现缺口时,Web 不应用该事件,而是重置投影并从 0 重连。 +- timeline 最多保留 500 条客户端状态,界面显示最近 100 条筛选结果。 + +## 数据最小化 + +事件负载经过 `redactAndLimit`。API Key、authorization、token 等敏感字段被脱敏;工具参数和结果有字节上限。图片事件只含 ID、MIME、字节数和尺寸,不含 base64。错误映射只保留明确 code 和有限长度的安全消息。 diff --git a/docs/production-roadmap.md b/docs/production-roadmap.md new file mode 100644 index 0000000..2c63d95 --- /dev/null +++ b/docs/production-roadmap.md @@ -0,0 +1,62 @@ +# 从课堂 MVP 到生产 Harness + +## 目标架构 + +```mermaid +flowchart LR + Web --> Gateway[Gateway / Auth / CSRF / Rate Limit] + Gateway --> API[Stateless API] + API --> DB[(Postgres)] + API --> Redis[(Redis Streams / Locks)] + API --> KMS[KMS / Secret Manager] + API --> Blob[Object Storage] + Redis --> Workers[Agent Workers] + Workers --> Sandbox[Per-run Container / microVM] + Sandbox --> Egress[Egress Proxy / Allowlist] + Workers --> Audit[Immutable Audit Log] + Redis --> SSE[Distributed SSE Gateway] + SSE --> Web +``` + +## 阶段 1:身份与持久化 + +- 引入用户、组织、Session 所有权与 RBAC。 +- Postgres 保存 Session、Run、Message、Event、ToolCall 和 Approval 的不可变记录。 +- 以 `(session_id, sequence)` 唯一约束保证事件顺序;命令使用幂等键。 +- Redis 保存短期锁、队列和 fan-out,避免同 Session 多 Worker 并发。 +- 为迁移和清理任务定义明确的数据保留周期。 + +## 阶段 2:凭证与 Web 安全 + +- API Key 进入 KMS / Secret Manager,应用只持有短期解密结果。 +- OAuth 或短期 provider token 优先于长期静态密钥。 +- Secure、HttpOnly、SameSite cookie;CSRF token;严格 CSP 与 frame-ancestors。 +- 对 Session、模型测试、消息、上传和审批分别限速并设置租户配额。 +- 所有错误响应使用稳定 code,不回显 provider body 或敏感 headers。 + +## 阶段 3:文件与执行隔离 + +- 原图和规范化图进入私有对象存储,使用租户前缀、服务端加密和生命周期规则。 +- 图片扫描、解码超时、内存限制和异步规范化。 +- 每个 run 在低权限容器或 microVM 内执行;只挂载该 Session 工作区。 +- 使用只读根文件系统、CPU/内存/文件数/时长配额、seccomp 和无特权用户。 +- 出站流量经过代理,只允许已批准的模型域名;禁止 metadata 和横向内网访问。 + +## 阶段 4:可靠 Agent 运行 + +- Durable queue 驱动 run 状态机,Worker 崩溃后可重试或终止。 +- Approval request 持久化并使用 compare-and-set 解决重复、冲突和超时竞态。 +- 工具 side effect 使用幂等键或事务 outbox;审批不等于可以重复执行。 +- 版本化 Prompt、Skill、模型配置和工具 schema,以便重放与审计。 +- 对 token、费用、延迟、tool error、approval time 和 cancellation 建立指标。 + +## 阶段 5:分布式事件与审计 + +- Redis Streams、Kafka 或数据库 outbox 发布事件。 +- SSE Gateway 按租户鉴权、从持久 cursor 重放,并支持跨实例 fan-out。 +- 审计日志记录操作者、决策、策略版本、输入摘要和 side effect,不记录秘密或原始敏感内容。 +- 定期验证删除任务确实清除了数据库、对象存储、缓存、凭证与 Sandbox volume。 + +## 上线门槛 + +生产发布前需要威胁建模、依赖与镜像扫描、SSRF/路径/上传模糊测试、故障注入、备份恢复演练、租户逃逸测试和第三方安全评审。课堂测试套件应保留,并新增真实 provider 的隔离式 smoke test;后者不能进入默认 CI,也不能使用个人密钥。 diff --git a/docs/security-boundaries.md b/docs/security-boundaries.md new file mode 100644 index 0000000..da5070c --- /dev/null +++ b/docs/security-boundaries.md @@ -0,0 +1,63 @@ +# 安全边界 + +## 信任模型 + +浏览器输入、模型输出、工具参数、上传文件名、MIME、Base URL、Skill 额外路径和 SSE cursor 都是不可信输入。Zod / TypeBox 负责结构校验,Harness 负责语义与权限校验,操作系统仍是最终权限边界。 + +## 已实现的课堂版保护 + +### 凭证 + +- Session 配置覆盖服务器环境变量;没有配置时明确报错。 +- API Key 只保存在 Server 内存和 Pi `InMemoryCredentialStore`。 +- 响应只返回 `keyHint`,不会返回原值或长度。 +- 不写浏览器 Storage、日志、事件、URL、Pi 用户目录或仓库文件。 +- 运行中禁止修改模型;清除凭证幂等。 + +### Base URL / SSRF + +- 使用 WHATWG URL,默认仅 HTTPS。 +- 拒绝 userinfo、fragment、非 HTTP(S)、异常端口和含凭证 query。 +- DNS 结果若命中 loopback、private、link-local、unspecified、multicast 或 metadata 范围则拒绝。 +- 开发模式只对显式 localhost HTTP 开例外;企业内网需要服务器管理员设置环境变量。 +- 当前请求策略要求同源重定向,不能依赖首次 URL 校验后任意跳转。 + +### 路径与写入 + +- 每个 Session 使用独立随机目录。 +- realpath + relative containment 防止 `..` 和绝对路径逃逸。 +- 逐段拒绝符号链接,并保护 `.env`、`.git`、`.ssh` 和常见私钥名。 +- write tool 只能调用 `SafeWorkspace.writeReportDraft`,目标固定为 `report.md`,临时文件以独占创建并原子 rename。 +- 删除 Session 会取消 run 和审批,并清除附件和整个 Session 目录。 + +### 图片 + +- 只收 JPEG、PNG、WebP;同时检查扩展名、MIME 和 magic bytes。 +- 流式字节上限、宽高上限与总像素上限阻止常见资源耗尽输入。 +- Sharp 自动旋转并重新编码,去掉不需要的元数据。 +- 随机服务端文件名,权限 0600;不提供任意路径下载或静态目录。 +- base64 只在 Server 构造 Pi prompt 的最后阶段短暂存在。 + +### 工具与审批 + +- allowlist 只开放课堂所需 read-only 工具。 +- 未知工具默认拒绝。 +- `save_report_draft` 在 Pi tool preflight 阶段等待服务器 Broker。 +- 审批绑定 approvalId、runId 和 toolCallId;支持 approve、deny、expired、cancelled。 +- Browser 点击不是最终事实,只有 `approval.resolved` 事件能改变投影状态。 + +## 不提供的隔离 + +Pi 没有内置 Sandbox,Project Trust 也不是 Sandbox。Pi Agent、Extension、Sharp 和所有 Node 依赖继承启动进程的用户权限与网络权限。应用层路径校验和审批不能防御依赖漏洞、Node 进程逃逸、同用户数据读取或不受控的出站模型请求。 + +因此不要用该课堂版处理生产密钥、不可信租户代码或敏感业务文件。真正部署至少需要:低权限系统用户、只读基础镜像、每 Session 容器或 microVM、挂载白名单、seccomp、出站网络 allowlist、资源配额和销毁验证。 + +## 部署前仍需补齐 + +- 登录、Session 所有权、租户授权和管理员权限。 +- SameSite Secure cookie、CSRF、防点击劫持和严格 CSP。 +- API、上传、run 和审批的速率限制与配额。 +- KMS / Secret Manager,不在应用堆中长期保存明文凭证。 +- 持久化且防篡改的审计日志。 +- 恶意图片扫描、对象存储隔离和生命周期策略。 +- 分布式锁、幂等键、Broker 持久化和崩溃恢复。