Все статьи
Артемий Мазаев·· 13 мин чтения

NVIDIA OpenShell: клетка для агента, которую он сам не откроет 🔒🤖

Разбираю NVIDIA OpenShell — открытую песочницу для агентов: Landlock и seccomp, политики на YAML с правилами по бинарю, методу и пути, подстановка кредов на выходе и prover на Z3.

NVIDIA OpenShell: клетка для агента, которую он сам не откроет 🔒🤖

Ну чё, малютки, агентам мы уже выдали shell, токены от GitHub, интернет и разрешили ставить себе скиллы. А кто за ними смотрит? Обычно сам агент: системный промпт «не делай плохого» плюс встроенная песочница харнеса, которая выключается одним флагом. Я на днях сам видел, как Codex не смог поднять свою песочницу bwrap внутри песочницы Claude Code, и выход мне предложили ровно один: --dangerously-bypass-approvals-and-sandbox.

NVIDIA в марте выкатила OpenShell — отдельный рантайм, который сидит между агентом и железом и решает, что агенту можно, а что нельзя. Поменять эти решения агент не может, даже если его взломали. Разбираемся, как оно устроено и от чего реально спасает.

🔥Суть за 10 секунд

OpenShell запускает агента (Claude Code, Codex, OpenCode, Copilot CLI, OpenClaw) в песочнице с изоляцией на уровне ядра. Файлы режет Landlock, сеть перехватывает seccomp, а решает всё супервизор снаружи песочницы по YAML-политике: какой бинарь, на какой хост, каким HTTP-методом и по какому пути. Настоящих токенов агент вообще не видит, их подставляют на выходе. Новые правила агент может попросить сам, а prover на Z3 проверяет, не открывают ли они дорогу к кредам. Лицензия Apache 2.0, написано на Rust.

Apache 2.0Лицензия

Открыто целиком, репозиторий NVIDIA/OpenShell. Входит в NVIDIA Agent Toolkit.

~87% RustСтек

Считал по байтам кода в репозитории. Ещё немного Go, Python и TypeScript для SDK и Helm-чарты для Kubernetes.

v0.1.6Версия

Вышла 27 октября 2026. Стабильные релизы раз в неделю, по вторникам. 20 тысяч звёзд на GitHub.


Зачем это всё

NVIDIA в анонсе описывает проблему как трилемму: безопасность, возможности, автономность — надёжно получаются только две из трёх. Дашь агенту полный доступ и отпустишь работать сутками — получишь, цитата, «долгоживущий процесс, который сам себя контролирует». Охрана сидит в том же процессе, который она охраняет.

Дальше в анонсе три фразы, которые хорошо показывают, чего бояться:

1Любая prompt injection — потенциальная утечка кредов. Токен лежит в окружении, сеть у агента есть.
2Любой сторонний скилл, который агент себе поставил, — непроверенный код с доступом к файлам.
3Любой сабагент может унаследовать права, которые ему никто не собирался давать.

OpenShell выносит проверку за пределы процесса агента. Ограничения висят на окружении, в котором агент запущен, а не на его поведении. Промпт можно уговорить, Landlock — нет. NVIDIA сравнивает это со вкладками браузера: каждая сессия изолирована, а права рантайм проверяет до того, как что-то выполнится.

💡Про встроенные песочницы харнесов

Справедливости ради, песочницы в Claude Code и Codex тоже работают на механизмах ядра, а не на промпте. Разница в том, кто ими управляет: харнес сам решает, когда включать песочницу и что в неё пускать, а ты можешь отключить её флагом. OpenShell оборачивает харнес снаружи, и изнутри его не выключишь.


Хронология: март → октябрь

Проект молодой и быстро меняется, так что весенние статьи про него местами уже устарели.

  • 24 февраля 2026 — на GitHub появился репозиторий.
  • 16 марта 2026 — анонс в блоге NVIDIA вместе с NemoClaw на GTC 2026. Тогда были версии 0.0.x.
  • 25 сентября 2026 — v0.1.0, большой релиз, который ломает совместимость. Обновиться с 0.0.x на месте нельзя: только снести, поставить заново и пересоздать все песочницы.
  • 27 октября 2026 — v0.1.6, актуальная на момент написания.

