Перейти до вмісту

Пісочниця та безпека

Агент читає файли, виконує шелові команди й викликає зовнішні API від вашого імені. Відмова, яка вас турбує, рідко буває злим умислом — це агент, який неправильно прочитав шлях і переписав вашу робочу копію, або той, який надіслав гілку, що ви ще не встигли перевірити. Тож питання, на яке продукт має відповісти, — не «чи можна довіряти агенту», а «який радіус ураження, коли він помиляється».

Control Center відповідає на це шарами, і шари навмисно незалежні. Якщо одного бракує, інші все одно тримаються. Це важливо, бо найзовнішній шар — пісочниця ОС — єдиний умовний: він залежить від того, чи пропонує хост бекенд, а на Windows не пропонує жоден.

П’ять речей обмежують запуск агента на кожному хості, з пісочницею чи без. Жодна з них не є межею ядра; усі вони справжні. Там, де хост пропонує бекенд, пісочниця ОС стоїть над ними, а розділ «Що додає пісочниця і де вона зупиняється» нижче описує, що це змінює.

Власний worktree з copy-on-write. Кожна розмова створює власну робочу копію кожного пов’язаного репозиторію, клоновану через copy-on-write вбудованим нативним модулем rift, а не скопійовану байт за байтом, тож ізоляція достатньо дешева, щоб бути типовою. Зареєстрований репозиторій, який ви додали до робочого простору, — не той каталог, у якому працює агент: дві розмови, що торкаються одного репозиторію, не бачать редагувань одна одної, і жодна не редагує вашу робочу копію.

Гранулярність варто уточнити: worktree — на рівні розмови, а не агента. Два агенти в одній розмові отримують різні робочі каталоги overlay, які спільно використовують каталог repos/ цієї розмови — у цьому й сенс, адже вони працюють над однією зміною — тоді як їхні приватні каталоги агентів залишаються окремими. Copy-on-write — єдиний бекенд усюди, де постачається rift: файлова система, яка не вміє reflink, або каталог даних на іншому томі, ніж репозиторій, провалює підготовку, а не переходить до запасного варіанта. Windows — виняток: там бекенда copy-on-write немає, тож git worktree є бекендом, а не тихою деградацією.

Обмеження шляхів у файлових інструментах вбудованого harness. Інструменти harness read, write, edit, apply_patch, search, find і search_files розв’язують кожен шлях відносно робочого каталогу запуску плюс спільних коренів — каталогу repos/ розмови, де живуть worktree, і каталогу вкладень простору, коли він існує — і відмовляють у всьому, що опиняється поза ними. Абсолютні шляхи приймаються лише якщо вони вже були всередині. Символьне посилання всередині worktree, що вказує назовні, теж розв’язується і відхиляється. Саме тому агент не може write до ~/.ssh, навіть коли пісочниці немає й близько.

Набір інструментів режиму. Режим розмови вирішує, які інструменти взагалі матеріалізуються для запуску. У режимах лише для читання (plan, review, orchestrate) інструменти, що змінюють стан, просто відсутні в реєстрі, а шелова політика команд для цих режимів забороняє будь-яку команду, що змінює стан, напряму — git commit, git push, npm install, docker та решту — а не запитує про них. Інструмент, якого немає, не можна викликати, а це сильніша гарантія, ніж інструмент, який присутній, але обмежений.

Уніфіковані обмеження дій. Кожен інструмент, що змінює стан, оголошує класи ефектів, які може спричинити, і рушій політик розв’язує кожен із них до дозволу, запиту або заборони, перш ніж виклик виконається. prompt без під’єднаного того, хто схвалює, розв’язується як заборона — питання, на яке ніхто не може відповісти, ніколи не стає «так». Модель розв’язання дивіться в розділі Обмеження.

