安全使用 Pi Coding Agent 的两种隔离方案

最近几个月我一直在用 Pi Coding Agent 写代码。它很极简,速度快,token 消耗也比其他 agent 低不少。代价是安全上基本没有护栏。

我搜过一圈,能直接拿来用的方案不多,于是自己折腾了一段时间,最后定下两条路:Docker 容器和 Linux 独立用户降权。下面是原理、配置,以及我实际用下来的一些经验。

一、风险点

Pi 官方文档里有一节专门讲 No Built-in Sandbox,原文大意是:

Pi 不包含内置沙箱。内置工具可以读文件、写文件、改文件、执行 shell 命令,使用的就是 pi 进程的权限。扩展是以相同权限运行的 TypeScript 模块。

官方还解释了为什么不做进程内沙箱:半成品的沙箱容易被当成真正的安全边界,但它照样要用宿主的 shell、文件系统和凭据,所以隔离得靠操作系统,或者容器/虚拟化这一层。

顺带澄清一个容易搞混的地方:Pi 的 project trust(启动时问你要不要信任当前项目)不是沙箱,它只管防止仓库在你批准之前偷偷加载扩展或改配置。

真正要担心的是这几类:

  • 越权读取:~/.ssh、~/.aws、项目里的 .env、数据库密码,都可能在它一次 cat 之后进了模型上下文。
  • 越权写入和误删:改坏系统配置、删掉别的项目、覆盖家目录里的其他文件。模型经常把路径算错,容易删错东西。
  • 提示注入与供应链:第三方扩展、MCP server、skills 和 Pi 同权限运行;仓库里的 README、代码注释、构建输出都可以藏指令,官方也说了防不住。

指望”我只让 Agent 干安全的事”是不行的。只要它读到的内容不完全可控,就该默认它哪天会执行一条你没写过的命令。

二、方案一:Docker 容器隔离

1. 思路

让 Pi 整个跑在容器里,只把当前项目目录挂进去,再单独给它一份配置目录。宿主上的 ~/.ssh、~/.aws 不挂载,容器里压根没有这些路径,也就谈不上读到或者修改。

2. 镜像与启动

镜像不复杂,装好 Node 和 Pi 就行。唯一的坑是别用 root 跑:建一个和宿主 UID 对齐的普通用户,否则写回宿主目录的文件全是 root 属主,后面收拾起来很烦。

FROM ubuntu:24.04