Самое заметное изменение в 0.1.0: из OpenShell выпилили управляемый маршрут инференса. В мартовском анонсе был «privacy router», который сам решал, куда отправить запрос к модели, по твоим правилам цены и приватности. В 0.0.x это работало так: агент стучался в inference.local, а OpenShell перенаправлял запрос дальше. Теперь openshell inference, API маршрутов и inference.local удалены. Ты подключаешь к песочнице провайдера (OpenAI, Anthropic, NVIDIA и т. д.), и агент ходит в его API напрямую, в родном формате. Видишь в гайде inference.local — значит, гайд для 0.0.x.


Как устроено

Компонентов немного:

1Gateway — control plane. Создаёт и удаляет песочницы, хранит политики и креды. Через него же ты заходишь в песочницу.
2Compute driver — запускает песочницу на конкретном рантайме: Docker, Podman, Kubernetes или microVM на libkrun (KVM на Linux, Hypervisor.framework на macOS).
3Supervisor — сидит снаружи, на доверенной стороне. Сверяет запросы с политикой, резолвит DNS, открывает разрешённые соединения и подставляет креды.
4Sandbox — сидит внутри, рядом с агентом. Определяет, какой бинарь делает запрос, и отдаёт запрос супервизору. Сам он ничего не решает: он заперт вместе с недоверенным кодом.
5Providers — хранят креды и привязывают их к сервисам. Prover формально проверяет правила, которые предлагает агент.

Песочница с супервизором общаются по одному HTTP/2-соединению с взаимной аутентификацией. По чему его пустить, решает драйвер: Unix-сокет для Docker и Podman, TCP для Kubernetes, vsock для microVM.

Запрос агента не уходит в сеть напрямую: песочница определяет бинарь, супервизор сверяет запрос с политикой, подставляет кред и только потом открывает соединение.

Как проходит один запрос (по документации):

  1. Агент открывает TCP-соединение или делает DNS-запрос.
  2. Песочница определяет, какая программа это делает.
  3. Запрос уходит супервизору по Sandbox Protocol.
  4. Супервизор сверяет его с политикой и, если можно, добавляет креды.
  5. Если всё ок, супервизор сам открывает настоящее соединение и гоняет трафик.

Всё работает по принципу fail closed. Агент не стартует, пока супервизор не ответил, а если связь с супервизором пропала, агента замораживают.


На чём держится клетка

Внутри песочницы всё держится на механизмах ядра Linux:

  • Landlock ограничивает доступ к файлам. Нужна ABI 3, то есть ядро 6.2+. Документация предупреждает, что одной версии ядра мало: нужные возможности надо проверять отдельно.
  • seccomp user notification перехватывает сетевые вызовы и держит их, пока супервизор решает, пускать или нет.
  • Агент работает от обычного пользователя без Linux capabilities. Root политика просто не пропустит: run_as_user можно ставить от 1 до 4294967294.

Снаружи стоит второй забор, у каждого рантайма свой:

РантаймКак отрезана сеть
Docker / Podmanу контейнера с агентом сеть выключена
KubernetesNetworkPolicy пускает только к сервису супервизора
microVMу гостевой системы нет сетевого устройства

Так что даже если агент как-то обойдёт seccomp, напрямую в интернет он не выйдет. Вся сеть идёт через супервизор.


Политика: кто, куда и каким методом

Политика — это YAML-файл. Вот пример прямо из документации:

version: 1

filesystem_policy:
  include_workdir: true
  read_only: [/usr, /lib, /etc]
  read_write: [/tmp]

network_policies:
  github_rest_api:
    endpoints:
      - host: api.github.com
        port: 443
        protocol: rest
        enforcement: enforce
        access: read-only
    binaries:
      - path: /usr/bin/gh
  npm_registry:
    endpoints:
      - host: registry.npmjs.org
        port: 443
        protocol: rest
        enforcement: enforce
        access: read-only
        allow_encoded_slash: true
    binaries:
      - path: /usr/bin/node

