📌 本文重點
- 將 Agent 視為 K8s Job 可利用現有雲原生能力
- 單靠 LLM sandbox 不足,必須做 OS/網路層隔離
- wire format 會吃掉 port 是「Agent Egress Illusion」關鍵風險
- eBPF 或 sidecar proxy 可實作可驗證的 egress 控制
在多代理(multi-agent)系統裡,真正麻煩的不是「叫一個 LLM 幫你想辦法」,而是如何安全地讓一堆有能力執行程式碼的 Agent,在企業網路裡被可靠調度、精準限權、可觀測且可驗證地被關起來。Google AX 給了一個值得抄的架構:把 Agent 當 Kubernetes Job 來排程,所有工具和憑證都以最小權限下放,同時嘗試提供精細網路出口控制——但也踩到一個 wire format 的安全坑。
這篇文章會聚焦三件事:
- 設計層:為何要把 Agent 當 Job 調度、如何在企業環境做最小權限隔離。
- 實作層:以 AX 風格實作一個精簡版 Go agent runner,含 agent spec、network policy、執行沙盒 配置樣板。
- 資安與踩坑:解析「Agent Egress Illusion」中 port 資訊丟失 bug,示範用 eBPF 或 sidecar proxy 做真正可驗證的 egress 控制,以及應自動化的安全檢查。
重點說明
1. 為何把 Agent 當 K8s Job 調度?
AX 的核心設計是:每個 Agent/任務就是一個可觀測、可重試的 Job,而不是一個長駐服務。這帶來幾個直接好處:
- 資源隔離自然落在 Pod/Job 邊界:CPU / memory / volume /
ServiceAccount都用現有 K8s 原生能力。 - 權限最小化更容易實作:不同工具/憑證綁在不同 Job template 上,下發時只給需要的那一份。
- 重試與狀態同步內建:Job 狀態是可觀測事件流,AX 做的是把這些 event 封裝成 Agent 狀態機(state machine),方便 orchestrator 決策下一步。
對你的專案來說,這代表:
- 不用自己重新發明一套 job scheduler,照 K8s Job 範式套一層 Go orchestration 即可。
- 多 Agent 協作 = 多 Job 工作流,你可以在 Go 裡用 DAG / state transition 描述整個流程。
💡 關鍵: 把每個 Agent 當一次性 Job,可直接沿用 K8s 的資源隔離與重試機制,避免重造 scheduler 輪子。
2. AX-style Agent Spec 與精細網路控制
AX 用一個類似「AgentSpec」的結構描述每個 Agent:
- 要跑的 tool image / command
- 限制的 network egress 規則(domain / IP / port)
- 授權的 secrets / credentials
它宣稱可以做到「精細網路出口控制」,但在 wire format 的 protobuf/JSON 中,port 資訊沒被帶到真正執行的層級,導致你以為只開 443,實際上所有 port 都能出去,這就是「Agent Egress Illusion」。
這個教訓是:
- 不要只相信 API 層的 policy,必須驗證到 socket 層。
- 對自己寫的 orchestrator,要有「從 UI → spec → wire → kernel」的完整 trace,確認資訊沒有在中途被丟失或過度簡化。
3. 為何單憑 LLM sandbox 不夠?
LLM sandbox 做的通常是:
- 限制 prompt 能呼叫哪些工具
- 限制工具的參數範圍
但實務上常見攻擊路徑是:
- 反向殼層(reverse shell)回到攻擊者機器
- 內網掃描打開更多攻擊面
- 泄漏環境中的 API key / DB credentials
這些都不會被「prompt-sandbox」阻止,只有 OS / network layer 的硬性隔離才有用:namespace、cgroup、iptables、eBPF、sidecar proxy 等。
實作範例:精簡版 AX-style Agent Runner(Go)
下面是一個簡化示意,展示如何在自己專案做一個 AX 風格的調度層。重點在 AgentSpec、NetworkPolicy、以及 runner 如何執行與觀測。
1. 定義 Agent Spec 與 Network Policy
// AgentSpec 描述一個可被調度的 Agent 任務
type AgentSpec struct {
ID string
Image string
Command []string
Env map[string]string
Network NetworkPolicy
Secrets []string // reference to secret IDs
}
// NetworkPolicy 為每個 Agent 定義 egress 規則
type NetworkPolicy struct {
AllowedDestinations []DestinationRule
}
type DestinationRule struct {
Host string // example.com or 10.0.0.0/24
Port int // 0 表示任何 port(強烈不建議在生產環境使用)
Proto string // tcp/udp
}
這個 Spec 可以直接當成你自己的 API 層 contract。關鍵是:
- 不要在任何一層「自動忽略」Port,即使使用者沒填,也要在 schema 上明確設定預設值與含義。
- 在轉成 wire format(JSON / protobuf)時,保證欄位不被省略。
2. Runner:把 Agent Spec 下放到 Worker 節點
下例示範一個最小可用 runner:從 queue 拿到 AgentSpec,spawn 一個 container(可以是 K8s Job、或本機用 container runtime)並附帶 network policy:
type Runner struct {
// 抽象的 container 執行介面,可以是 K8s client,也可以是本機 Docker
Exec ExecBackend
}
type ExecBackend interface {
Run(spec AgentSpec) (RunHandle, error)
}
// K8sJobBackend 是一個以 K8s Job 為基礎的實作
type K8sJobBackend struct {
// kube client, omitted
}
func (b *K8sJobBackend) Run(spec AgentSpec) (RunHandle, error) {
job := BuildK8sJobFromAgent(spec)
// 將 NetworkPolicy 轉成 K8s NetworkPolicy 或 CNI 插件規則
np := BuildNetworkPolicyFromAgent(spec)
// 建議:先創 NetworkPolicy,再創 Job
if err := b.applyNetworkPolicy(np); err != nil {
return RunHandle{}, err
}
if err := b.createJob(job); err != nil {
return RunHandle{}, err
}
return RunHandle{ID: spec.ID}, nil
}
其中 BuildNetworkPolicyFromAgent 是重點,用來把 AX-style 的 egress 規則真正落到 K8s 或底層 CNI:
func BuildNetworkPolicyFromAgent(spec AgentSpec) *netv1.NetworkPolicy {
// 示意:將每個 DestinationRule 轉為 egress rule
// 注意:K8s NetworkPolicy 的 port 是獨立欄位,不能在轉換時丟掉
}
如果你不想依賴 K8s NetworkPolicy,也可以直接用 sidecar proxy 控制:
- 為每個 Agent Pod 加一個 envoy/mitmproxy sidecar。
- 在 sidecar config 中生成 per-Agent egress ACL。
3. 觀測與狀態同步
AX 的設計亮點之一是狀態同步與觀測。你可以在 runner 裡加上一層事件流:
type AgentStatus string
const (
StatusPending AgentStatus = "PENDING"
StatusRunning AgentStatus = "RUNNING"
StatusSuccess AgentStatus = "SUCCESS"
StatusFailed AgentStatus = "FAILED"
)
type StatusStore interface {
Update(id string, status AgentStatus, meta map[string]string) error
}
func (r *Runner) RunAndWatch(spec AgentSpec, store StatusStore) error {
handle, err := r.Exec.Run(spec)
if err != nil {
return err
}
store.Update(spec.ID, StatusRunning, nil)
go func() {
result := handle.Wait()
if result.Err != nil {
store.Update(spec.ID, StatusFailed, map[string]string{"error": result.Err.Error()})
} else {
store.Update(spec.ID, StatusSuccess, nil)
}
}()
return nil
}
這樣你即可在前端或控制平臺上,實時看到每個 Agent 的狀態,並進行工作流編排(例如下一個 Agent 只有在上一個成功時才啟動)。
資安與踩坑:Agent Egress Illusion 與真正可驗證的控制
1. Agent Egress Illusion:port 資訊在 wire format 被吃掉
簡化來說,問題長這樣:
- Config / UI 層支援 host+port,例如:
example.com:443。 - Spec 序列化成 wire format(JSON / protobuf)時,只保留
example.com,port 被 default 掉(例如0或空)。 - 執行層的 network module 把「空 port」解讀為「any port」。
結果:你以為自己寫了 allow example.com:443,實際執行的是 allow example.com:*,甚至 allow *:*。
避免同樣踩坑,最實際的做法是:
- 在 schema 層面強制 port 必填或有明確預設邊界(例如只允許
80/443)。 - 在 wire / decode 層加 結構化驗證:任何 Host 沒設定 Port 就拒絕部署。
- 寫端到端測試,驗證「指定 port 不同時,實際 socket 行為不同」。
💡 關鍵: 只要 port 在序列化或 decode 過程被默默 default,實際執行的網路權限就會遠超過 UI 上看到的設定。
2. 用 eBPF 做真正可驗證的 egress 控制
在 Linux 上,可以用 eBPF 寫一段針對 connect() 的 hook,根據 Agent ID + 目的 host/port 做精細控制。示意邏輯(偽 C):
int sock_connect(struct bpf_sock_addr *ctx) {
__u16 dport = bpf_ntohs(ctx->user_port);
struct agent_policy *policy = lookup_policy_for_current_pid();
if (!policy) return 0; // 無 policy 的情況下可選擇允許或拒絕
if (!policy_allows(policy, ctx->user_ip4, dport)) {
return -EPERM; // 直接阻擋
}
return 0;
}
然後在 Go runner 裡,為每個 Agent 產生對應的 eBPF policy map:
func InstallEBPFPolicy(agentID string, net NetworkPolicy) error {
// 1. 將 AgentID 映射到一個 cgroup 或 pid namespace
// 2. 將 DestinationRule 寫入 eBPF map
return nil
}
這樣你可以做到:
- policy 與 socket 在同一層被驗證,不再依賴高層配置是否完整。
- 可以收集 eBPF 事件,作為 shadow env 觀測:任何違反 policy 的連線都會被紀錄,甚至被 fuzz 測試工具捕捉。
3. Sidecar Proxy 方案(較好上手)
如果 eBPF 對團隊太重,你可以選擇 sidecar proxy:
- 每個 Agent Pod 都透過 sidecar 發出所有外部連線。
- AX-style
NetworkPolicy轉成 proxy 的 ACL:
# envoy filter pseudo config
- name: agent-egress-filter
typed_config:
allowed_destinations:
- host: example.com
port: 443
- host: api.internal
port: 8443
對開發者來說,這個方案的優點是:
- 配置全部在 user space,容易 debug。
- 可以在 staging 做 policy fuzzing:自動產生隨機目標,確認 proxy 確實阻擋。
建議與注意事項
1. 三件必做的自動化安全檢查
- 端到端 egress 測試:
- 建一個專門的 Agent,用來嘗試從各種 host/port 打出去。
-
CI 裡檢查:不應該開放的目標全部連線失敗。
-
policy fuzzing:
-
針對你的
NetworkPolicy模組,用模糊測試工具產生多組 host/port 組合,確認 decode / merge 過程不會產生意外的「any port」。 -
shadow env 觀測:
- 在 staging/灰度環境,把所有 egress 事件打到一個集中 log。
- 針對異常 pattern(例如 Agent 對內網 IP 連線)設 alert。
2. 多代理環境常見安全誤區
- 只做 prompt 限制不做網路限制:LLM 只要能呼叫
bash或python,就能繞過任何 prompt policy。 - 所有 Agent 共用一組 API key 或
ServiceAccount:一旦有 Agent 被利用,就等於全網被打開。 - 忽略內網掃描:很多團隊只防外聯,不防 Agent 對內網執行
nmap/port scan,結果是 lateral movement 更容易。
實務建議:
- 每個 Agent 類型一個 最小權限
ServiceAccount,不要共用。 - 有寫檔需求的 Agent,只給 只讀/只寫的特定 volume,避免觸及主機檔案系統。
- 將所有 egress 限制在白名單 domain,而不是黑名單。
💡 關鍵: 多代理環境的核心風險在橫向移動與憑證濫用,因此需以最小權限帳號和白名單 egress 為設計預設。
3. 如何在現有專案落地 AX-style 架構
- 已有 K8s:
- 直接定義自己的 Agent CRD + Controller,用上文的
AgentSpec當 schema,K8s Job 當執行層。 -
接上
NetworkPolicy或 sidecar proxy,做 per-Agent egress。 -
沒有 K8s、純 VM/裸機:
- 用 Go Runner + container runtime(Docker /
containerd),以namespace+iptables或eBPF做隔離。 - 同樣透過
AgentSpec描述任務,確保 spec → runtime 的 mapping 可觀測。
核心結論:
- 把 Agent 當 Job 調度,可以用現有雲原生堆疊快速搭起可擴充的多代理平臺。
- LLM sandbox 絕對不夠,必須有實體網路與系統層的防護,並且用
eBPF或 sidecar 這類「可驗證」的手段落實。 - 在設計 Agent 平臺時,wire format 不可忽視,任何欄位(尤其是 port)一旦在序列化過程中被吃掉,就會變成下一個「Agent Egress Illusion」。
🚀 你現在可以做的事
- 在現有專案中定義一個
AgentSpec結構,試著用 K8s Job 或 Docker 實作最小可用 runner- 為某個測試 Agent 建立白名單 egress 規則,實驗一版 sidecar proxy 或
NetworkPolicy的落地做法- 寫一個簡單的 egress 測試 Agent,加入 CI 流程,驗證你的網路策略沒有出現「any port」的隱形放寬

