Агент, которому дали доступ к файловой системе, сети и переменным окружения, — это процесс, который может сделать с вашей машиной почти всё, что может сделать запустивший его пользователь. Вопрос не в том, доверяем ли мы модели, а в том, какие именно примитивы ядра ограничивают её действия и в каком порядке они применяются. Порядок здесь не деталь: несколько механизмов изоляции ломаются, если применить их не в той последовательности, и часть отказов выглядит как успешная защита, хотя на деле это лишь предсказание supervisor'а.
Уровни изоляции и примитивы ядра
В gVisor изоляция строится вокруг seccomp-фильтрамеханизма ядра, который перехватывает системные вызовы процесса и разрешает или запрещает каждый из них по заданному списку правил, устанавливаемого один раз при старте песочницы. Это накладывает жёсткое требование: набор разрешённых системных вызовов должен быть настолько широким, насколько широким может оказаться набор capabilitiesвозможностей, которые процесс может запросить за время жизни. Фильтр ставится в installSeccompFilters; при отключённом seccomp он не ставится вовсе, и в лог уходит предупреждение о менее безопасном режиме. Набор правил зависит от конфигурации: host-сеть включается, когда сеть сконфигурирована как host, raw-сокеты — только при host-сети и включённом raw, host-файловая система соответствует DirectFS, а TPUProxy и RDMAProxy определяются по спецификации и конфигурации.
Отдельный механизм — runsc-fd-parkingвспомогательный процесс, который удерживает закреплённые файловые дескрипторы песочницы до её завершения, чтобы сама песочница не оказалась их последним держателем. Для этого он открывает pidfd и запускается в собственной сессии.
В greywall файловая изоляция возложена на bubblewrap, а Landlock-хелперы оставлены как legacy/backup. Внутрипроцессно применяются no_new_privs (через prctl) и seccomp. Сетевой seccomp имеет три режима: Restrictedзапрещает сетевые системные вызовы, оставляя только сокеты семейства AF_UNIX, ProxyRoutedпропускает сокеты только семейств AF_INET и AF_INET6, чтобы трафик шёл через прокси, и VmSocketRestrictedзапрещает только сокеты семейства AF_VSOCK.
В hajiz заявлена сборка нескольких механизмов безопасности ядра в одну систему с моделью default-denyвсё запрещено, вы явно разрешаете только необходимое: namespaces, Landlock, seccomp-BPF и eBPF-аудит, без sudo.
Модель доверия к агенту и слои стека
В Orloj модель доверия не выражается терминами semi-trusted/untrusted — вместо этого доверие ограничивается политиками, вычисляемыми во время выполнения. Governance-слой встроен в runtime, а не вынесен в отдельный процесс. AgentPolicy проверяется перед каждым ходом агента и ограничивает разрешённые модели, заблокированные инструменты и бюджет токенов на запуск. AgentRole вместе с ToolPermission проверяются перед каждым вызовом инструмента: worker собирает разрешения агента из всех привязанных ролей и сверяет их с требованиями инструмента. Все решения governance детерминированы и fail-closedпри неопределённости система отказывает, а не разрешает, а отказы возвращают структурированные ошибки и попадают в task trace и history.
Слои в Orloj разделены явно: Execution Runtime отвечает за последовательное и message-driven выполнение, workers, leases, heartbeats, retries, idempotency keys и dead-letter states; Governance & Human Review — за политики, роли, разрешения и одобрения; Tool & Integration Layer — за HTTP, gRPC, внешние сервисы, webhook callbacks, discovery MCP-серверов, CLI, WASM, A2A interop, auth, изоляцию, таймауты и retries.
В Clawdstrike доверие реализовано через guard stackнабор композируемых проверок на границе инструмента, каждая из которых возвращает вердикт с доказательствами, а режим fail-fast или aggregate задаётся политикой на границе инструмента. SDK-адаптеры и сенсоры ОС подают одно каноническое событие в policy engine; guard stack возвращает вердикт, и вердикт сопровождается подписью Ed25519. Каждый guard — композируемая проверка на границе инструмента, возвращающая вердикт с доказательствами; режим fail-fast или aggregate настраивается на уровне политики. В enterprise-режиме цепочка receipt передаётся по NATS в Spine checkpointer, где независимый свидетель со-подписывает каждый батч.
Порядок применения механизмов и почему он критичен
Пайплайн изоляции в дочернем процессе hajiz выполняется в хуке pre_exec — после fork(), но до exec(), — и порядок шагов строгий. Сначала namespaces: unshare для user, mount, IPC, UTS и network, запись uid/gid-маппингов, remount / как MS_PRIVATE. Затем capabilities, затем Landlock, затем seccomp-BPF, и последним — exec.
Внутри шага capabilities порядок тоже важен: сначала PR_CAPBSET_DROPвызов, который убирает указанную capability из bounding set — набора прав, которые процесс в принципе может получить, затем PR_CAP_AMBIENT_CLEAR_ALLвызов, который очищает ambient set — набор прав, автоматически сохраняемых при запуске новых программ, затем capsetвызов, который устанавливает effective, permitted и inheritable наборы capabilities процесса с нулевыми значениями, и наконец PR_SET_NO_NEW_PRIVS.
Нарушение порядка ломает защиту, и каждое нарушение ломается по-своему. Если обнулить capabilities до PR_CAPBSET_DROP, то вместе с остальными правами уходит CAP_SETPCAP, и дроп bounding set завершится с EPERM. Дроп capabilities до user namespace тоже ломается: у непривилегированного пользователя нет capabilities для дропа — нужно сначала войти в user-ns. Если seccomp поставить до Landlock, то загрузка Landlock требует landlock_create_ruleset, а строгий seccomp этот системный вызов уже заблокирует. Remount / до user namespace не сработает: mount(MS_PRIVATE) требует CAP_SYS_ADMIN, доступный непривилегированному юзеру только внутри user-ns. cgroups v2 применяются родительским процессом к себе до spawn child, а при выходе делается cleanup; если cgroups v2 недоступны — предупреждаем и продолжаем без лимитов.
Файловая система: deny-by-default и carveouts
Последовательность монтирований bubblewrap строится в create_filesystem_args и дополняется в create_bwrap_flags. Сначала выбирается база: при полном доступе на чтение добавляется read-only корень и минимальный device tree (--ro-bind / / --dev /dev); иначе стартуют с пустого корня (--tmpfs / --dev /dev) и накладывают scoped --ro-bind для разрешённых читаемых корней. Затем маскируются unreadable-предки writable-корней, после чего writable-корни перепривязываются через --bind <root> <root>, а под ними повторно применяются read-only защиты и маски вложенных unreadable-carveout'ов.
Маски должны идти после bind-ов, потому что bind пересоздаёт поддерево и стирает ранее наложенные маски. Тесты это фиксируют: один требует, чтобы unreadable carveout был повторно применён после writable bind, другой — чтобы метаданные монтировались после своего writable-корня.
Writable roots реализованы через --bind <root> <root>: после базового read-only корня разрешённые для записи корни перепривязываются, чтобы вернуть запись. Read-only subpaths внутри writable root обрабатываются так: если путь существует и находится внутри разрешённых для записи путей, добавляется --ro-bind <subpath> <subpath>, а если не существует — маскируется первый отсутствующий компонент. Empty-file bind-data открывает /dev/null, сохраняет его дескриптор и передаёт через --ro-bind-data <fd> <path>. Empty-directory маска задаёт права 555, монтирует tmpfs и перемонтирует в read-only.
Случай writable-symlink внутри read-only пути обрабатывается отдельно: если обход логического пути находит симлинк под writable root, возвращается фатальная ошибка. Причина — гонка TOCTTOUпроверка и использование разнесены во времени, и объект между ними может измениться: маскирование разрешённого к чтению пути через изменяемый симлинк защитило бы старую цель, тогда как логический путь мог бы позже указывать в другое место.
Расширение unreadable-glob'ов через ripgrep
Расширение начинается с split_pattern_for_ripgrep: она разрешает относительный шаблон в абсолютный, находит индекс первого glob-метасимвола и делит строку на статический префикс (корень поиска) и glob-суффикс. Если префикс пустой или равен /, расширение не выполняется, и вызывающий код падает с фатальной ошибкой, требуя не-root префикс.
Суффикс прогоняется через escape_unclosed_glob_classes, которая для каждого [ собирает содержимое до ]: если ] найден, класс сохраняется как есть, иначе открывающая скобка заменяется на литерал \[ вместе с накопленным содержимым. Это нужно потому, что политика файловой системы принимает незакрытый [ как литерал, а ripgrep трактует его как невалидный синтаксис glob — экранируется только незакрытая открывающая скобка класса.
Затем для каждого корня вызывается ripgrep_files (rg --files --hidden --no-ignore --null с --glob на каждый шаблон) или, если rg не найден, glob_files, которая строит GlobSet через GlobBuilder с literal_separator(true) и allow_unclosed_class(true) и обходит дерево. Найденные пути дополняются каноническими целями симлинков, а при превышении лимита совпадений возвращается фатальная ошибка.
DefaultDenyRead, allowWrite и denyWrite в greywall
В greywall чтение по умолчанию запрещено: defaultDenyRead при отсутствии значения считается true, и тогда доступны только системные пути, текущий рабочий каталог и пути из allowRead. Чтобы отказаться от этого, задают "defaultDenyRead": false. Запись тоже запрещена по умолчанию — её нужно явно разрешить через allowWrite, а denyWrite перекрывает allowWrite и служит для защиты секретов и опасных файлов.
denyWrite всегда побеждает: даже если путь указан в allowWrite, добавление его в denyWrite блокирует запись. Это позволяет защищать чувствительные файлы внутри в остальном доступного для записи каталога:
"allowWrite": ["."], "denyWrite": [".env", ".git/hooks"]Дополнительно greywall защищает некоторые опасные цели независимо от конфигурации — файлы запуска оболочки, git-хуки и .env. Эта «dangerous file protection» блокирует запись независимо от конфигурации, снижая векторы закрепления и подмены окружения: .git/hooks/*, файлы запуска оболочки (.zshrc, .bashrc и т. п.) и некоторые каталоги конфигурации редакторов и инструментов.
Сеть: выбор режима seccomp
Режим выбирается функцией network_seccomp_mode. Сначала проверяется should_install_network_seccomp: seccomp ставится, если политика сети не включена либо если разрешена сеть для прокси. Если ставить не нужно — фильтр не устанавливается; иначе при proxy-routed сети выбирается ProxyRouted, а во всех остальных случаях — Restricted.
В режиме Restricted безусловно запрещаются connect, accept, accept4, bind, listen, getpeername, getsockname, shutdown, sendto, sendmmsg, recvmmsg, getsockopt, setsockopt, а для socket и socketpair запрет срабатывает только когда первый аргумент (domain) не равен AF_UNIX. В режиме ProxyRouted для socket запрещаются все семейства кроме AF_INET и AF_INET6 (а если managed_network.dangerously_allow_all_unix_sockets истинно, то и кроме AF_UNIX), для socketpair запрещается всё кроме AF_UNIX. В режиме VmSocketRestricted для socket и socketpair запрещается только AF_VSOCK.
Во всех режимах, кроме VmSocketRestricted, дополнительно запрещаются ptrace, process_vm_readv и process_vm_writev, а также всегда запрещаются io_uring_setup, io_uring_enter и io_uring_register.
Маршрутизация трафика через внешний прокси
На Linux greywall запускает команду под bubblewrap с --unshare-net, поэтому песочница получает собственное изолированное сетевое пространство имён, и наружу ничего не может выйти напрямую. Исходящий трафик идёт через TUN-устройство внутри этого пространства: tun2socks читает каждый пакет и пересылает его через Unix-socket-мост к HTTP- и SOCKS5-прокси greyproxy на хосте. Захват прозрачный и работает даже для приложений, игнорирующих переменные окружения прокси. DNS идёт тем же путём: DNS-мост внутри пространства имён пересылает запросы через границу Unix-сокета к DNS-прокси greyproxy.
Если TUN-устройство недоступно, greywall откатывается к установке HTTP_PROXY, HTTPS_PROXY и ALL_PROXY внутри пространства имён, и тогда маршрутизируются только приложения, уважающие эти переменные, а остальные блокируются изоляцией пространства имён.
На macOS нет ни TUN-устройства, ни DNS-моста: песочница работает под sandbox-exec со сгенерированным профилем Seatbelt, который по умолчанию запрещает все исходящие сетевые соединения, добавляя единственное исключение — соединения к HTTP- и SOCKS5-портам greyproxy на localhost. Greywall также выставляет переменные прокси в окружении процесса; приложения, которые их игнорируют, не выходят в сеть незаметно, а получают отказ Seatbelt. Из-за отсутствия прозрачного захвата на macOS сетевая гарантия слабее, чем на Linux, для любого приложения, игнорирующего переменные окружения прокси, и это сделано намеренно: тихий обход хуже жёсткого отказа.
Greywall не запускает собственный прокси и не решает, какие хосты разрешены: песочница блокирует прямой исходящий трафик на уровне ОС, а единственная достижимая изнутри песочницы сетевая точка — SOCKS5-прокси. По умолчанию greywall указывает на Greyproxy по адресу socks5://localhost:43052 с DNS на localhost:43053; если прокси не запущен, у песочницы нет сети вообще.
Audit-режим hajiz
Audit-режим запускает бинарь без изоляции, наблюдает его системные вызовы и автоматически генерирует TOML-профиль с нужными разрешениями. Запуск выполняется как hajiz --audit /usr/bin/curl https://example.com, вывод можно направить в файл через --audit-output curl.toml, а затем применить профиль через --profile curl.toml.
Бэкенд трассировки выбирается автоматически: eBPF применяется при запуске от root или с CAP_BPF и трейсит системные вызовы через raw_tracepoint/sys_enter, давая низкий overhead и только имена вызовов; strace — непривилегированный fallback, медленнее, но дополнительно захватывает пути файлов и сетевые адреса. В eBPF-программе фильтрация идёт по tgid, и при несовпадении возвращается 0; идентификатор системного вызова берётся из аргументов контекста и инкрементирует счётчик в карте. Сгенерированный профиль использует группы io, memory, threading, network, process.
Права, capabilities и user namespaces
В gVisor при запуске sandbox-процесса проверяется, является ли текущий EUID ненулевым. Если задан host-сеть или DirectFS и в спецификации есть user namespace, то при rootless EUID проверяется, можно ли обойтись непривилегированным маппингом: CanUseUnprivilegedMappingпроверка, что uid- и gid-маппинги пусты либо содержат ровно один элемент с Size==1 и HostID, равным текущему euid/egid. Если это так, применяются SetUIDGIDMappings и GidMappingsEnableSetgroups=false, иначе выставляется флаг для последующего вызова SetUserMappings через newuidmap/newgidmap. В обоих rootless-случаях вызывается ConfigureCmdForRootless, который создаёт socketpair, донатит один конец для синхронизации userns и выставляет ambient capabilities, включая CAP_SETUID, CAP_SETGID, CAP_SYS_ADMIN и CAP_SETPCAP, а также добавляются аргументы с uid/gid.
В container.go для gofer при rootless EUID без user namespace работа завершается ошибкой, иначе применяется та же логика, а после старта gofer вызывается SetUserMappings по PID процесса.
В hajiz сброс capabilities происходит в дочернем процессе через хук pre_exec. Между broker start и drop_all_capabilities() остаётся привилегированное окно: загрузка BPF, attach и sendmsg, длящееся микросекунды. В sandbox окно привилегий — от fork() до первой инструкции в userspace, то есть ровно один syscall return, и первое, что делает процесс, — drop_all_capabilities(). Целевой бинарь никогда не запускается с повышенными правами — ни на одну инструкцию.
Ресурсные лимиты и cgroups
hajiz применяет лимиты ресурсов через cgroups v2: доступные группы — io, memory, threading, network, process. Пример запуска: hajiz --max-mem 512M --max-cpu 50 --max-pids 64 /usr/bin/myapp. Если cgroups v2 недоступны, hajiz предупреждает и продолжает без лимитов.
В gVisor createRoot создаёт cgroup'ы для контейнера: если args.Spec.Linux пуст, он инициализируется, а CgroupsPath по умолчанию выставляется в / + ID контейнера, если он пуст и не включён тестовый режим. При не отключённых cgroup'ах вызывается setupCgroupForRoot, возвращающий parent и sub cgroup; при cgroupfs контейнер присоединяется к sub-cgroup, иначе к parent, потому что присоединение к нелистовым cgroup'ам в cgroupsv2 незаконно и вернёт EBUSY.
Для подконтейнеров cgroup не создаётся, если спецификация — root-контейнер без аннотации родительской cgroup, или если спецификация Linux пуста либо путь cgroup пуст; иначе создаётся cgroup и вызывается установка compat-каталога, чтобы cAdvisor и другие inotify-инструменты могли обнаружить подконтейнер. При ошибках EACCES/EROFS в rootless-режиме логируется предупреждение и настройка пропускается, а иначе ошибка оборачивается.
adjustSandboxOOMScoreAdj пропускает настройку, если спецификация — root-контейнер и идёт destroy, загружает контейнеры, игнорирует контейнеры типа sandbox и контейнеры без процесса, находит минимальный OOMScoreAdj, а при destroy без найденного score берёт сохранённое исходное значение. Если score найден, вызывается setOOMScoreAdj, который открывает /proc/<pid>/oom_score_adj на запись и пишет значение, игнорируя NotExist и ESRCH как гонки с завершением процесса.
Управление секретами и учётными данными
Greywall сначала выполняет детекцию: сканирует переменные окружения по списку известных имён учётных данных и распространённых суффиксов, после чего каждому найденному значению присваивает уникальный плейсхолдер вида greyproxy:credential:v1:gw-<id>:<digest>, регистрирует соответствие плейсхолдера реальному значению в greyproxy через sessions API и переписывает окружение песочницы так, чтобы переменные содержали плейсхолдер. При HTTP-запросе из песочницы greyproxy заменяет каждое вхождение плейсхолдера в заголовках или параметрах запроса на реальное значение перед отправкой вверх по потоку, а при выходе песочницы сессия удаляется.
Флаг --secret VAR помечает переменную как секрет вручную, если она не подпадает под авто-детекцию, при этом переменная должна существовать в окружении, а пустые значения пропускаются. Флаг --inject LABEL внедряет учётные данные, хранящиеся в панели greyproxy: значение не обязано быть в оболочке, greyproxy предоставляет плейсхолдер, а greywall устанавливает его как переменную окружения внутри песочницы.
Сессия живёт по TTL 15 минут по умолчанию, greywall обновляет её каждые 60 секунд через heartbeat, при неудаче heartbeat (например, из-за перезапуска greyproxy) сессия регистрируется заново, а при выходе удаляется. На macOS нет аналога bind-mount namespaces Linux, поэтому sandbox-exec может разрешать или запрещать доступ к файлам, но не перенаправлять чтение на другой файл: по умолчанию .env-файлы полностью запрещены в профиле Seatbelt при активной защите учётных данных, чтение даёт permission-denied. Подстановка в переменных окружения и на HTTP-уровне продолжает работать, а обходной путь — использовать --inject, чтобы учётные данные существовали только как переменные окружения с плейсхолдерами внутри песочницы, а не на диске в .env.
Сокращение видимости окружения в SkillSandbox
SkillSandbox очищает окружение перед запуском процесса и затем добавляет только явно разрешённые переменные: env_clear() перед spawn, после чего внедряются только объявленные переменные. Это делается на шаге конвейера «Build filtered env», который идёт после применения сетевых правил и перед запуском процесса навыка. Список разрешённых переменных задаётся в манифесте skillsandbox.yaml в секции env_vars.allow, например allow: ["OPENWEATHER_API_KEY", "LANG", "PATH"], а всё, что не перечислено, отбрасывается — в комментарии к манифесту прямо помечено, что AWS_SECRET_ACCESS_KEY и GITHUB_TOKEN будут stripped. За эту фильтрацию отвечает модуль enforcer/env_filter.rs.
Дополнительно сетевой канал утечки закрывается отдельным механизмом: рантайм резолвит объявленные домены в IP-адреса и применяет цепочку iptables с политикой default-deny, где разрешены только объявленные домены и порты, а остальное отбрасывается на уровне ядра. В демо это подтверждается выводом о том, что зловредный навык собрал 4 переменные окружения и 0 ценных учётных данных, а HTTPS-эксфильтрация заблокирована.
Политики, вердикты и аудит
В Clawdstrike каноническое событие — это единый формат, в который SDK-адаптеры и сенсоры ОС подают данные. Это событие поступает в policy engine вместе со стеком проверок, где каждый guard — композируемая проверка на границе инструмента, возвращающая вердикт с доказательствами. Стек возвращает вердикт: при allow действие выполняется, при deny оно блокируется по принципу fail-closed, и каждый вердикт сопровождается Ed25519-квитанцией, содержащей решение, политику, его принявшую, и доказательства. Квитанции канонизируются по RFC 8785, поэтому подпись проверяется побайтово одинаково в Rust, TypeScript и Python.
Fail-closed означает: невалидные политики отклоняются при загрузке, ошибки оценки запрещают доступ, а отсутствующая конфигурация по умолчанию ограничительна. Понижение уровня безопасности требует явного, аудируемого действия.
Защита от подмены логов строится на хеш-цепочкецепочке, в которой каждое событие хранит хеш предыдущего, поэтому незаметно изменить или удалить запись нельзя. Периодически создаются чекпоинты, содержащие корень Merkle-дерева по диапазону событий, временную метку и подпись от корня и временной метки. Correlation-контексты подписываются корневым агентом с помощью Ed25519 по канонизированному набору полей trace_id, span_id, parent_span_id, root_agent, created_at. События также подписываются Ed25519 (64-байтовые подписи), что позволяет обнаруживать подделку. Инварианты безопасности включают append-only, целостность цепочки, подписанные события, аутентичность контекста и обнаружимость пропусков.
В Orloj AgentPolicy — первая проверка в authorization flow: когда агент выбирает tool call, runtime проверяет все применимые политики до оценки role-based permissions. Проверяются три условия: если инструмент в blocked_tools, вызов немедленно отклоняется; если модель агента не в allowed_models, выполнение отклоняется; если превышен max_tokens_per_run, выполнение останавливается. Режим apply_mode определяет охват: scoped (по умолчанию) применяется только к перечисленным target_systems и target_tasks, а global — к каждому выполнению. Поле target_agents ограничивает политику только перечисленными агентами; если оно пустое, политика применяется ко всем агентам, а при заданном target_agents max_tokens_per_run действует как бюджет на агента, а не общий на систему. Агенты, не перечисленные ни в одном target_agents, ограничены только политиками без target_agents.
Пограничные случаи и отказы
При отсутствии writable root (когда защищаемый путь не существует) bubblewrap находит первый несуществующий компонент и, если он лежит внутри разрешённых на запись путей, монтирует на него /dev/null, чтобы процесс не мог создать защищаемую иерархию. Если же путь существует и является каталогом, он маскируется через --perms 000 (или 111, если внутри есть writable-потомки) и --tmpfs с последующим --remount-ro.
При symlink-ancestor'ах, если симлинк находится под writable root, возвращается фатальная ошибка, потому что маскирование разрешённого пути было бы гонкой с изменяемым симлинком. Для protected symlinked directory subpaths тест ожидает ошибку с текстом «cannot enforce sandbox read-only path», то есть такие случаи тоже fail closed. Причина в том, что разрешение текущей цели симлинка — это лишь снимок TOCTTOU: bwrap защитил бы старую цель, тогда как логический путь мог бы позже указывать в другое место.
hajiz обрабатывает ограничения окружения так: при включённом по умолчанию kernel.apparmor_restrict_unprivileged_userns на Ubuntu 23.10+/24.04 LTS блокируется unshare(CLONE_NEWUSER) без явного AppArmor-профиля, и hajiz это определяет и печатает подсказку. При недоступности cgroups v2 hajiz предупреждает и продолжает без лимитов ресурсов. Для трассировки системных вызовов eBPF применяется, когда запущено от root или с CAP_BPF, а strace служит непривилегированным fallback. Поскольку eBPF требует CAP_BPF, а целевой бинарь не должен работать привилегированно, используется broker/sandbox split: ядро проверяет CAP_BPF только в момент создания BPF-объекта, после чего дескриптор можно передать через SCM_RIGHTS любому процессу, в том числе непривилегированному.
Внутри Docker изоляция сетевого пространства имён обычно недоступна без --cap-add=NET_ADMIN, и greywall это обнаруживает и автоматически переключается на резервный режим: изоляция файловой системы через bubblewrap и блокировка команд продолжают работать, а маршрутизация сети переходит на переменные окружения прокси, при этом программы, игнорирующие эти переменные, не будут изолированы по сети. Проверить доступные возможности можно командой greywall --linux-features, а для полной сетевой изоляции в Docker нужно добавить --cap-add=NET_ADMIN.
Greywall не позиционируется как защита от полностью недоверенного кода: он является defense-in-depthподход к безопасности, при котором несколько независимых защитных механизмов выстраиваются слоями, чтобы отказ одного не открывал всю систему для полу-доверенного кода — скриптов цепочки поставок, незнакомых репозиториев, AI-агентов — и не предназначен для сдерживания активно вредоносного кода, пытающегося вырваться. Он не предотвращает исчерпание ресурсов (CPU, память, диск), не предотвращает эксфильтрацию данных на разрешённые домены и не защищает от эксплойтов ядра или повышения привилегий. Для агентов рекомендуется рассматривать их как полу-доверенную автоматизацию: ограничивать запись рабочим пространством (и, возможно, /tmp), настраивать внешний прокси на разрешение только нужных сетевых назначений и использовать режим мониторинга для аудита заблокированных попыток и ужесточения политики.
Что видит клиент и наблюдаемость
SkillSandbox выдаёт три слоя наблюдаемости: поток событий в терминал через --watch, экспорт спанов OpenTelemetry через --otel и структурированный execution trace в формате JSON. В execution trace попадают события с полями kind и message: например, dns_resolution с сообщением о разрешении домена, network_egress_allowed с разрешённым соединением, network_egress_blocked с сообщением о блокировке необъявленного исходящего трафика, env_var_access о фильтрации переменных окружения, stderr с сообщением о блокировке эксфильтрации и skill_completed о завершении с кодом 0. При попытке эксфильтрации в trace фиксируется событие network_egress_blocked с текстом о том, что весь необъявленный исходящий трафик заблокирован по умолчанию, а также событие stderr с сообщением о блокировке HTTPS-эксфильтрации. Дополнительно trace содержит violation_summary с классификациями угроз, включая credential_exfiltration с severity critical и MITRE-тактикой exfiltration, и флагом all_prevented: true. Классификация угроз автоматически сопоставляет каждое событие с тактиками MITRE ATT&CK, например credential_harvesting → credential-access и supply_chain_attack → exfiltration.
В greywall режим -d/--debug даёт подробный вывод, включающий активность прокси, решения фильтра и детали команды песочницы. Режим -m/--monitor показывает только заблокированные запросы и нарушения, что удобно для аудита и настройки политики. Флаги можно комбинировать: greywall -m -d -- npm install даёт одновременно мониторинг нарушений и полную команду песочницы. Режим --learning запускает команду в песочнице с ослабленной файловой системой (разрешены чтение и запись), чтобы трассировщик мог наблюдать полный шаблон доступа; сеть при этом остаётся изолированной. Трассировка файловой системы использует strace на Linux и eslogger на macOS; на macOS сам трассировщик работает под sudo, а команда в песочнице — от обычного пользователя. По завершении команды greywall разбирает лог трассировки, отфильтровывает системные пути, текущий рабочий каталог и чувствительные пути, сворачивает пути в минимальные наборы каталогов и сохраняет JSONC-шаблон с секциями allowRead, allowWrite, denyWrite, denyRead в ~/.config/greywall/learned/<command>.json.
Телеметрия hajiz — это структурированные JSON-события, эмитируемые на
Где смотреть в коде
- GreyhavenHQ/greywall/docs/concepts.md: Model
- loader.go: PreSeccompCallback
- backbay-labs/clawdstrike/README.md: verification
- GreyhavenHQ/greywall/docs/benchmarking.md: Initialization
- GreyhavenHQ/greywall/docs/credential-protection.md: it works
- OrlojHQ/orloj/docs/pages/concepts/architecture.md: Layer
- landlock.rs: should_install_network_seccomp
- GreyhavenHQ/greywall/docs/benchmarking.md: Factors