Что здесь важно:

Одни секции меняются на ходу, другие нет. filesystem_policy, landlock и process фиксируются при старте песочницы. network_policies и network_middlewares можно менять прямо во время работы, без рестарта. Доступ к файлам посреди сессии не расширишь, а сеть — пожалуйста.

Запрещено всё, что не разрешено. Любое исходящее соединение отклоняется, если под него нет правила в network_policies.

Правило привязано к бинарю. gh может читать API GitHub, а curl на тот же хост не пустят. Смотрят на настоящий путь к исполняемому файлу, а скрипт считается своим интерпретатором. Ещё OpenShell запоминает хэш бинаря при первом соединении и отказывает, если файл потом поменяли. Подложить свой gh не выйдет.

Проверка в два этапа. Сначала хост, порт и бинарь (L4). Если у эндпоинта задан protocol, дальше проверяется каждый запрос внутри соединения (L7): для rest это метод, путь и параметры. Ещё поддерживаются websocket, graphql, mcp, json-rpc и голый tcp. Пресет read-only — это GET, HEAD, OPTIONS, read-write добавляет POST, PUT, PATCH, full пускает всё. Запрет из deny_rules перебивает любой allow, где бы тот ни стоял в файле.

Метаданные облака закрыты всегда. Политика никогда не пустит на loopback, link-local и 0.0.0.0, в том числе на 169.254.169.254. Классический способ утащить креды инстанса в облаке здесь не сработает, даже если ты по глупости впишешь такое правило.

⚠️Ловушка: enforcement по умолчанию — audit

По умолчанию enforcement стоит в audit: нарушение L7-правил попадает в лог, а запрос проходит. Блокирует только enforce. Запрет соединений по умолчанию работает всегда, но если ты написал access: read-only и забыл enforcement: enforce, POST спокойно уйдёт. В примере выше enforce прописан явно, и это не случайно.

Покрути сам: политика та же, что в примере, плюс подключён провайдер GitHub.

🚦 Пропустит или нет
Политика из документации: gh может читать api.github.com, node может читать registry.npmjs.org, больше ничего. К песочнице подключён провайдер GitHub. Собери запрос и посмотри, на каком шаге его пропустят или завернут.
/usr/bin/gh → GET https://api.github.com/repos/aishiz/blog
Authorization: Bearer openshell:resolve:env:GITHUB_TOKEN
✓
1. Кто звонит
seccomp перехватил connect(), песочница определила бинарь /usr/bin/gh, хэш совпал.
✓
2. Куда нельзя никогда
Это не loopback, не link-local и не 0.0.0.0.
✓
3. L4: хост + порт + бинарь
Правило github_rest_api: api.github.com:443 для /usr/bin/gh.
✓
4. L7: метод + путь
GET /repos/aishiz/blog укладывается в read-only.
✓
5. Креды
Супервизор заменил openshell:resolve:env:GITHUB_TOKEN на настоящий токен уже на выходе. Агент этот токен так и не увидел.
✅ Пропущено. Запрос ушёл, ответ вернётся агенту.
Это игрушечная модель по документации v0.1.6, а не сам супервизор. Настоящий ещё отвечает на DNS заглушками, расшифровывает TLS, чтобы заглянуть в запрос, и прогоняет трафик через middleware. Порядок проверок и тексты отказов упрощены.

Креды, которых агент не видит

Больше всего мне в OpenShell нравится, как сделаны креды. Настоящих ключей агент внутри песочницы не видит вообще. При старте OpenShell меняет каждый кред в окружении агента на заглушку вида openshell:resolve:env:GITHUB_TOKEN. Агент кладёт её в заголовок как обычный токен, а супервизор на выходе подставляет настоящий.

Подставит он его, только если:

  • сетевая политика пускает этот бинарь на этот адрес;
  • кред привязан к хосту, порту и пути этого запроса.

