iT邦幫忙

2026 iThome 鐵人賽

DAY 27
0
AI Engineering

Harness Engineering × Pi Agent 實戰:打造可觀測、可評估的 AI Coding Agent系列 第 27 篇

Day27:字串規則擋不住繞路:Agent 沙箱真正該隔離的是什麼?

  • 分享至 

  • xImage
  •  

閘門擋得住手滑,擋不住惡意

Day25 做了一個刪除閘門,Day26 量到它擋錯了 8 次、也漏掉了它看不懂的寫法。那個閘門有一個本質限制,我必須講清楚:

它是「政策」,不是「邊界」。

它靠比對指令字串來判斷該不該擋。Day26 的 v2 補掉了 find -delete,但同一批執行裡,agent 隨手寫的 find ... -print0 | xargs -0 rm -rf 就直接穿過去了。而後面還排著 sh -c、python -c "shutil.rmtree(...)"、make clean、git clean -xdf——每補一條規則,誤擋正確指令的機率就上升一分,而繞路的寫法是無窮的。

真正的邊界只有一種:讓 agent 連「碰到」那些檔案的能力都沒有。那就是沙箱。

今天先看 Pi 怎麼把沙箱做成一個「可以換掉的零件」。

Pi 的態度:不做半套

Day19 引用過 Pi 安全文件裡那句話,這裡值得再看一次:

Pi does not include a built-in sandbox... A partial in-process sandbox would be easy to misunderstand as a security boundary.

Pi 不內建沙箱,理由不是做不到,而是做半套比不做更危險——使用者會以為自己被保護了。

它的替代方案是:把「工具實際在哪裡執行」抽象成一個介面,讓你自己換掉。

那個介面只有兩件事

ExecutionEnv 的定義短得讓人意外:

/** Filesystem and process execution environment used by the harness. */
export interface ExecutionEnv extends FileSystem, Shell {}

就是「檔案系統」加「跑指令」。Shell 只有兩個方法(exec 和 cleanup),FileSystem 有十幾個:

cwd / absolutePath / joinPath / canonicalPath / exists / fileInfo / listDir
readTextFile / readTextLines / readBinaryFile
writeFile / appendFile / renameFile / remove / createDir
createTempDir / createTempFile / cleanup

Day12 拆的那四個內建工具,沒有一個直接碰 node:fs。 read 呼叫的是 env.readTextFile(),bash 呼叫的是 env.exec()。工具只認識這個介面。

這就是整個沙箱設計的關鍵:換掉 env,工具的程式碼一行都不用改,但它們做事的地點全部換了。

四個值得抄的設計決定

這個介面有幾個細節,任何要做遠端/隔離執行的人都會用到:

一、每個操作都回傳 Result,不丟例外。

readTextFile(path: string, abortSignal?: AbortSignal): Promise<Result<string, FileError>>;

為什麼?因為當 env 可能是「另一台虛擬機」或「遠端容器」時,失敗是正常路徑而不是異常:連線斷掉、VM 沒起來、路徑在對面不存在。用回傳值強制每個呼叫點處理失敗,比散落的 try/catch 可靠。

二、錯誤碼與作業系統無關。

type FileErrorCode = "aborted" | "not_found" | "permission_denied"
                   | "not_directory" | "is_directory" | "invalid" | "not_supported" | "unknown";

工具不需要去解析 ENOENT、EACCES 這些平台字串,也不用管對面是 Linux 容器還是 Windows 主機。抽象層要抽的不只是操作,還有錯誤。 這是很多自製抽象層會漏掉的一半。

三、每個方法都吃 abortSignal。
使用者按 Ctrl+C 的時候,在遠端跑的 grep 也要停下來。取消能力必須一路傳到最底層,不能只在最上面做。

四、連暫存檔都在介面裡。
createTempDir / createTempFile 是 env 的一部分,而不是呼叫 os.tmpdir()。理由很實際:如果工具在容器裡跑,暫存檔卻建在主機上,那個檔案在容器裡根本看不到。一旦你有了執行環境的概念,所有「檔案在哪裡」的假設都要跟著搬家。

三種隔離方式,差別在「誰被關起來」

Pi 的文件把可行做法分成兩類,很值得對照著看:

方式 被隔離的是 代價
Gondolin extension 只有工具執行(透過換掉 env,路由進 Linux micro-VM) 需要 QEMU;只覆蓋內建工具
Plain Docker 整個 pi 程序 憑證要進容器;設定與 session 也在容器裡
OpenShell 整個程序,外加政策控制 需要 gateway

Gondolin 是「agent 的腦留在主機上,手伸進 VM 裡做事」;Docker 是「連人帶腦整個關進去」。

這裡有一個容易踩雷的細節,文件寫得很清楚:

Extensions run wherever the pi process runs.

也就是說,用 Gondolin 這種「只路由工具」的方案時,你自己寫的 extension 工具還是在主機上跑。Day25 那個閘門是 extension,所以它在主機端;但如果我寫的是一個會刪檔案的自訂工具,它不會被 Gondolin 關進 VM。隔離的邊界在哪裡,要能講得出來,否則就是假的安全感。

我這台機器上的現實

原本大綱寫的是「host vs Docker vs Gondolin 三方比較」。實際做下去發現 Gondolin 在這台機器上跑不了:它是 Linux micro-VM,需要 QEMU,而我在 Windows 上;npm 上的 runner 套件也是 Linux-only。

所以明天改成兩方比較:host vs Docker。這不是偷懶,而是一個對讀者有用的資訊——在 Windows 上做 agent 隔離,Docker 幾乎是唯一現實的選項。

Docker 這條路也有它自己的麻煩,而且正好呼應「隔離的代價」這個主題:容器裡的 Pi 需要憑證才能呼叫模型。官方文件提供兩種做法,一種是把 API key 傳進容器,一種是掛載主機的設定目錄——而後者等於把主機的登入憑證交給容器。

隔離不是免費的:你關住 agent 的同時,也要把它需要的東西一件一件送進去,而每送一件,邊界就少一分。

明天

Day28 實際做:把量測台的任務放進 Docker 容器跑,跟主機上跑的結果比較成功率、成本與時間,並且把「憑證怎麼進容器」這件事處理掉。


上一篇
Day26:安全閘門為何讓成功率下降?一個不懂 Shell 的規則如何誤擋 Agent
下一篇
Day28:把 Coding Agent 放進 Docker:執行成本很低,憑證與網路邊界呢?
系列文
Harness Engineering × Pi Agent 實戰:打造可觀測、可評估的 AI Coding Agent 共 30 篇
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言