Облікові дані від брокера й очищене середовище. Облікові дані випускаються для кожного запуску й відкликаються, коли запуск завершується, а токен GitHub, який має запуск, може читати, але не робити push, — див. «Push проходить через Control Center» нижче. ACP, bash harness і Claude Code під нативною обгорткою отримують середовище, очищене від векторів ін’єкції динамічного компонувальника та інтерпретатора (LD_PRELOAD, DYLD_INSERT_LIBRARIES, NODE_OPTIONS, GIT_SSH_COMMAND та подібних), перш ніж процес стартує. Claude Code через NoSandboxAdapter успадковує середовище хоста.

Файлові інструменти harness виконуються в процесі сервера, а не в дочірньому процесі, тож жоден профіль пісочниці не міг би їх обмежити, навіть якби його приєднали — через загорнутий виконавець команд проходить лише bash. Їх обмежує перевірка шляхів, описана вище, і тому ця перевірка є несучою, а не другою лінією оборони.

bash — інше місце, де варто бути точним. Його команди оцінюються проти політики команд режиму, а потім запускаються з очищеним середовищем і worktree як робочим каталогом. Там, де бекенд існує, цей запуск загорнуто, тож саме пісочниця не дає шеловій команді писати поза worktree; там, де його немає — Windows або Linux без bwrap і socat — політика команд є єдиним, що стоїть між командою та рештою диска. У режимі chat політика жорстко забороняє фіксований список (підвищення привілеїв, знищення системи та диска, втеча з контейнера й простору імен) і запитує підтвердження для мутацій віддаленого стану (git push, gh pr create, публікація та встановлення пакетів). Усе, чого немає в жодному списку, виконується. Конвеєри, ланцюжки та підшели розбираються, і кожна підкоманда оцінюється окремо, а перемагає найсуворіше рішення.

Claude Code як адаптер — це випадок, де пісочниця важить найбільше. Control Center запускає його з --dangerously-skip-permissions, бо неінтерактивний claude -p інакше блокувався б назавжди на власному запиті на схвалення. Його виклики читання, запису та редагування відбуваються всередині власного процесу, і Control Center їх ніколи не бачить. Його виклики Bash перед виконанням надсилаються до хука на сервері, який класифікує командний рядок і запитує відповідне правило, а інструменти mcp__*, які обслуговує Control Center, проходять через точку контролю як завжди. Там, де бекенд існує, ці невидимі виклики все одно відбуваються всередині обгортки пісочниці, тож ОС є межею навколо них. Там, де його немає, немає нічого. Кожен адаптер зазначає це у своїй матриці примусу — дивіться матрицю чесності адаптерів.

Прапорців дозволів для окремих агентів немає. Чи може агент створити коміт, зробити push, відкрити pull request, писати в зовнішній трекер або виходити в мережу, вирішують обмеження — дозволити, спочатку запитати або заборонити, — і ті самі правила діють незалежно від того, який адаптер запускає агента.