Если агент (или injection, который им рулит) отправит заглушку на чужой хост, в ответ прилетит 403 с причиной credential_endpoint_mismatch. Украсть из окружения можно только строку openshell:resolve:env:..., а вне песочницы она ничего не стоит. Подставлять креды OpenShell умеет в заголовки, Basic auth, query, путь URL, тело запроса и WebSocket (последние два надо включать явно), а ещё умеет подписывать запросы AWS SigV4.

Провайдер создаётся и подключается так:

openshell provider create --name my-claude --type claude-code --from-existing
openshell sandbox create --from registry.example.com/your-org/claude-agent:latest \
  --provider my-claude --provider my-github -- claude

Агент сам просит доступ

Беда любой строгой песочницы в том, что агент постоянно упирается в отказы. Ему нужен PyPI, потом какой-то CDN, потом API, про который ты не подумал. Если на каждый отказ дёргать человека, никакой автономности не будет.

Для этого в OpenShell есть policy advisor (по умолчанию выключен). Если его включить, OpenShell кладёт в песочницу гайд и SKILL.md, а агенту становится доступен локальный API policy.local. Через него агент смотрит текущую политику и недавние отказы, отправляет предложение и ждёт решения. Предложить можно только новое сетевое правило: удалять правила и трогать настройки файлов и процессов нельзя. Кстати, OpenShell и сам набрасывает черновики правил по заблокированным соединениям, даже когда advisor выключен.

Каждое предложение потом проверяет prover. Он переводит политику, набор кредов и реестр бинарей в SMT-модель для Z3 и ищет ответ на четыре вопроса:

КатегорияВопрос
link_local_reachЕсть ли доступ к 169.254.0.0/16 или fe80::/10?
l7_bypass_credentialedМожет ли бинарь с не-HTTP протоколом достучаться до хоста, где есть кред?
credential_reach_expansionДостаёт ли бинарь до хоста с кредом, куда раньше не мог?
capability_expansionПоявился ли новый HTTP-метод там, где кред уже был?

Последние два вопроса prover задаёт старой и новой политике и показывает только то, что добавилось. Если нашлось хоть что-то, автоодобрения не будет. Если автоодобрение включено и находок нет, правило применяется сразу, без рестарта. Но по умолчанию автоодобрение выключено, и каждое предложение ждёт человека.

🧮 Агент просит доступ, prover решает
Сейчас в политике одно правило: gh читает api.github.com. В песочнице лежат креды GitHub для api.github.com:443 и github.com:22. Агент упёрся в отказ и просит новое правило. Выбери, что он просит.
Агенту надо поставить пакет.
- host: pypi.org port: 443 protocol: rest enforcement: enforce access: read-only binaries: [/usr/bin/pip]
нет
link_local_reach
Пускает на 169.254.0.0/16 или fe80::/10?
нет
l7_bypass_credentialed
Бинарь без HTTP достаёт до хоста с кредами?
нет
credential_reach_expansion
Бинарь впервые достаёт до хоста с кредами?
нет
capability_expansion
Новый HTTP-метод там, где креды уже были?
нет
эвристика advisor-а
Приватные адреса и wildcard-хосты?
✅ Одобрено автоматически. Сетевые правила меняются на ходу, так что новое подхватится без рестарта песочницы.
Настоящий prover переводит политику, набор кредов и реестр бинарей в SMT-модель для Z3 и ищет пути до хостов с кредами. capability_expansion он не показывает, если для того же бинаря и хоста уже нашёл credential_reach_expansion. Опасно ли действие по смыслу, он не решает: GET и PUT в репозиторий для него одинаково «можно».

В README prover-а честно написано, чего он не умеет. Он проверяет, может ли бинарь это сделать, но не опасно ли это. PUT и GET в репозиторий для него одинаково «действия с кредом», а нужно ли это действие вообще, решает тот, кто ревьюит.


Как запустить

Требования (по support matrix v0.1.6):

ЧтоМинимум
ОСLinux Debian/Ubuntu x86_64 и aarch64, macOS Apple Silicon через Docker Desktop; Windows через WSL 2 — экспериментально
ЯдроLandlock ABI 3 (Linux 6.2+), seccomp user notification
РантаймDocker 28.0+, или Podman 5.x, или Kubernetes 1.29 + Helm 3

Установка и первая песочница:

curl -LsSf https://raw.githubusercontent.com/NVIDIA/OpenShell/main/install.sh | sh
openshell sandbox create --name demo

Установщик ставит CLI и локальный gateway. Образ по умолчанию — минимальная Ubuntu 24.04 (nvcr.io/nvidia/base/ubuntu:24.04), и агента в нём нет. Образ с Claude Code или Codex собираешь сам, пушишь в registry и передаёшь через --from: с 0.1.0 OpenShell больше не собирает Dockerfile на лету.

Дальше пригодятся:

# зайти внутрь
openshell sandbox connect demo
# выполнить команду
openshell sandbox exec -n demo -- ls -la /workspace
# логи, включая решения allow/deny
openshell logs demo --tail
# что сейчас разрешено
openshell policy get demo --full
# TUI
openshell term

Есть SDK для Python (uv add openshell), TypeScript (@nvidia/openshell-sdk) и Go, а для самих агентов — скилл: npx skills add NVIDIA/OpenShell.

✅А при чём тут NemoClaw

NemoClaw — надстройка NVIDIA, которая запускает OpenClaw внутри OpenShell. Песочница, политики и маршрутизация — это всё OpenShell, а NemoClaw только дёргает его CLI и API, ставит политику построже и чистит образ (выкидывает gcc, make, netcat). Весной NemoClaw часто называли «корпоративной альтернативой OpenClaw», но точнее сказать, что это плагин OpenClaw для OpenShell.


Где не спасёт

  • В рамках разрешённого агент делает что хочет. Дал gh право read-write на свой репозиторий — injection может заставить агента запушить мусор, и OpenShell это пропустит. Он ограничивает ущерб от injection, но саму injection не ловит.
  • audit по умолчанию. Про это выше: L7-правила без enforcement: enforce только пишут в лог.
  • tls: skip пропускает трафик без расшифровки TLS, и тогда метод и путь никто не видит. Документация прямо запрещает ставить его вместе с L7-протоколом.
  • От сети он не отрезает совсем. Модель такая: заранее разрешённые адреса плюс ревью всего нового. Нужен полный air gap — это не сюда.
  • Проект молодой. 0.1.0, после которого старые установки пришлось сносить, вышел месяц назад. Интерфейсы с пометкой Experimental могут поменяться даже в патч-релизе. Windows пока экспериментальный, WebSocket-транспорт Codex App Server в проде не поддерживается.
  • Телеметрия включена по умолчанию. Выключается через OPENSHELL_TELEMETRY_ENABLED=false.

TL;DR

🔥Коротко
  • OpenShell — открытый (Apache 2.0) рантайм NVIDIA, который запускает агента в песочнице, а все решения принимает за пределами его процесса.
  • Файлы режет Landlock, сеть перехватывает seccomp, а у самого контейнера или VM сети нет вообще. Всё идёт через супервизор.
  • Политика на YAML: бинарь + хост + порт, затем метод + путь. Всё, что не разрешено, запрещено, хэш бинаря проверяется, метаданные облака закрыты всегда.
  • Настоящих кредов агент не видит: в окружении лежит заглушка, а ключ подставляется на выходе и только для привязанного хоста.
  • Агент может сам попросить правило, а prover на Z3 ищет в нём новые пути к кредам. Нашёл — решает человек.
  • Ловушки: enforcement по умолчанию audit, inference.local больше нет, с 0.0.x на 0.1.x — только через переустановку.

Мне эта штука нравится больше, чем очередной «AI-файрвол» на промптах. Тут понятная модель угроз и механизмы ядра, которые можно проверить, а не ещё одна LLM, которая просит другую LLM вести себя хорошо. Из чего вообще состоит агент, я разбирал в статье про харнесы. А если надо, чтобы секреты не уезжали к LLM-провайдеру прямо в промптах, смотри guardrails-llm-filter: это другой слой защиты, и одно другое не заменяет.


Источники