用 Docker 做一次性安全 AI Agent 沙盒

用 Docker 做一次性安全 AI Agent 沙盒

📌 本文重點

  • Agent 執行程式必須強制進 Docker 沙盒
  • 工具層限制不夠,需從執行環境畫界
  • 以最小權限、隔離與審計集中管理程式執行

第一個痛點很直接:讓 Agent 能跑程式,又不讓它毀你主機、偷你資料或亂打外部服務。從 Rovo 被 PDF 隱藏指令牽著走,到 OpenClaw 為了搶健身房名額去「半駭半腳本」打 API,核心問題都是:你給了 Agent 工具權限,它就有能力放大任何輸入或目標的風險。本文的結論是實作面很務實:把「執行程式」這件事強制丟進一次性的 Docker 容器沙盒,搭配最小權限、網路/檔案隔離與審計,讓 Agent 成為受控服務,而不是在主機上為所欲為的黑盒。

💡 關鍵: 把所有「程式執行」集中到一次性沙盒裡,是把 Agent 從高風險黑盒變成可控服務的核心做法


重點說明

1. 為什麼需要「一次性沙盒」而不是單純 API 限制

  • Rovo 的案例:攻擊者把指令藏在 PDF,Agent 幫忙從 Jira/Confluence 撈敏感資料,自動送到外部伺服器且不留操作痕跡。你就算限制 tool schema,還是擋不住「正當 API 被惡意使用」。
  • Gym hack 案例:OpenClaw 收到「幫我排到前面」這種模糊目標,就會自然探索網站邊界。只要你給它 HTTP client 或瀏覽器能力,沒有技術上的「這裡不能做」的牆。
  • 結論:工具層級的限制不夠,你必須在「執行環境」上畫界:這段 code 只能在隔離的容器裡跑;這個容器只能存取限定資料;超時就殺掉;所有輸出都被記錄與審核。

💡 關鍵: 單靠限制工具參數無法阻止「正當 API 被惡用」,必須從執行環境切斷風險擴散路徑


Docker 沙盒設計的核心原則

1. 最小權限 + 只讀檔案系統

  • 使用非 root user、關掉不必要的 capabilities(CAP_NET_ADMIN 等)。
  • 根檔案系統 readonly,只有特定目錄(例如 /tmp/work)可寫,避免 Agent在容器內長期累積垃圾或做持久化攻擊。

2. 網路與檔案系統隔離

  • 默認 無外網,只有明確允許的出口(例如企業 MCP / API gateway)。
  • 不掛宿主機目錄,尤其不要掛 /var/run/docker.sock,這是最常見的逃逸坑。
  • 針對多 Agent 系統,容器間一律不互通,避免 Agent 彼此側通道傳遞資料。

3. 資源限制 + 超時

  • 用 --cpus、--memory、--pids-limit 等參數防止無限 fork/吃爆 RAM。
  • 在工具層加 硬超時(例如 5–30 秒), timeout 就 docker kill。

4. 日誌與審計

  • 把 Agent 的 code、stdin、stdout、stderr 全部打包成事件,寫到集中式 log / SIEM。
  • 在多 Agent 架構中,透過 MCP / 工具網關,把「誰在什麼上下文下開了沙盒、跑了什麼」都留下 audit trail。

多 Agent 系統裡的「動態沙盒策略」

多 Agent coding 常見失敗點之一是:角色設計清楚,但行為邊界沒明確技術約束。建議是把沙盒視為一個「策略開關」:

  • 規劃幾種沙盒 profile:
  • analysis:只允許在容器內跑靜態分析工具,完全沒網路。
  • integration-test:允許打 staging 環境,有限 CPU/MEM。
  • prod-readonly:只能打只讀 API(例如查詢服務),禁止寫操作。

  • Controller Agent 不直接執行程式,而是呼叫一個 run_in_sandbox(profile, code) 工具,由工具決定 spawn 哪種 Docker 容器。

  • 所有 Agent 的「可執行能力」集中到這一個工具上,便於與企業現有 CI/CD、MCP、監控系統整合,把安全策略集中管理。

💡 關鍵: 用多種沙盒 profile 對應不同 Agent 角色與任務,才能在安全與靈活之間做細緻權衡


