Azure 沙箱隔离设计:Agent Governance Toolkit 中 ACASandboxProvider 的架构、治理与失败契约
【免费下载链接】agent-governance-toolkitAI Agent Governance Toolkit — Policy enforcement, zero-trust identity, execution sandboxing, and reliability engineering for autonomous AI agents. Covers 10/10 OWASP Agentic Top 10.项目地址: https://gitcode.com/GitHub_Trending/ag/agent-governance-toolkit
本文围绕仓库设计文档 docs/proposals/AZURE-SANDBOX-ISOLATION-DESIGN.md 展开,深入剖析ACASandboxProvider如何把每个 Agent 会话一对一映射到 Azure Container Apps(ACA)沙箱,并完整解释其供给(provisioning)、执行(execution)、出口(egress)配置、状态追踪、取消与清理的全生命周期契约。读完本文,你将掌握该 Provider 的构造参数、SandboxConfig各字段语义、默认拒绝的出口网络策略、失败行为的精确边界,以及治理评估、静态扫描、base64 传输等执行链路在源码中的真实实现。
设计总览:从 Agent 会话到 Azure 沙箱的一对一映射
ACASandboxProvider是 Agent Governance Toolkit 中五种沙箱后端之一,位于 agent-governance-python/agent-sandbox/src/agent_sandbox/aca_sandbox_provider/aca_sandbox_provider.py,与其他后端一样实现统一的 SandboxProvider 抽象基类。
它的核心设计思想是:每个 Agent 会话(session)映射到沙箱组(sandbox group)内的一个 Azure Container Apps 沙箱,Provider 全权负责该沙箱的供给、执行、出口配置、状态查询、取消与清理。与本地 Docker / Hyperlight 后端不同,ACA 后端把计算隔离交给 Azure 托管的数据平面(data plane),治理层则保留在进程内:
SandboxGroupClient:数据平面客户端,作用域限定在一个(resource_group, sandbox_group)组合内,负责创建、列出、删除组内沙箱;SandboxClient:单个沙箱的客户端,由begin_create_sandbox(...).result()返回,承载exec、set_egress_policy、delete、get等操作;SandboxGroupManagementClient:仅当设置了ensure_group_location时才构造,用于首次使用时通过 ARM 控制平面自动创建沙箱组。
从源码结构可以推断,该设计刻意把「云上执行」与「治理决策」解耦:资源与出口设置来自SandboxConfig,可选的执行决策来自原生 ACS 运行时,两者在create_session时合并,并在 Azure 执行之前完成评估。
当前契约:create_session 与 SandboxConfig
设计文档给出的契约签名如下,这也是所有 Provider 共用的统一入口:
handle = provider.create_session( "agent-1", runtime=runtime, config=config, )对应SandboxProvider抽象基类中的定义(见 sandbox_provider.py L156-L163),返回的SessionHandle携带agent_id、session_id(ACA 后端中即沙箱 ID)与SessionStatus.READY状态。
SandboxConfig:宿主资源配置的唯一入口
SandboxConfig(sandbox_provider.py L63-L105)统一承载 CPU、内存、超时、环境变量、挂载与网络设置,各字段默认值如下:
| 字段 | 默认值 | 说明 |
|---|---|---|
timeout_seconds | 60.0 | 单次执行超时(秒),超时后结果被标记为 killed |
memory_mb | 512 | 沙箱内存上限(MiB),传给 Azure 时最小钳制为128Mi |
cpu_limit | 1.0 | CPU 上限(核),传给 Azure 时转换为毫核(m),最小100m |
network_enabled | False | 是否启用网络;关闭时network_allowlist不生效 |
read_only_fs | True | 只读文件系统 |
env_vars | {} | 注入沙箱的环境变量 |
input_dir/output_dir | None | 输入/输出目录挂载 |
network_allowlist | [] | 出口放行的 host 列表 |
tool_allowlist | [] | 工具白名单;ACA 后端不支持并直接拒绝 |
network_default | "deny" | 出口默认动作,仅允许"allow"/"deny",其他值在__post_init__直接抛ValueError |
ring | None | 超管执行环约束(#2666),见下文 |
代码中_cpu_millicores(aca_sandbox_provider.py L383-L386)将cpu_limit折算为毫核并钳制下限 100m;_memory_mib(L388-L390)将memory_mb格式化为Mi后缀并钳制下限 128Mi。在create_session中,只有当显式传入config时这些资源上限才会被透传给begin_create_sandbox(cpu、memory、environment参数),不传则交由沙箱镜像默认值(测试test_resource_caps_omitted_when_no_config明确断言了这一点)。
另外注意:create_session会先调用_validate_resource_name(L85-L90)校验agent_id,其正则^[a-zA-Z0-9][a-zA-Z0-9_-]{0,62}$(L82)同时用于校验sandbox_group,杜绝名称被插值进 ARM/数据平面 URL 时产生畸形请求。
原生治理:AgentControl / HostSession
设计文档指出AgentControl是可选的、拥有全部治理决策的组件。在实现中,当create_session收到runtime参数时,Provider 会从agent_control_specification导入HostSession并将其包装为评估器(L481-L486):
evaluator = HostSession( runtime, agent_id=agent_id, session_id=f"aca-{agent_id}" )该评估器随后在每次execute_code时以pre_tool_call(tool_name="sandbox_execute", args=eval_ctx, call_id=...)的形式被调用,eval_ctx至少包含agent_id、action="execute"与code,并会合并调用方传入的context(测试test_context_is_merged_into_eval_ctx验证了合并行为)。也就是说,治理决策永远发生在任何 Azure 调用之前——create_session阶段评估器即被装配,execute_code阶段先评估后执行。
Egress 网络策略:默认全拒、显式放行
设计文档定义了出口网络的三条规则,实现位于_apply_egress_policy(L392-L443),并由create_session在每次会话创建时强制执行(L576-L577):
- 每个会话都收到一条显式 Azure egress 策略;
- 默认拒绝所有出站流量:
network_default="deny"时,即使hosts为空也会调用SandboxClient.set_egress_policy,构造EgressPolicy(default_action="Deny", host_rules=[]),产生一个完全没有出站网络的沙箱(测试test_default_session_applies_deny_all_egress、test_empty_allowlist_plus_deny_is_total_lockdown验证); - 非空
network_allowlist生成 host 放行规则:每个 host 被翻译为EgressHostRule(pattern=h, action="Allow"),随defaultAction: Deny一并下发(测试test_filtered_egress_is_fail_closed_by_default验证了pypi.org、*.github.com等 pattern 的生成); - 无限制出口需要显式双重开启:
network_enabled=True且network_default="allow"。此时_apply_egress_policy直接return,不发起任何 egress API 调用(测试test_network_default_allow_skips_egress_api_call),即保留 Azure 侧的默认放行行为。
create_session中允许列表的实际计算逻辑(L467-L474)也值得注意:
allow_hosts = list(cfg.network_allowlist) if cfg.network_enabled else [] net_default = ( "deny" if allow_hosts or not cfg.network_enabled else cfg.network_default )这意味着:只要network_enabled=False,无论 allowlist 是否配置,最终策略都是 deny(fail-closed);只有网络显式启用且 allowlist 为空时,network_default才真正决定默认动作。
实现细节上,SDK 需要类型化模型EgressPolicy/EgressHostRule;若旧版或 fork 的 SDK 缺少这两个模型,代码会回退到{"defaultAction": "Deny", "hostRules": [...]}字典形态(L427-L435,测试test_egress_falls_back_to_dict_when_typed_models_missing覆盖)。另外,如果配置了ring约束且其network_allowed=False(如超管环 RING_3_SANDBOX),即使策略允许,也会强制清空 allowlist 并把network_default重置为deny(L496-L500)。
失败行为契约:执行前失败与执行后记录
设计文档明确了失败行为的边界,源码中逐一对应:
执行前失败(抛异常,Azure 不产生副作用):
| 失败场景 | 行为 | 依据 |
|---|---|---|
| 无效 Agent ID / sandbox group 名称 | ValueError,提示需匹配[a-zA-Z0-9][a-zA-Z0-9_-]{0,62} | L465、L197 |
SDK 不可用(azure-containerapps-sandbox未安装) | Provider 标记is_available()==False,create_session抛RuntimeError并携带可操作原因 | L230-L243、L456-L463 |
缺少 region(且无ensure_group_location/AZURE_SANDBOX_REGION) | Provider 不可用,unavailable_reason提示补充 region | L245-L253 |
| provisioning 失败(配额、后端错误) | RuntimeError("Failed to create Azure sandbox for agent ...") | L541-L566 |
沙箱客户端缺少sandbox_id | RuntimeError,明确提示 | L568-L574 |
| 运行时(治理)拒绝 | PermissionError,消息来自 verdict | L637-L638 |
| 治理返回 transform verdict | PermissionError(沙箱无法改写即将执行的代码,拒绝而非放行原文) | L632-L636 |
| 静态代码扫描命中危险调用 | SandboxCodeViolation(PermissionError子类) | L640、code_scanner.py |
执行后记录(仅日志,不阻断会话):
- Azure egress API 失败仅记录日志:因为沙箱可能已经存在,
set_egress_policy抛出的异常被logger.warning("Failed to apply egress policy on sandbox '%s': %s", ...)捕获(L436-L443),而默认请求状态仍保持 deny——这正是设计文档强调的「沙箱可能已存在,而默认请求状态保持为拒绝」的 fail-closed 语义。测试test_egress_policy_failure_is_logged_not_raised确认了即便 egress 调用失败,create_session仍返回SessionStatus.READY。 - 销毁失败仅记录日志:
destroy_session中sb_client.delete()失败被吞掉并打 WARNING(L734-L741,测试test_destroy_swallows_delete_failure)。
执行流程纵深:从治理评估到 base64 传输
execute_code(aca_sandbox_provider.py L598-L720)是理解隔离设计的关键路径,完整调用链为:
- 会话查找:根据
(agent_id, session_id)从内部缓存取出SandboxClient、评估器与会话配置;找不到则抛RuntimeError("No active session ... Call create_session() first.")(L612-L616); - 治理评估:构造
eval_ctx并调用evaluator.pre_tool_call(L618-L638),拒绝或 transform verdict 都在此抛出,不触发任何 Azure 调用(测试test_policy_deny_raises_before_any_azure_call断言sb.exec未被调用); - 静态代码扫描:
enforce_no_subprocess_execution(code)(L640)基于 AST 检查subprocess、os.exec*、os.system、pty.spawn、shutil.which等危险调用(见 code_scanner.py L15-L47),命中即抛SandboxCodeViolation(测试test_static_scan_blocks_subprocess_before_any_azure_exec验证os.system('kubectl get secrets')在执行前被阻断); - 执行环子进程闸门:若配置了
ring,通过RingEnforcer.check_resource(..., SUBPROCESS)二次检查,并由RingBreachDetector记录调用、支持熔断(L642-L666,对应 #2666); - base64 传输:源码被
base64.b64encode编码后拼装为echo {encoded} | base64 -d | python3再调用sb_client.exec(command)(L670-L676)。这样做让请求体对宿主 shell 不透明,多行脚本与引号都能原样执行(测试test_code_is_base64_piped_into_python3验证了含换行与引号的代码往返一致); - 结果规范化:
_unpack_exec_result(L93-L121)兼容 0.1.0b1 的类型化结果对象(exit_code/stdout/stderr)与更早 preview 的字典形态(exitCode驼峰或exit_code蛇形);stdout/stderr 各截断至 10000 字符; - 超时判定:若实际耗时超过会话
timeout_seconds,结果被标记killed=True并写入kill_reason(L705-L712,测试test_timeout_kill_when_duration_exceeds_session_cfg通过冻结time.monotonic验证)。
这套链路保证了「评估 → 扫描 → 传输 → 执行 → 超时」的顺序与 fail-closed 属性均可被测试锁定。
生命周期管理:状态、销毁与资源清理
Provider 内部以_state_lock(threading.RLock)保护三张映射表:_sandboxes(会话 → SandboxClient)、_evaluators(会话 → 评估器)、_session_configs(会话 → SandboxConfig),另有 ring 相关的_ring_enforcers/_ring_breach_detectors(L214-L220)。多会话隔离由这些按(agent_id, session_id)键控的字典保证,测试文件中明确覆盖了「每个会话的评估器、配置、沙箱客户端不跨 Agent 泄漏」的场景。
- 状态查询:
get_session_status返回SessionStatus.READY或DESTROYED(L756-L762); - 销毁:
destroy_session先从状态表中移除条目(保证幂等,二次销毁不重复调用 delete),再调用sb_client.delete()(L722-L741); - 资源释放:
close()关闭SandboxGroupClient与SandboxGroupManagementClient共享的 HTTP 管道,失败仅 debug 级记录;Provider 同时实现了上下文管理器__enter__/__exit__(L788-L792),推荐用with ACASandboxProvider(...) as p:包裹使用(测试test_context_manager_calls_close验证)。
源码级验证:测试如何锁定隔离行为
仓库提供了两套测试:单元测试 tests/test_azure_sandbox.py(mock 全部 Azure 调用,无需凭证与网络)与集成测试tests/test_azure_sandbox_integration.py(命中真实 Azure)。单元测试覆盖点与本设计文档逐条对应:
- 名称校验:
TestValidateResourceName参数化验证 63 字符上限、禁止前导-/_、空格、斜杠、点号与超长名; - 构造期不可用路径:SDK 缺失、region 缺失、
endpoint_for_region失败、DefaultAzureCredential失败,均使is_available()==False且unavailable_reason携带可操作的安装提示(如agt-sandbox[azure]); - egress 语义:默认 deny-all、allowlist 生成 Allow 规则、
network_default="allow"跳过 API 调用、egress 失败仅记录; - 资源投影:CPU 下限
100m、内存下限128Mi、无 config 时不传资源参数、TypeError时回退到最小 kwargs 重试; - 执行路径:治理拒绝/transform 拒绝不触达 Azure、静态扫描阻断、base64 往返、10k 截断、超时 kill、执行异常包装为
ExecutionStatus.FAILED; - 生命周期:销毁幂等、删除失败吞掉、
close容错、async 变体委托同步实现(asyncio.to_thread,定义于 sandbox_provider.py L232-L265)。
部署与集成注意点
安装与初始化(详见 agent-sandbox README)需注意 ACA 后端依赖早期访问 SDK:
pip install "agt-sandbox[azure,policy]" pip install https://github.com/microsoft/azure-container-apps/releases/download/python-sdk-v0.1.0b1-early-access/azure_containerapps_sandbox-0.1.0b1-py3-none-any.whl az login # 或在使用托管标识的托管计算环境中运行from agent_sandbox import ACASandboxProvider provider = ACASandboxProvider( resource_group="my-rg", # 必须已存在,Provider 不创建资源组 sandbox_group="agents", # 设置 ensure_group_location 时自动创建 region="eastus2", # 选择数据平面端点 subscription_id=None, # 缺省回退到 AZURE_SUBSCRIPTION_ID 环境变量 disk="python-3.13", # 预装 python3 的公共磁盘镜像 ensure_group_location="eastus2", # 首次使用时创建沙箱组 ) if not provider.is_available(): raise SystemExit(f"ACA unavailable: {provider.unavailable_reason}")几点实践要点:
- 资源组必须预先创建:
resource_group不存在时,create_session会把 Azure 的 404 包装成RuntimeError抛出(Provider docstring 明确说明,可用az group create -n my-rg -l eastus2预先创建); - region 解析优先级:
region→ensure_group_location→AZURE_SANDBOX_REGION环境变量,三者皆无则 Provider 不可用;测试环境可传endpoint=显式指定数据平面地址绕过endpoint_for_region; tool_allowlist在 ACA 后端会被拒绝:ACA 没有工具注册通道,源码在create_session直接抛ValueError,提示「通过 ACS 运行时强制工具访问」(L475-L479)——这是该后端与其他后端(如 Hyperlight)的能力差异,属预期行为;ensure_group_location的容错:管理客户端构造失败或 SDK 缺少对应方法时,Provider 仅降级(不自动建组)而不会崩溃,测试test_mgmt_client_failure_does_not_kill_provider、test_mgmt_client_attributeerror_is_tolerated均验证了这一点。
综上,ACASandboxProvider把「托管沙箱执行」与「进程内治理」组合为一条 fail-closed 的隔离链路:出口网络默认全拒、治理与静态扫描先于一切 Azure 调用、超时与清理皆有明确契约。对于需要生产级多租户隔离、又不想自建基础设施的 Agent 工作负载,它是 Agent Governance Toolkit 中与 Docker、Hyperlight、MXC、nono 并列的可直接替换后端——统一SandboxProviderAPI 使应用代码无需改动即可切换后端。
【免费下载链接】agent-governance-toolkitAI Agent Governance Toolkit — Policy enforcement, zero-trust identity, execution sandboxing, and reliability engineering for autonomous AI agents. Covers 10/10 OWASP Agentic Top 10.项目地址: https://gitcode.com/GitHub_Trending/ag/agent-governance-toolkit
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考