|
| 1 | +# ADR-0001: Threat Model for execute_script sandbox |
| 2 | + |
| 3 | +**Status:** Draft — ожидает решения владельца (OPEN_QUESTION) |
| 4 | +**Дата:** 2026-08-05 |
| 5 | +**Автор:** агент (по итогам внешнего аудита и верификации A2) |
| 6 | + |
| 7 | +## Context |
| 8 | + |
| 9 | +`sandbox/executor.py` честно декларирует в докстринге (executor.py:14): |
| 10 | + |
| 11 | +> NOT a security boundary for untrusted code. Defense-in-depth for agent tools. |
| 12 | +
|
| 13 | +Текущая реализация — два слоя защиты: |
| 14 | + |
| 15 | +1. **AST-валидация** (deny-лист): запрещены `eval`/`exec`, `importlib`, `pkgutil`, |
| 16 | + `runpy`, `ast.Delete` (executor.py:73 — «Can delete variables»), опасные |
| 17 | + атрибуты и т.д. |
| 18 | +2. **Runtime-перехват**: подмена `__import__` + allowlist модулей + выполнение |
| 19 | + в subprocess с `_build_minimal_env` (executor.py:306), который **намеренно НЕ |
| 20 | + копирует `os.environ`** (executor.py:309: «Deliberately does NOT copy |
| 21 | + os.environ — parent secrets (API keys, ...)» не утекают в дочерний процесс). |
| 22 | + Исключения-минимум: `SYSTEMROOT`, `SYSTEMDRIVE`, `TEMP`, `TMP`. |
| 23 | + |
| 24 | +Внешний аудит (2026-08-05) подтвердил: обвязка сделана аккуратно, но это |
| 25 | +классическая blacklist-модель на чистом Python-интерпретаторе **без ОС-изоляции** |
| 26 | +(нет seccomp, namespaces, gVisor). История Python sandbox escape |
| 27 | +(`object.__subclasses__` цепочки, frame-объекты в трейсбеках исключений, |
| 28 | +format-string трюки) показывает, что такие deny-листы со временем протекают. |
| 29 | + |
| 30 | +**Открытый вопрос для владельца:** для какого класса ввода эта defense-in-depth |
| 31 | +защита считается достаточной? Есть ли путь, по которому в executor попадает |
| 32 | +ввод из внешнего/недоверенного источника (не сгенерированный агентом сниппет |
| 33 | +внутри уже доверенного MCP-процесса)? |
| 34 | + |
| 35 | +**Побочное наблюдение (не блокер):** `ast.Delete` блокируется целиком — даже |
| 36 | +`del x` для локальной переменной запрещён. Это может быть побочный эффект |
| 37 | +широкой формулировки, ломающий легитимный код агента. Если владелец выберет |
| 38 | +Вариант A — рассмотреть сужение до `del` глобальных/атрибутных имён. |
| 39 | + |
| 40 | +## Decision |
| 41 | + |
| 42 | +**Рекомендация агента: Вариант A** — сохранить текущую defense-in-depth, |
| 43 | +зафиксировать границы модели угроз в этом ADR, **не расширять** поверхность |
| 44 | +без нового решения владельца. |
| 45 | + |
| 46 | +Решение финально принимает владелец. До решения `executor.py` не редактируется |
| 47 | +(зафиксировано в `.agent_task_state.md` Danger Zone). |
| 48 | + |
| 49 | +## Alternatives considered |
| 50 | + |
| 51 | +### Вариант A: Defense-in-depth (текущий) |
| 52 | +- **Применимо:** доверенные сниппеты (генерированные агентом) внутри уже |
| 53 | + доверенного MCP-процесса |
| 54 | +- **Guard:** AST-валидация + blacklist + runtime `__import__`-перехват + |
| 55 | + subprocess-изоляция с минимальным env |
| 56 | +- **Ограничение:** не защищает от агента-вредителя и от недоверенного внешнего |
| 57 | + ввода (если такой путь появится) |
| 58 | +- **Усилия:** 0 (текущее состояние); документирование границ — 1 коммит |
| 59 | +- **Риск:** если появится недоверенный источник ввода, blacklist может протечь |
| 60 | + (нужно будет поднять до B) |
| 61 | + |
| 62 | +### Вариант B: OS-изоляция |
| 63 | +- **Применимо:** недоверенный ввод (внешний агент, CI-артефакты, пользовательский |
| 64 | + код из репозиториев) |
| 65 | +- **Guard:** container/microVM + seccomp (или изолированный runtime) |
| 66 | +- **Усилия:** 2-4 недели, новая зависимость, усложнение инсталляции и поддержки |
| 67 | + Windows-платформы (проект активно поддерживает Windows) |
| 68 | +- **Ограничение:** в чистом виде избыточно для текущего единственного вызова |
| 69 | + (execute_script → доверенные сниппеты) |
| 70 | + |
| 71 | +### Вариант C: Гибрид |
| 72 | +- **Применимо:** оба класса ввода одновременно |
| 73 | +- **Guard:** threat classifier на входе: доверенные пути → Вариант A, |
| 74 | + недоверенные → Вариант B |
| 75 | +- **Усилия:** выше, чем B (два пути + классификатор) |
| 76 | +- **Ограничение:** классификатор сам становится поверхностью атаки (ошибка |
| 77 | + классификации = неверный путь выполнения) |
| 78 | + |
| 79 | +## Impact |
| 80 | + |
| 81 | +- **При Варианте A (рекомендация):** изменения кода нет. Фиксируются границы: |
| 82 | + executor — инструмент для доверенных сниппетов агента, не для пользовательского |
| 83 | + кода. `KNOWN_ISSUES`/README получают явную строку о границах. Новый |
| 84 | + недоверенный путь ввода → немедленное переоткрытие ADR (эскалация до B/C). |
| 85 | +- **При Варианте B/C:** новая зависимость, изменения в `install.py`, |
| 86 | + платформенная матрица CI расширяется. Отдельная фича со своим планом. |
| 87 | + |
| 88 | +## Status |
| 89 | + |
| 90 | +Draft — ожидает решения владельца. Связанный OPEN_QUESTION зафиксирован в |
| 91 | +`.agent_task_state.md` (A2). После решения — обновить этот раздел |
| 92 | +(✅ Accepted / ❌ Rejected / superseded) и при необходимости правки executor.py. |
0 commit comments