# Node 22 + 基础工具
RUN apt-get update && apt-get install -y --no-install-recommends \
      ca-certificates curl git ripgrep build-essential \
 && curl -fsSL https://deb.nodesource.com/setup_22.x | bash - \
 && apt-get install -y --no-install-recommends nodejs \
 && rm -rf /var/lib/apt/lists/*

# 安装 pi
ARG PI_VERSION=0.85.1
RUN npm install -g @earendil-works/pi-coding-agent@${PI_VERSION}

# 非 root 用户,UID 与宿主对齐,避免挂载目录出现 root 属主文件
ARG HOST_UID=1000
RUN useradd -m -u ${HOST_UID} -s /bin/bash dev
USER dev
WORKDIR /home/dev/workspace
ENTRYPOINT ["pi"]

构建完之后,配置和凭据也单独放一份,不要挂宿主的 ~/.pi:

docker build --build-arg HOST_UID=$(id -u) -t pi-sandbox .

# 专用的 Pi 配置/凭据目录,不要直接用宿主的 ~/.pi
mkdir -p ~/pi-data/pi

cd ~/code/my-project
docker run --rm -it \
  -v "$PWD:/home/dev/workspace" -w /home/dev/workspace \
  -v "$HOME/pi-data/pi:/home/dev/.pi" \
  pi-sandbox

--rm 表示容器退出即删。容器里临时装的东西会丢,项目代码和 ~/pi-data 是持久保存的。

3. 几个坑

  1. 别挂 docker.sock。 很多人为了让容器里也能跑 docker,把 /var/run/docker.sock 挂进去。这等于把宿主的 root 权限交出去,隔离白做。
  2. 别直接挂宿主的 ~/.pi。 那里有你所有的会话、设置和凭据。需要哪个凭据,就往 ~/pi-data/pi 里放哪个。当然了如果你使用docker,可以直接删除宿主机的Pi。
  3. git 同理。要让 Agent push 代码,就单独生成一把只对单个仓库有效的 deploy key,别把能开所有仓库的私钥塞进去,出事后的损失不是一个量级。
  4. 构建时记得用 .dockerignore 把数据目录排除掉。否则含密钥的文件会被打进镜像层,docker history 一翻就出来了。

4. pi-docker:完整实现

上面这个最小版本只能算跑通。真正用起来还有一堆琐事:在任意目录一条命令启动、同时开几个项目、dev server 的端口映射、容器用完自动回收……我把这些塞进了一个小工具 pi-docker,顺带把日常最常用的几条命令也封了进去。用法:

cd 任意项目目录
pi-docker        # 启动 Pi 会话(一次性容器,退出即焚)
pi-shell         # 进容器交互 shell,用于试装软件
pi-up / pi-exec  # 常驻容器,可在单独终端跑 dev server
pi-doctor        # 自检路径、凭据、镜像

容器里还跑了个看门狗:没有活跃会话、空闲超过 15 分钟,就自动停掉并删除,免得用久了容器越积越多。

pi-docker 下载地址见:pi-docker.zip

三、方案二:Ubuntu 独立用户降权

如果你的开发机就是 Linux,又不想为每个项目都开容器,还有另一种更轻的做法:不换环境,只换身份,让 Pi 以一个专用的低权限用户运行。

1. 工作原理

Linux 判断一个进程能不能读某个文件,看的是启动它的用户身份(UID)。Pi 以 pi-agent 的身份跑,而你的 ~/.ssh 属主是你自己,内核就会在它试图打开文件的时候直接拒绝。这就是全部的安全来源。

2. 三个细节

  1. 项目不能放在家目录下。Ubuntu 家目录默认 750 甚至 700,other 没有任何权限,pi-agent 连门都进不去。共享工作区得放在双方都能进的地方,比如 /srv/dev。
  2. 新建文件的归属会让 git 卡住。pi-agent 建出来的文件,属主和属组都是它自己;默认的 umask 022 又让文件是组只读。结果你既不是属主、也不在它的组里,改都改不动,git 提交自然失败。解决办法是两件事一起做:给共享目录加 setgid 位,让新建文件自动继承 dev 组;再把 pi-agent 的 umask 改成 002,让新文件组内可写,这两件缺一不可。
  3. sudo 会重置 PATH。 它默认把 PATH 换成 secure_path,可以把pi装在系统级目录,或是按我下面的单独设置PATH目录。

3. 配置脚本

这套配置我整理成了脚本,直接跑就行(把 WORKSPACE 换成你自己的共享目录):

#!/usr/bin/env bash
set -euo pipefail

AGENT_USER="pi-agent"
SHARED_GROUP="dev"
WORKSPACE="/srv/dev"

# 1. 建组、建用户(锁定密码,禁止登录)
sudo groupadd -f "$SHARED_GROUP"
if ! id "$AGENT_USER" &>/dev/null; then
  sudo useradd -m -s /bin/bash -g "$SHARED_GROUP" "$AGENT_USER"
  sudo passwd -l "$AGENT_USER"
fi
sudo usermod -aG "$SHARED_GROUP" "$USER"
sudo usermod -aG "$SHARED_GROUP" "$AGENT_USER"

# 2. 共享工作区:双方可读写,setgid 让新文件继承组
sudo mkdir -p "$WORKSPACE"
sudo chgrp -R "$SHARED_GROUP" "$WORKSPACE"
sudo chmod -R g+rwX "$WORKSPACE"
sudo chmod g+s "$WORKSPACE"

# 3. umask 002:新文件组内可写(与 setgid 缺一不可)
grep -q '^umask 002' "/home/$AGENT_USER/.bashrc" \
  || echo 'umask 002' | sudo tee -a "/home/$AGENT_USER/.bashrc"

# 4. 继承 git 身份
sudo tee "/home/$AGENT_USER/.gitconfig" >/dev/null <<EOF
[user]
  name = $(git config user.name 2>/dev/null || echo 'Your Name')
  email = $(git config user.email 2>/dev/null || echo 'you@example.com')
EOF

# 5. 专用 SSH key,去 GitHub 配成单仓库 deploy key
if [ ! -f "/home/$AGENT_USER/.ssh/id_ed25519" ]; then
  sudo -u "$AGENT_USER" ssh-keygen -t ed25519 -N '' -f "/home/$AGENT_USER/.ssh/id_ed25519"
  sudo -u "$AGENT_USER" cat "/home/$AGENT_USER/.ssh/id_ed25519.pub"
fi

4. 无感 wrapper

每次手写 sudo -u pi-agent 实在太烦,我索性写了个 pi-safe 代替 pi,它会顺手把当前目录纳入共享组,用起来和原来几乎没区别:

#!/usr/bin/env bash
set -euo pipefail

AGENT_USER="pi-agent"
AGENT_HOME="/home/pi-agent"
PI_BIN="$AGENT_HOME/.local/share/pi-node/node-v22.23.2-linux-x64/bin/pi"
SHARED_GROUP="dev"
BREW="/home/linuxbrew/.linuxbrew"

p="$(pwd)"
sudo chgrp -R "$SHARED_GROUP" "$p" 2>/dev/null || true
sudo chmod -R g+rwX "$p" 2>/dev/null || true
sudo chmod g+s "$p"

# 单独设置PATH,ENV变量,如果有其他的类似JAVA_HOME,可以自行添加
exec sudo -u "$AGENT_USER" env \
  HOME="$AGENT_HOME" \
  PATH="$AGENT_HOME/.local/share/pi-node/node-v22.23.2-linux-x64/bin:$BREW/opt/python@3.12/libexec/bin:$BREW/bin:$BREW/sbin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin" \
  HOMEBREW_PREFIX="$BREW" \
  HOMEBREW_CELLAR="$BREW/Cellar" \
  HOMEBREW_REPOSITORY="$BREW/Homebrew" \
  bash -c 'umask 002; exec "$0" "$@"' "$PI_BIN" "$@"
chmod +x ~/bin/pi-safe
cd /srv/dev/my-project
pi-safe 

四、两种方案对比与选择

两种方案我都用了不短时间,下面是我自己的体感:

维度 Docker 容器 Linux 降权用户
隔离强度 强 中
文件权限摩擦 低(UID 对齐后基本无感) 中(需 setgid + umask + 共享组)
环境一致性 强(镜像锁死工具链) 弱(用宿主工具链)
资源开销 中 几乎为零
跨平台 macOS / Windows / Linux 仅 Linux
内核漏洞逃逸 有风险(与宿主共享内核) 不适用
本地服务 / 端口 需要端口映射 原生支持
对宿主的副作用 几乎无 Agent 装的包会留在系统里

我日常主力是 Docker 模式,隔离干净,代价是每次 Pi 升级都得重新构建镜像,或是安装软件也要重构镜像。这个成本不小,但有AI也很方便。Linux 上如果嫌麻烦,降权用户也够用,但它会随着时间,开发环境会变乱。两者你可以自行尝试选择。

五、参考资料

  1. Pi 官方安全文档(No Built-in Sandbox)
  2. Pi 官方容器化文档
  3. Pi 项目主页

文章未经特殊标明皆为本人原创,未经许可不得用于任何商业用途,转载请保持完整性并注明来源链接 《ITechLib》

Mac环境ComfyUI部署JoyCaption2实现图片反推

一、图片反推利器JoyCaption2介绍

JoyCaption2是一款很优秀的图片反推模型,可以根据图生文(或图片打标),支持多模态语义理解、智能标签优化、并支持与ComfyUI集成。下面介绍Mac环境下插件的安装和使用:

二、软件版本信息

三、安装步骤:

可参考官方文档 进行手工安装,下面内容针对Mac环境进行了适当调整:

1. 插件安装

把仓库下载克隆到 custom_nodes 子文件夹下:

cd custom_nodes
git clone https://github.com/EvilBT/ComfyUI_SLK_joy_caption_two.git

修改requirements.txt,根据mac后面遇到的问题进行修改: 将huggingface_hub改为>=,将bitsandbytes版本改为>=0.42.0,因为mac没有0.44.1的版本,具体如下:

huggingface_hub>=0.23.4
transformers>=4.44.0
numpy==1.26.4
sentencepiece==0.2.0
pillow>=10.4.0
bitsandbytes>=0.42.0
peft>=0.12.0

安装依赖:

pip install -r ComfyUI_SLK_joy_caption_two\requirements.txt

2. 模型下载

  • google/siglip-so400m-patch14-384:视觉编码器,使用huggingface-cli来整体下载,并把siglip-so400m-patch14-384内的全部文件复制到models/clip/siglip-so400m-patch14-384
  • unsloth/Meta-Llama-3.1-8B-Instruct:语言大模型,务必下载此 8B 版本,bnb-4bit 版本因 bitsandbytes 版本问题在 Mac 上不被支持。下载完成后,将整个文件夹内容复制到models\LLM\Meta-Llama-3.1-8B-Instruct路径下。
  • Joy-Caption-alpha-two:核心推理模型(版本2024-09-26a),必须手动下载:鉴于该工程在 huggingface 上属于 space,下载指令如下:huggingface-cli download --token 替换为你的token spaces/fancyfeast/joy-caption-alpha-two --local-dir joy-caption-alpha-two;然后将 Joy-Caption-alpha-two 下的cgrkzexw-599808 文件夹的所有内容下载复制到models/Joy_caption_two 下。

3.重启ComfyUI生效

四、示例工作流

工作流支持图生文,批量处理,个性化扩展配置。

下载地址如下:JoyCaption2-comfyUI-example.json

五、常见问题解决:

  1. ImportError: Using bitsandbytes 4-bit quantization requires the latest version of bitsandbytes: pip install -U bitsandbytes:此问题是由于 Mac 系统中的 bitsandbytes 版本过低,且当前无更高版本可供使用。解决方案为采用unsloth/Meta-Llama-3.1-8B-Instruct的 8B 版本,避免使用 bnb-4bit 版本。
  2. 报错huggingface_hub版本太低:因其他工具依赖更高版本,可通过修改 requirements 文件,将 huggingface_hub 改为 “>=” 形式。

参考资料:

文章未经特殊标明皆为本人原创,未经许可不得用于任何商业用途,转载请保持完整性并注明来源链接 《ITechLib》