實作範例

以下用 Python/Node 示範如何把 LLM 工具調用綁定到「在新容器內執行 code」。

1. Docker 镜像設計

這是一個極簡、偏安全的 Python 執行沙盒:

# Dockerfile.sandbox
FROM python:3.11-slim

# 建立非 root 使用者
RUN useradd -m sandbox && mkdir -p /app && chown -R sandbox:sandbox /app
USER sandbox

WORKDIR /app

# 只安裝必要套件
RUN pip install --no-cache-dir pytest requests

# 預設為只讀根檔案系統;允許 /tmp/work 可寫(由 run script 控制)
ENV PYTHONUNBUFFERED=1
CMD ["python", "-u", "main.py"]

注意:

  • 不要在這個鏡像裡放企業敏感設定檔或憑證。需要時改用 API gateway + 短期 token。
  • main.py 可以是一個固定的 runner,從環境變數或掛載目錄讀入待執行的 user code。

2. Python:在新容器內執行 Agent 產生的程式碼

假設你在後端定義了一個工具 run_code_in_sandbox 給 LLM 使用:

import subprocess
import tempfile
import uuid
from pathlib import Path

SANDBOX_IMAGE = "my-org/agent-sandbox:latest"

def run_code_in_sandbox(code: str, timeout_sec: int = 10) -> dict:
    # 為這次執行建立一次性工作目錄
    workdir = Path(tempfile.mkdtemp(prefix="agent-sandbox-"))
    script_path = workdir / "main.py"
    script_path.write_text(code, encoding="utf-8")

    container_name = f"agent-sandbox-{uuid.uuid4()}"

    cmd = [
        "docker", "run", "--rm",
        "--name", container_name,
        # 資源限制
        "--cpus", "0.5",          # 最多半顆 CPU
        "--memory", "512m",       # 限制記憶體
        "--pids-limit", "128",    # 限制子行程
        # 禁用網路:完全隔離
        "--network", "none",
        # 根檔案系統掛為 readonly
        "--read-only",
        # 掛載工作目錄到 /app,並提供 /tmp/work 可寫
        "-v", f"{workdir}:/app:ro",
        "-v", f"{workdir}/tmp:/tmp/work:rw",
        SANDBOX_IMAGE,
    ]

    try:
        proc = subprocess.run(
            cmd,
            capture_output=True,
            text=True,
            timeout=timeout_sec,
        )
    except subprocess.TimeoutExpired:
        # 超時直接 kill 容器
        subprocess.run(["docker", "kill", container_name], capture_output=True)
        return {"ok": False, "error": "timeout"}

    return {
        "ok": proc.returncode == 0,
        "stdout": proc.stdout,
        "stderr": proc.stderr,
        "exit_code": proc.returncode,
    }

幾個關鍵點:

  • 沒有掛宿主機敏感目錄,也沒有掛 /var/run/docker.sock,避免 Agent 直接控制 Docker daemon。
  • --network none:這個 profile 完全不能出網。若要出網,請另外設計受控 network profile,例如只允許打企業 MCP gateway。
  • --read-only 搭配小範圍可寫目錄,避免 Agent 於容器內持久化惡意腳本。

在你的 LLM tool schema 中,可以這樣暴露給模型:

{
  "name": "run_code_in_sandbox",
  "description": "在隔離的 Docker 容器內執行短程式碼(無網路、有限資源)",
  "parameters": {
    "type": "object",
    "properties": {
      "language": {
        "type": "string",
        "enum": ["python"],
        "description": "目前只支援 Python"
      },
      "code": {
        "type": "string",
        "description": "要執行的程式碼,必須是單檔腳本"
      }
    },
    "required": ["language", "code"]
  }
}

3. Node.js:以 MCP / 工具網關方式整合

如果你採用 MCP 或類似工具網關,建議把沙盒能力封裝成一個 service tool,而不是每個 Agent 都直接呼叫 Docker。

// sandboxTool.ts
import { execFile } from "child_process";
import { promisify } from "util";
const execFileAsync = promisify(execFile);

export async function runInSandbox(params: {
  code: string;
  profile?: "analysis" | "integration-test";
}) {
  const profile = params.profile ?? "analysis";

  const dockerArgs =
    profile === "integration-test"
      ? ["--cpus", "1", "--memory", "1g", "--network", "sandbox-staging"]
      : ["--cpus", "0.5", "--memory", "512m", "--network", "none"];

  const { stdout, stderr } = await execFileAsync("python", [
    "run_sandbox.py",
    JSON.stringify({ code: params.code, dockerArgs }),
  ], { timeout: 15000 });

  // 在這裡寫 audit log 到你的集中式監控
  // logSandboxEvent({ profile, codeSnippet: params.code.slice(0, 500), stdout, stderr })

  return { stdout, stderr };
}

在 MCP server 端,你只要把這個 runInSandbox 暴露為工具,並在工具 metadata 中標註:

  • scope:只允許特定角色的 Agent 使用(例如 CodeExecutor)。
  • audit:所有呼叫自動記錄到審計管線。

這樣一來,企業的 Agent 系統就有一致的安全邊界:所有程式執行都要經過同一層沙盒服務,方便治理與合規。


建議與注意事項

1. 千萬不要掛宿主 Docker socket

最常見也最危險的做法,是為了讓測試方便,直接在容器內掛:

-v /var/run/docker.sock:/var/run/docker.sock

這等同給了容器(也就是 Agent)對宿主機 Docker daemon 的完全控制權,能:

  • 啟動任意 privileged 容器;
  • 掛載宿主機任意路徑;
  • 讀取其他服務的環境變數與機密。

結論:在 Agent 沙盒場景,禁止掛 docker.sock 是硬規則。 需要 orchestrate 容器時,請在宿主或受控 sidecar 上做,而不是讓 Agent 直接控制。

2. 網路出口要明確設計,而不是「先開再說」

  • Rovo 被利用,就是因為 Agent默許能打外部網路,把敏感資料送走。
  • 建議預設 no egress,再逐步開放:
  • 只允許打企業 API gateway。
  • Gateway 再依使用者、任務、Agent role 做細粒度授權。

避免一開始就給 Agent 完整的 requests / fetch 能力,卻沒有 outbound policy。

3. 限制 fork、磁碟寫入與長時間運行

  • 使用 --pids-limit 防止 fork bomb。
  • 用 --read-only + 小範圍可寫目錄限制磁碟寫入,並定期清理一次性目錄。
  • 工具實作上務必加 硬超時,不可只依賴 Agent 自己判斷何時該結束。

4. 在企業場景下與現有系統整合

把 Agent 沙盒當成一個可以被納入現有治理架構的「服務」:

  • CI/CD:
  • 把沙盒鏡像視為一個版本化的 artifact,透過 pipeline 發布。
  • 變更權限、套件時要走同樣的審核流程。

  • MCP / 工具網關:

  • 透過 MCP 將沙盒工具集中管理,設定 scope(哪些 Agent 角色可以呼叫)、quota(每日執行次數)、審計策略。
  • 在多模型、多 Agent 環境中,統一用一套沙盒服務取代每個團隊自己寫的「隨便跑程式 API」。

  • 監控與審計:

  • 將每次沙盒執行事件(who / which agent / what code / which profile / result)送到 Log system / SIEM。
  • 在發現異常 pattern(例如大量嘗試未授權 API 操作)時,能快速追溯與調整策略。

總結:Docker 沙盒的價值不只是「比較安全」,更是讓 Agent 行為可以被管理、可觀測、可審計。只要你把「執行程式」這件事全部強制走沙盒,並搭配最小權限與網路出口策略,你就能在保持開發效率的前提下,大幅降低 Rovo 類型的資料外洩與 OpenClaw 類型的「創意駭客」事件死角。


🚀 你現在可以做的事

  • 在本機或測試環境建一個最小權限的 agent-sandbox Docker 鏡像,實驗一次性容器執行程式碼
  • 把現有 Agent 的「程式執行工具」改成呼叫 run_code_in_sandbox 類型服務,強制走沙盒
  • 檢查你的系統是否有容器掛載 /var/run/docker.sock 或無網路出口限制,列出並規劃修補清單

留言

發佈留言

發佈留言必須填寫的電子郵件地址不會公開。 必填欄位標示為 *