Skip to content

feat(isolation): запис агента обмежено worktree на рівні ОС - #60

Merged
vitaliytv merged 2 commits into
mainfrom
claude/worktree-isolation
Aug 19, 2026
Merged

feat(isolation): запис агента обмежено worktree на рівні ОС#60
vitaliytv merged 2 commits into
mainfrom
claude/worktree-isolation

Conversation

@vitaliytv

Copy link
Copy Markdown
Member

Рішення

Ізолюємо лише файловий запис і лише в межі worktree.

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

Що це дає: агент, який зірвався, не може зіпсувати робоче дерево, чужі worktree, конфіги користувача чи систему — усе поза worktree для нього read-only.

Механізм

sandbox.isolation: worktree (вимкнено за замовчуванням, той самий принцип, що в skill_profiles) обгортає CLI у sandbox-exec. Дозволено на запис: worktree, TMPDIR і явно перелічені sandbox.isolation_writable (для стану CLI на кшталт ~/.claude) — кожен отвір видно в конфігу.

Корінь — worktree, а не тека вузла: агент штатно чіпає весь чекаут (код, тести, конфіги), і звуження до теки вузла зробило б пісочницю непридатною, а не суворішою.

Пастка, яку варто знати

Шлях обовʼязково канонізується. sandbox-exec звіряє резолвлені шляхи: профіль із логічним /var/folders/… не покриває реальний /private/var/folders/…, і пісочниця забороняє запис навіть у власний worktree. Симптом — «усе зламалось», причина — непомітна. Я наступив на це під час перевірки; тепер на це є тест.

Fail closed

На непідтримуваній платформі — відмова run-а, а не тихий запуск без ізоляції: виглядати захищеним, не будучи ним, гірше за явну відмову. Реалізовано macOS; Linux свідомо не вгадуємо (bwrap без можливості перевірити — це саме той випадок, коли неперевірений код у безпековому місці гірший за його відсутність).

Найважливіший тест модуля справді запускає пісочницю і перевіряє, що запис у worktree проходить, а запис у $HOME — ні. Він доводить ізоляцію, а не збирання рядка профілю.

Побічна знахідка

Під час перевірки набір впав десятьма помилками. Розібрався: одна тимчасова невдача (budget-exceeded під паралельним clippy+лінтом) отруювала PATH_LOCK, і сусідні тести падали каскадом PoisonError — справжню причину доводилось відкопувати з-під наслідків. Замок лише серіалізує мутацію PATH і жодного інваріанта не боронить, тож тепер unwrap_or_else(into_inner). Перевірив, що це не моя регресія: 3/3 прогони чисті і на main, і зі зміною.

Карта

Заразом актуалізовано docs/conformance.md:

  • рядок sandbox тепер називає межу чесно — «лише файловий запис, лише macOS»;
  • MCP позначено «розвиток спинено» за твоїм рішенням, з аналізом трьох варіантів у «Закритих питаннях» (чинний стан робочий; ЧАСТКОВО тепер не борг, а зафіксована межа);
  • секція «Відкриті питання» була порожньою («немає») попри те, що питання накопичились — заповнена шістьма, з directory.rs першим.

Перевірка

cargo test --workspace459 passed, clippy й лінт чисті.

Рішення Vitalii: ізолюємо лише файловий запис і лише в межі worktree.
Повна ізоляція (мережа, exec) ламає підписочні CLI — вони ходять у власні
API і запускають тулчейни, — тож її вимикали б назад цілком. Вузька межа,
яку не хочеться вимикати, захищає більше за широку, яку вимкнули.

sandbox.isolation: worktree (вимкнено за замовчуванням, як skill_profiles)
обгортає CLI у sandbox-exec: запис дозволений у worktree, TMPDIR і явно
перелічені sandbox.isolation_writable; усе інше read-only. Мережа й
allowlist команд лишаються декларативними.

Корінь — worktree, а не тека вузла: агент штатно чіпає весь чекаут, і
звуження до теки вузла зробило б пісочницю непридатною, а не суворішою.

Шлях канонізується обовʼязково. sandbox-exec звіряє резолвлені шляхи, і
профіль із логічним /var/folders/… не покриває реальний /private/var/…
— пісочниця забороняла б запис навіть у власний worktree. Симптом «усе
зламалось», причина непомітна; на це є тест.

На непідтримуваній платформі — відмова run-а, а не тихий запуск без
ізоляції: виглядати захищеним, не будучи ним, гірше за явну відмову.
Реалізовано macOS (перевірено живим прогоном пісочниці в тесті), Linux
свідомо не вгадуємо.

Побічно: PATH_LOCK у тестах більше не отруюється — одна тимчасова невдача
за budget перетворювалась на десяток PoisonError, і справжню причину
доводилось відкопувати з-під наслідків.
@vitaliytv
vitaliytv merged commit 2a0657d into main Aug 19, 2026
2 of 4 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant