3. 开发:保护客户代码仓库

交付阶段: 开发环境 新问题: Forge 如何执行客户代码,却看不到无关源码、开发者凭据或不受限制 的网络? 交付物: 经过测试的 Sandbox 边界与一次性 Workspace 设计。 配套实验: code/02

“Sandbox”背后的问题

ByteCraft 的 Design Partner 愿意提供一个小型私有仓库,但前提是团队解释源码 存放位置和访问主体。完成本地实验后,Maya 说:“Forge 现在已经运行在 Sandbox 中。”

Lina 提出了一个看似简单的问题:

Sandbox 到底保护什么,又不保护什么?

Sandbox 不是一个有魔力的标签。对 Forge 而言,它必须保护私有源码、隔离 Agent 与凭据、限制网络目标,并留下足够证据来解释失败或恶意操作。如果团队说不清这些 边界,就无法测试它们。

本章暂停功能开发,逐层打开 Sandbox。

karsSandbox 是工作单元

在 kars 中,一个 karsSandbox 代表一个 Agent 工作负载。它连接:

  • OpenClaw、Hermes、其他 Adapter 或 BYO Runtime;
  • 必需的 InferencePolicy;
  • Sandbox 隔离与安全设置;
  • 网络策略和可选 Approval;
  • 自动生成的 Kubernetes 资源;
  • 协调状态与 Conditions。

自定义资源是期望状态契约,运行中的 Pod 是该契约产生的结果之一。直接修改 Pod 不会重新定义 Sandbox;Controller 可能在协调期间替换它。

在 Forge 故事中,一个 Sandbox 表示一个研发角色的一次受限执行上下文。这不代表 所有开发者、仓库和 Agent 都应该共享一个长期 Workspace。

打开接近生产形态的 Pod

在本地 Kubernetes 与 AKS 中,关键 Pod 形态如下:

karsSandbox: forge
└── Pod
    ├── init: egress-guard  UID 0,仅初始化期间拥有 NET_ADMIN
    ├── openclaw           UID 1000
    └── inference-router  UID 1001

Agent Container 技术结构

openclaw Container 被刻意视为 Pod 中信任等级最低的 Container。KARS Controller 根据 KarsSandbox.spec.runtime 与 spec.sandbox Contract 生成它,而不是要求应用 自己在代码中写死全部安全设置。

Kubernetes/Runtime 细节 Forge 配置 Agent Container 内的结果
runAsNonRoot / Runtime UID true / UID 1000 Agent 不能依赖 Root Ownership
readOnlyRootFilesystem true Image Layer、Binary 与系统配置不可写
allowPrivilegeEscalation false Setuid 或 Process Transition 不能增加权限
Linux Capability Drop ALL 没有环境 Network、Mount 或 Process-management Capability
Writable Path /sandbox、/tmp State 与 Cache 具有明确的清理边界
Volume 没有 hostPath 或 Docker Socket Agent 无法到达开发者 Home 或 Container Daemon
Service Account automountServiceAccountToken: false Filesystem 中不会出现隐式 Kubernetes API Bearer Token
Provider Environment 没有 GitHub/Copilot Token Reference Prompt-injected Code 无法从 env 读取模型凭据
Repository Access 该 Pod 不挂载 Checkout 读写必须通过 Router 到受限 Workspace MCP Service
Network Namespace 与 Router 共享,并使用 UID-aware Egress Control Loopback 可用,独立外部出口不可用
Operator Access Exec Admission Policy 普通 pods/exec 与 Attach 路径不能进入 Agent Runtime

Container 仍然拥有完成任务所需的能力:运行 OpenClaw、在批准的 Writable Path 中保存 Conversation State、调用 127.0.0.1:8443,以及请求受治理工具。Sandbox 并不表示一个 空进程,而是所有超出 Runtime Envelope 的副作用都必须跨越独立执行的边界。

KARS 提供什么,平台仍负责什么

KARS 提供 Runtime Adapter、Controller Reconciliation、Router Sidecar、UID 分离、 Sandbox Security Context、Egress Guard Integration、NetworkPolicy Intent、Policy Reference、Condition 与 Exec Admission Boundary。平台团队仍需负责 Image、 MCP Implementation、Secret Selection、Allowed Endpoint、Writable Data,以及任何会 消费 Agent Artifact 的外部系统。

这正是 ByteCraft 场景中的实际优势:安全控制不需要在 OpenClaw 内重复实现;同时 KARS 也不会声称一个不安全 Tool Server 或错误挂载的 Secret 因为旁边运行着 Router 就自动变得安全。

egress-guard

Init Container 安装网络规则,使 Agent UID 可以通过 Loopback 到达路由器,却无法 建立独立外部路径。它是 Safety Net,而不是策略决策点。

agent

Forge 与 OpenClaw Runtime 以 UID 1000 运行。在 code/01 的实际实现中, OpenClaw 不会直接挂载代码仓库,而是调用本地 Router,由 Router 代理访问独立的 Workspace MCP Service。OpenClaw 既不持有 Provider 凭据,也没有直接出口。

inference-router

Rust 路由器以 UID 1001 运行。面向模型的 HTTP Endpoint 使用端口 8443, Forward Proxy 路径使用 8444。它负责评估策略、预算、身份、出口、治理和审计。

UID 分离十分重要:Forge 解释或执行的代码,不会以持有外部权限的路由器用户运行。

围绕一次代码修改的五层边界

Maya 给 Forge 分配包含 Issue #482 的 Workspace。团队沿任务检查五层边界。

1. 进程边界

Forge 以非 root Agent 进程运行,路由器使用不同 UID。研发 Agent 执行的命令 不应读取路由器的进程环境或凭据材料。

本地 Kubernetes 与 AKS 的边界强于单容器 Docker 开发模式。Docker 模式中 Agent 和路由器位于同一容器,UID 与网络隔离不等同于生产。

2. 文件系统边界

Agent 只获得任务需要的 Runtime 文件。良好的研发 Sandbox 使用固定 Revision 的 临时 Checkout 或 Worktree,不会挂载开发者 Home、SSH 目录、全局 Git Credential Store 或无关仓库。

Forge 采用了更强的分离:经过加固的 forge-workspace-mcp Pod 使用有大小限制的 emptyDir 管理固定 Revision 仓库;OpenClaw Pod 没有仓库或 hostPath 挂载。 MCP Deployment 同时关闭自动 Service Account Token 挂载,并且只暴露七个受限工具。

kars 定义 Runtime Sandbox,但平台团队仍需谨慎设计 Volume 与 Secret。 NetworkPolicy 无法保护被错误挂载到 Agent 容器中的 Secret。

3. 网络边界

Agent 把受控请求发送到 127.0.0.1:8443 或文档规定的 Proxy 路径。Egress Guard 与 Kubernetes NetworkPolicy 阻止独立路径,路由器执行真正的策略决定。

必须区分:

  • 网络控制回答:“这个 Packet 能否从其他路径离开?”
  • 路由器策略回答:“这个模型、工具、主机或操作是否允许?”

纵深防御同时需要两者。

4. 身份边界

生产环境中,路由器根据部署模式使用 Workload Identity 或每 Sandbox Entra Agent ID。Forge 不会得到对应 Azure 凭据。

本地 Kubernetes 会复现 Pod 与网络形态,但使用静态提供商凭据进行开发。它拥有 接近生产的基础设施形态,却不是生产身份。

5. 生命周期与证据边界

Controller 观察 karsSandbox、创建或更新资源,并报告 Conditions。路由器记录 请求期间的决定。任务结束后,可以删除 Workspace,同时把审计证据独立导出。

临时执行可以降低持久化风险,但在导出证据前删除 Pod,也可能破坏重要事故上下文。

检查 Sandbox,而不是假设

先启动第 2 章的 Forge 部署,再运行可执行实验:

cd code/02
make inspect

脚本会从 KarsSandbox/forge 自动发现生成的 Namespace 与 Pod,不会写死 Pod 名称。 它会导出:

  • Sandbox 期望状态与 Conditions;
  • Pod Spec 与精简的 UID/挂载摘要;
  • NetworkPolicy;
  • Workspace MCP Deployment;
  • kars Exec Admission Policy;
  • 最近的 Controller 与 Router 日志。

输出保存在 Kubernetes 外部的 .evidence/<UTC timestamp>/,因此 Pod 重新协调或 Workspace 清理后,证据仍然存在。

普通运维人员也不能假设自己拥有交互式 Shell:

kubectl -n kars-forge exec <forge-pod> -c openclaw -- id

kars 会通过 kars-sandbox-exec-ban 拒绝该请求。完整实验可以短时启用可审计的 Namespace Break-glass Label,只执行范围明确的只读探测,并通过 Shell Trap 删除标签。