Push — це дія, яка має витримати навіть агента, що намагається обійти правило, тож її забезпечують тим, що агент має, а не тим, що він запускає. Коли налаштовано GitHub App, запуск у worktree GitHub отримує токен установлення з областю репозиторію, який може читати вміст і працювати з pull request, але не може робити push. Конфігурація git запуску переписує кожен URL для push у GitHub (https://github.com/, git@github.com:, ssh://git@github.com/) на шлюз запусків агентів — loopback-ендпоінт у cc_server. Шлюз читає, які ref оновлює push, запитує правило Надіслати на віддалений репозиторій — тож правило, обмежене гілками, наприклад «заборонити push у main», теж діє, — і лише тоді передає push у GitHub з токеном запису, який випускає для цього й ніколи не віддає агентові. Відхилений push видно у власному виводі git:

! [remote rejected] main -> main (Control Center policy: …)

Шлюз передає push лише у власний репозиторій worktree або в репозиторій GitHub, пов’язаний із робочим простором. Fetch іде прямо в GitHub із токеном читання.

Інші шелові команди натрапляють на м’якший шлюз. Інструмент bash harness і, через хук, інструмент Bash Claude Code класифікують кожен командний рядок і запитують правило, під яке він підпадає: git commit, merge, rebase, cherry-pick, revert і am — це Створити коміт; gh pr create — Відкрити pull request; gh pr merge, review, comment і close — Опублікувати рецензію або злити; gh issue create, edit, comment і close — Запис у зовнішній трекер; установлення пакетів — Установити пакет; curl і wget — Доступ до мережі. Це розпізнає звичний запис дії, і агент, який вирішить її приховати (eval, файл зі скриптом, прямий виклик API), зможе це зробити. Push від цього не залежить.

Жоден API-ключ тікетів не потрапляє в пісочницю. Агенти пишуть тікети через MCP-інструменти Control Center, які запитують правило Запис у зовнішній трекер.

Правило Доступ до мережі читається один раз, коли запуск стартує. Мережа пісочниці або ввімкнена, або вимкнена, тож заборона вимикає її, а дозволити й спочатку запитати залишають її ввімкненою; інструменти harness web_fetch і web_search і далі запитують під час кожного виклику. Облікові дані відкликаються, коли запуск згортається, разом з усіма токенами запису, які випустив шлюз, тож дескриптор не переживає запуск.

Що додає пісочниця і де вона зупиняється

Розділ із назвою «Що додає пісочниця і де вона зупиняється»

Перелік SandboxBackend має три значення — native (Seatbelt на macOS, bubblewrap на Linux і WSL2), none і microvm. Загортання команд агента використовує лише перші два: microvm — бекенд закритої ВМ для інтерактивних терміналів і rigs, а не тихе покращення обгортки bash. Розуміння нативної форми пояснює, що ви отримуєте там, де вона приєднана, і чого вона ніколи не збиралася вам дати.

cc_server опитує хост один раз під час завантаження і, де бекенд відповідає, живить обидва шви, які має шлях диспетчеризації, бо транспорти не поділяють один. Перший (SandboxPort) загортає CLI Claude Code. Другий (SandboxManager) загортає адаптери ACP і інструмент bash вбудованого harness. Живлення лише одного залишило б решту запускатися незагорнутими, що й бувало раніше. На хості без бекенда або під --sandbox off вони всі запускаються незагорнутими, і сервер каже, який це випадок, у своєму рядку запуску. ACP і bash harness усе одно очищують середовище; Claude Code через NoSandboxAdapter успадковує середовище хоста.

Одна поверхня навмисно відмовляється від пісочниці: термінал, який обслуговує сервер, приймає аргумент бекенда для кожної сесії, а cc_server створює його з типовим значенням — шел хоста, тож термінал, який ви відкриваєте із застосунку, працює без пісочниці, якщо клієнт явно не назве native. Це справжній шел на хості сервера, а не запуск агента.

На Linux bubblewrap будує простір імен монтування, тож агент справді бачить лише те, що було змонтовано в нього. На macOS sandbox-exec застосовує профіль Seatbelt, який є типово дозвільним — повністю заборонний за замовчуванням профіль робить інтерактивний шел непридатним, бо кількість викликів dyld, XPC і Mach, які треба перелічити, практично неосяжна. Тому профіль macOS починається з (allow default) і вирізає заборони: читання шляхів із секретами, політика запису, скинута до заборони з явними дозволами поверх неї, блокування unlink на кожному забороненому шляху та його предках, щоб перейменування не могло обійти заборону запису, заборони exec для завжди небезпечних бінарників і для всього, скопійованого до каталогу з правом запису, і мережа, обмежена портами проксі на loopback плюс DNS.

Практичний наслідок: на Linux агент бачить вужчу частину файлової системи; на macOS агент усе ще може читати більшість хоста, а ваші зареєстровані робочі копії захищені явними заборонами запису й перейменування, а не невидимістю. І те, і те — ізоляція на рівні простору імен, а не межа безпеки ядра — самі бекенди кажуть про це у своїх примітках щодо можливостей.

Набір bind-mount — саме там прокладено межу між агентами. Диспетчеризація overlay (Claude Code і ACP) монтує до чотирьох шляхів, розв’язаних для кожного запуску: робочий каталог overlay агента з правом читання й запису, каталог конфігурації агента лише для читання, спільний каталог repos/ розмови з правом читання й запису, коли він існує, і каталог вкладень простору лише для читання, коли він існує. Каталог сусіднього агента ніколи не монтується. Обгортка bash harness вужча: вона монтує лише робочий каталог запуску. Це та сама форма, яку перевірка шляхів у процесі harness забезпечує програмно, тож ці дві збігаються — але лише перевірка шляхів охоплює власні файлові інструменти harness, які взагалі ніколи не доходять до простору імен монтування.

Ваші зареєстровані робочі копії додаються до набору заборон на запис поверх цього, у кожному режимі й на всіх трьох загорнутих шляхах (Claude Code, ACP, bash harness), і кожне правило видається і в буквальному, і в розв’язаному через символьні посилання написанні. Друге написання — не примха: ядро зіставляє правило заборони з розв’язаним шляхом, а macOS робить розв’язання звичайним випадком, а не екзотичним — /tmp насправді /private/tmp, /var/folders/… насправді /private/var/folders/… — тож робоча копія, досягнута через будь-який із них, була б доступною для запису, поки профіль начебто її забороняв.

Мережа під пісочницею — це список дозволених доменів для кожної сесії, який забезпечують проксі на loopback у процесі. Вихідний трафік типово заборонено: коли мережу вимкнено, проксі все одно стартують, але їхній список дозволених порожній, тож кожне вихідне з’єднання відхиляється. Коли її увімкнено, розв’язувач будує список дозволених із дбайливо підібраного базового набору — провайдери LLM API, хостинг git, реєстри пакетів, каталог моделей — плюс домени GitHub, і зіставляє з ним цільовий хост кожного з’єднання. Шаблони працюють (*.example.com збігається з піддоменами, а не з самим доменом). Окремий завжди заборонений список перемагає список дозволених і охоплює кінцеві точки метаданих хмари та стоки телеметрії. Чого не можна налаштувати, то це сам список: специфікація несе поле egressAllowlist для додаткових доменів, але ніщо у шляху диспетчеризації його не заповнює, тож на практиці кожен запуск отримує базовий набір плюс GitHub і нічого більше.

Заборони на macOS можна спостерігати: монітор читає системний журнал на предмет заборон пісочниці, відфільтровує відомий шум, дедуплікує шторми повторів і показує кожну як SandboxViolation, називаючи те, що було спробовано, і те, що було заблоковано. На Linux шлях обгортки розбирає stderr на EPERM, Operation not permitted і Permission denied і видає той самий тип події — читання системного журналу там немає. Вони надходять до власного потоку подій запуску, тож заборонений системний виклик з’являється в транскрипті, а не лише в журналі. Телеметрія існує лише поки нативна пісочниця справді загортає процес — на хості без бекенда спостерігати нічого. Вона також не пов’язана з обмеженнями: порушення походить від забороненого системного виклику, ніколи від пошуку правила.

Підтвердження походять від обмежень, а не від пісочниці

Розділ із назвою «Підтвердження походять від обмежень, а не від пісочниці»

У пісочниці немає шляху схвалення. Вона запускається, виконує, видає події та згортається; у ній немає місця, де людина могла б відповісти на питання. Коли ви бачите підтвердження в просторі — схвалити це надсилання, схвалити це видалення — його підняв рушій обмежень (або політика команд harness) перш ніж дія взагалі дійшла до середовища виконання.

Цей порядок навмисний. Схвалення, яке сталося б після системного виклику, було б записом аудиту, а не бар’єром. Саме тому правило fail-closed живе в обмеженнях: коли не під’єднано того, хто схвалює, рішення ухвалюється, перш ніж щось запуститься, і це відмова.

Запустити діагностику агента — єдине місце, де сказано, що можна перевірити вручну.