Files
analysis_claude_code/learn-pi-agent/s11_trust_and_execution_boundary

s11: Trust and Execution Boundary — 加载有 trust,执行靠容器

加载在 core 里管,执行交给容器。 Pi 边界:执行权限边界 —— 资源加载看 trust,执行边界不内置、靠部署层 containerization。

上一节:s10 → s11 → 下一节:s12


问题

core 会接触本地项目:要加载项目资料(s08),工具也会执行本地动作(s04)。

这两件事风险差很多:加载一份资料只是读,执行一个动作可能改动系统。所以加载该有个开关——不可信的项目,连资料都别加载,防恶意 AGENTS.md 或扩展混进来。

但"执行"这件事,Pi 的真实取舍和我们直觉不同:它不在 core 里限制执行权限。文件系统、进程、网络全开放,权限等于启动它的用户。真要隔离执行,靠部署层把整个进程关进容器。

s11 就把这两件事的真实分工摆出来:加载在 core 里用 trust 管,执行边界交给容器。


解决方案

两个层次,分工明确:

层 在哪 管什么
加载 core 内(trust) 不可信项目不加载资料,防恶意资源
执行 部署层(containerization) 整个进程关进沙箱/容器,限制文件/进程/网络

containerization 有三种 pattern(见 Pi 的 containerization.md):

OpenShell       整个 pi 进程跑在策略控制的沙箱
Gondolin        pi 留主机,工具执行路由到 Linux 微虚拟机
Plain Docker    整个 pi 进程跑在本地容器

重要:教学版不再发明 ExecutionPolicy/Executor 那种"core 内 dryRun/allow 开关"——它在 Pi 里没有对应物。core 内唯一能拦住执行的,是 s05 的 beforeToolCall hook(按工具 allow/block)。系统级的执行隔离,整体推给容器。


工作原理

先定信任开关。

export type ProjectTrust = "trusted" | "untrusted";

资源加载看 trust。 load(trust) 在 untrusted 时直接返回空——core 拿不到任何项目资料。

load(trust: ProjectTrust = "trusted"): ContextResource[] {
  if (trust === "untrusted") return [];
  return this.resources.map((r) => ({ ...r }));
}

createTurnSnapshot 把 trust 透传给 load,所以拍快照时就决定了本轮装不装资料。

执行不靠 core 管。 这里没有 ExecutionPolicy、没有 Executor。executeToolCall 的签名回到 s05 的样子(无 policy 参数):

export function executeToolCall(registry, hooks, call): ToolResultMessage {
  const before = hooks.beforeToolCall?.(call) ?? { type: "allow" };
  if (before.type === "block") return { /* blocked */ };
  // ... 真正执行 handler,错误捕获 ...
}

唯一能拦住执行的,是 beforeToolCall hook——它是扩展层的、按工具的拦截,不是系统级权限。要系统级隔离执行,去部署层用容器。

这一节真正建立的是执行权限边界,而且是对齐 Pi 的真实取舍:加载在 core 里用 trust 管(防恶意资源),执行不在 core 里管,整体交给部署层 containerization。core 保持轻量,权限的"重活"推给容器——这正是 README 里说的"Pi 不内置 permission system"。


试一下

运行(默认 trusted):

npm run s11

输出类似:

s11: Trust and Execution Boundary

[resources]
AGENTS.md

[execution boundary]
Pi 不在 core 内限制执行权限。执行边界靠部署层 containerization:
- OpenShell:整个 pi 进程跑在策略控制的沙箱
- Gondolin:pi 留主机,工具执行路由到 Linux 微虚拟机
- Plain Docker:整个 pi 进程跑在本地容器
core 内唯一的执行拦截点是 s05 的 beforeToolCall hook。

不可信项目(不加载资料):

npm run s11 -- --trust untrusted
[resources]
none(untrusted,不加载任何资料)

观察重点:trust 只管"加载不加载资料";执行边界那段说明清楚——core 里没有 dryRun/allow 开关,真要限制执行得用容器。


接入主线

s11 在 s10 上累积。相对 s10 的变更:

组件 s10 s11
新增类型 — ProjectTrust
ResourceLoader.load load() load(trust)(U1,默认 trusted)
createTurnSnapshot (state, registry, loader) 多一个 trust(默认 trusted)
执行权限 只有 hook trust 控加载;执行靠 containerization(core 内不内置 permission)

焊接点:loader.load(trust) 决定 context 装不装资料;createTurnSnapshot 透传 trust。executeToolCall 保持 s05 的签名(无 policy)——执行拦截只有 beforeToolCall hook,系统级隔离交给容器。

注:本节移除了早期教学版的 ExecutionPolicy/Executor。它们是为了"自演示执行边界"而发明的,但 Pi 真实没有这层——保留会让内核和 Pi 不一致。


接下来

现在工具、命令、项目资料都是零散定义的。想复用一组能力,没有个清单说明"这包里有什么"。

下一节会把它们整理成一个带清单的包,方便整体分发和加载。

进入下一节:s12。


Pi 源码溯源:不内置 permission,靠 containerization

教学版用 trust 控加载。Pi 的真实情况值得特别说明——它不内置 permission 系统,权限边界靠外部容器化。

源码在哪

  • packages/coding-agent/docs/containerization.md — 三种容器化方案(官方文档)
  • packages/coding-agent/src/core/project-trust.ts:45 — resolveProjectTrusted
  • packages/coding-agent/src/core/extensions/runner.ts — trust 事件
  • packages/coding-agent/src/tools/bash.ts:66 — bash 执行(无权限检查)

核实:Pi 确实不内置 permission

README 说"Pi 不内置 permission system"。源码证实:createLocalBashOperations(bash.ts:66)直接 spawn(shell, ...),没有任何权限检查——文件系统、进程、网络全开放,权限等于启动它的用户。

那 trust 管什么

Pi 的 ProjectTrust(project-trust.ts:45)只管资源加载,不管执行:

async function resolveProjectTrusted(options): Promise<boolean> {
  if (options.trustOverride !== undefined) return options.trustOverride;
  if (!hasProjectTrustInputs(options.cwd)) return true;       // 没有可信任输入,直接信任
  const { result } = await emitProjectTrustEvent(...);          // 问扩展 hook
  if (result) return result.trusted === "yes";
  const decision = options.trustStore.get(options.cwd);        // 查历史决策
  if (decision !== null) return decision;
  switch (options.defaultProjectTrust ?? "ask") {              // 默认问用户
    case "always": return true;
    case "never": return false;
    case "ask": break;
  }
}

trust 决定"要不要加载这个项目的扩展/资源"(防恶意 AGENTS.md 或扩展),不限制加载之后的执行。

三种容器化方案

containerization.md 给三种 pattern:

方案 怎么做 适用
OpenShell 整个 pi 进程跑在策略控制的沙箱 想全面限制
Gondolin 扩展 pi 留在主机,工具执行路由到 Linux 微虚拟机 想保护 provider auth
Plain Docker 整个 pi 跑在本地容器 简单隔离

beforeToolCall 是唯一的执行拦截点

Pi 唯一能拦截执行的,是 s05 的 beforeToolCall hook——扩展可以在那里 block 某个工具。但这是扩展层的、按工具的,不是 core 内置的、系统级的权限系统。

一句话

教学版用 trust 控加载,和 Pi 对齐;执行边界也对齐——不内置,靠 containerization 三方案在部署层做。早期教学版发明过 ExecutionPolicy,但那是为了自演示,Pi 真实没有,所以本节移除了它。