使用 Forge 测试边界

code/01 中的 Fixture 仓库就是专门准备的一次性测试仓库。绝不要使用生产 Checkout 进行以下实验。

测试 预期结果
检查 Forge 与分配的 Workspace 通过 kars 资源和受限 MCP 工具允许
读取开发者 Home 中的 Secret 没有挂载 Host 文件系统或 Home 目录
通过路由器调用模型 在推理策略范围内允许
从 OpenClaw 直接访问未知主机 超时并返回 HTTP 000
从 OpenClaw 写入 /etc 被只读根文件系统拒绝
在 OpenClaw 中查找 Copilot 凭据 不存在 Provider 凭据变量
对 OpenClaw 使用普通 kubectl exec 被 kars Admission Policy 拒绝
重启 Agent Controller 将工作负载恢复到期望状态
引用不存在的推理策略 得到 Degraded/InferencePolicyNotFound

运行非破坏性检查:

make test

运行完整实验,包括短时 Break-glass 探测与 Forge Pod 替换:

make test-full

经过验证的 macOS arm64 运行结果包括:OpenClaw UID 1000、Router UID 1001、 根文件系统写入被拒绝、OpenClaw 中没有 Provider 凭据、直接 HTTPS 被阻断、 通过 127.0.0.1:8443 返回 HTTP 200、缺失 Policy 时出现 Degraded Condition, 以及删除 Forge Pod 后 Controller 成功创建替代 Pod。

实验开始前会强制检查 code/01 使用的 Microsoft Package Feed Proxy:

https://packagefeedproxy.microsoft.io/npm/
https://packagefeedproxy.microsoft.io/pypi/simple/
https://packagefeedproxy.microsoft.io/nuget/v3/index.json

选择正确隔离级别

kars 使用安全的 Sandbox 默认值,包括 Enhanced Isolation、严格 Seccomp Profile 和 Default-Deny 网络。AKS 可以选择由 Kata/AMD SEV-SNP 支持的 Confidential Isolation,进一步加强工作负载隔离。

当 Forge 处理未发布源码或高价值构建输入时,机密隔离可能合适。但它不能取代 最小权限身份、签名镜像、工具策略、出口策略或代码审查。

Sandbox 不承诺什么

团队把以下限制写入威胁模型:

  • 它不能证明生成的 Patch 一定正确。
  • 它不会让不可信代码自动变得可以合并。
  • 它无法保护被错误挂载到 Agent 中的凭据。
  • 它不会把 Docker 开发模式变成生产边界。
  • 它不能取代租户级 RBAC、配额、镜像策略或审计导出。
  • 如果策略明确允许任意 Shell 和不受限出口,它也无法补偿。

Sandbox 负责限制权限;测试、评估、评审和部署策略仍然决定变更是否可接受。

Forge Sandbox 设计记录

继续之前,团队记录:

Workload:一次 Forge Builder 任务
Source:Workspace MCP Pod 的 emptyDir 中保存固定 Revision Checkout
Agent User:UID 1000
Router User:UID 1001
外部路径:仅 Router
Agent 中的凭据:无
OpenClaw 可写范围:/sandbox 与 /tmp
仓库写入:仅受限 Workspace MCP 工具
生命周期 Owner:kars Controller
证据目标:code/02/.evidence,然后进入外部 Audit Store
清理:删除 Pod 或 Workspace 前先导出证据

下一章会把已经理解的 Runtime 边界转化为可评审的 Kubernetes 契约。

完成定义

只有固定 Revision 的一次性 Checkout 被隔离在 Workspace MCP Pod、OpenClaw 没有 仓库挂载或可复用 Git/模型凭据、直接访问未知目标失败、UID 分离与 Exec 限制清晰 可见、Controller Reconciliation 得到验证,并且导出的证据在 Workspace 清理后仍然 保留时,开发环境才算 Ready。

官方参考

Sandbox Escape 检查点:移除环境权限

第一个 Containment 检查点是结构性的。Forge 只能写入 /sandbox 与 /tmp, 不会得到 Host Mount、Docker Socket、Provider Credential 或自动挂载的 Kubernetes ServiceAccount Token。普通 Agent Runtime Exec 会被拒绝,Break-glass 则必须显式 启用并接受审计。

在 code/02 中运行:

make test

本阶段不假设模型总能识别 Prompt Injection,而是移除把错误决定升级为宿主机或集群 控制权所需的进程级能力。