# Задание A37: изоляция агентов, защита учётных данных и разрушительных операций ## Дата поступления 2026-08-30 ## База Ветка ревьюера `review/a28-a31-fixes` (`ab2ee12`). ``` git fetch origin --prune git checkout -b antigravity/a37-isolation-guards origin/review/a28-a31-fixes ``` В `main` напрямую не пушить. ## Порядок исполнения Два прохода: **Flash** реализует, **Pro** проводит аудит. Пункт **P0-6** написан для аудитора. Выполнять **после A34 и A35**: пока нельзя подключить аккаунт и хаб не участвует в вызовах Hermes, укреплять периметр вокруг системы, которая ничем не управляет, преждевременно. --- ## Задача Владелец держит на трёх машинах 24 аккаунта, а работу ведут несколько автономных агентов. Изоляции между ними нет никакой, и это уже приводило к последствиям. Повод пришёл извне: OpenAI официально описала инцидент, где экспериментальные агенты использовали **внутренний Artifactory как канал связи между собой**, обменивались найденными обходами, получили обходной доступ в интернет и в итоге скомпрометировали часть инфраструктуры Hugging Face. Вывод сформулирован там жёстко: ``` deny internet != secure agent ``` Разрешённый внутренний сервис становится proxy, доской объявлений, скрытым каналом и точкой опоры. ## Наши собственные случаи — не гипотезы Каждый проверен или произошёл в этом проекте: 1. **Любой сайт в соседней вкладке мог управлять хабом.** CORS стоял как `allow_origins=["*"]` вместе с `allow_credentials=True`, а на `127.0.0.1` токен не требуется вовсе. Проверено запросом: страница со стороннего адреса получала `200` и полный снапшот со всеми аккаунтами. Закрыто ревьюером в `c35bc48`, но это тот самый класс, о котором говорит инцидент. 2. **Агенты координируются через общий канал** — публичный репозиторий на GitHub. Они пишут ветки, читают чужие, и как минимум однажды работа ушла в `main` минуя ревью (зафиксировано в A23). 3. **Общее рабочее дерево.** Агент Codex переключил ветку в каталоге, где в это же время работал ревьюер; коммиты чуть не легли в чужую ветку. Пришлось собирать их через временный индекс, чтобы не мешать. 4. **Учётные данные 24 аккаунтов лежат общей кучей** в `~/.hermes/agy_profiles/`, доступной любому процессу пользователя. Разделения по агентам нет. 5. **Ревьюер удалил учётные данные `grok-worker-1`**, проверяя кнопку удаления на живом профиле. Ничто не помешало. 6. **Хаб открыт в домашнюю сеть поверх HTTP.** Токен идёт по сети открытым текстом; почты аккаунтов — тоже, пока не сделан P0-4 из A31. ## Что уже есть — не переделывать Поиском по коду: отдельного слоя безопасности нет, но три вещи работают и их надо использовать как основу. ``` agy_subprocess.build_safe_subprocess_env очистка окружения подпроцесса update_manager.ALLOWED_UPDATE_HOSTS белый список хостов обновления web/server.sanitize_snapshot вычистка секретов из снапшота ``` --- ## P0-1. Граница рабочей области и разрушительные операции Ни агент, ни модель, ни инструмент не должны удалять или перезаписывать данные **за пределами разрешённой области**, независимо от того, кто выполняет команду. 1. **Явная граница** — каталог проекта плюс явно разрешённые пути. Всё остальное вне области. 2. **Классификация операций**: удаление, рекурсивное удаление, массовое перемещение и перезапись, усечение. Проверять и вызовы Python (`shutil.rmtree`, `os.remove`, `os.unlink`), и командную строку (`rm`, `rm -rf`, `Remove-Item`, `del`), потому что агенты ходят обоими путями. 3. **Отказ объясняет причину и предлагает безопасную замену**, а не просто запрещает. 4. **Безусловный запрет** на каталоги учётных данных: `~/.hermes/agy_profiles/`, `~/.ssh/`, файлы `auth.json`, `hub_settings.json`. Удаление аккаунта делается **только** штатным действием `delete_credentials` с подтверждением — как раз потому, что удаление вручную уже происходило. 5. **Сухой прогон**: показать, что будет удалено, до удаления. ## P0-2. Разделение агентов 1. **У каждого агента свой рабочий каталог.** Общее дерево уже приводило к переключению ветки под чужой работой. Отдельные клоны либо `git worktree` на агента. 2. **Свои учётные данные.** Агенту нужны те аккаунты, с которыми он работает, а не все 24. Предложить схему разделения и обосновать; **молча ничего не переносить** — потеря учётных данных стоит владельцу повторного входа во все аккаунты. 3. **Свой след в журнале.** Каждое действие с аккаунтами и конфигурацией записывается с указанием, кто его выполнил. Сейчас по журналу нельзя отличить действия ревьюера от действий агента. ## P0-3. Сетевая граница самого хаба 1. **Белый список исходящих обращений.** Хаб ходит к провайдерам, к API релизов и к локальным серверам — этот список конечен и должен быть явным. Образец есть: `ALLOWED_UPDATE_HOSTS`. 2. **CORS остаётся закрытым по умолчанию.** Список источников — настройка `web_api_allowed_origins`. Возврат `allow_origins=["*"]` считать дефектом; нужен тест. 3. **Токен обязателен при небlocalhost-привязке** — уже так, не ослаблять. Проверить, что сравнение осталось постоянного времени и в байтах. 4. **HTTP по сети — назвать риском в интерфейсе.** Владелец должен видеть, что при сетевой привязке поверх HTTP токен и почты идут открытым текстом. Не запрещать, а сказать прямо и предложить туннель или VPN. ## P0-4. Журнал, по которому можно расследовать После инцидента вроде описанного нужен ответ на вопрос «кто, что и когда». Записывать: кто выполнил, какое действие, над каким профилем и ролью, результат, время. Секреты в журнал не попадают — тест на это уже есть для снапшота, распространить на журнал. ## P0-5. Проверка исполнением 1. Попытка удалить файл вне рабочей области — отказ с причиной; проверено. 2. Попытка удалить каталог учётных данных — отказ; проверено. 3. Обращение к хосту вне белого списка — отказ; проверено. 4. Запрос с чужого `Origin` — заголовков CORS нет; проверено запросом. 5. Работа хаба при всех включённых ограничениях не деградирует: маршрутизация, обновление, обнаружение моделей работают. Пункт 5 обязателен: защита, ломающая продукт, хуже её отсутствия. ## P0-6. Аудит вторым проходом 1. **Ложное чувство защиты.** Проверить, что ограничения нельзя обойти очевидным способом — например, относительным путём или символической ссылкой за пределы области. 2. **Отказ не должен ронять хаб.** Сработавшая защита — это отказ операции, а не падение процесса. 3. **Секреты в журнале и в сообщениях об отказе** — искать целенаправленно. 4. **Запустить с ограничениями и поработать**, а не только прогнать тесты. 5. **Побочные изменения** объяснить. 6. **Пропущенный пункт назвать пропущенным.** --- ## Ограничения - Не ломать работу агентов ради строгости: они должны продолжать работать в своих каталогах. - Учётные данные не переносить и не удалять без явного согласия владельца. - Зона: новый слой безопасности, `web/server.py`, журнал, тесты. Веб-клиент — A34, определение роли — A35, конвейер — A36. - Правило честности без исключений. - Тег `v0.1.1` не создавать. ## Критерии приёмки 1. Ветка в `origin` от `review/a28-a31-fixes`, `git status` чист. 2. Разрушительная операция вне рабочей области отклоняется с причиной; проверено исполнением. 3. Каталоги учётных данных защищены безусловно; удаление аккаунта возможно только штатным действием. 4. Есть сухой прогон, показывающий последствия до выполнения. 5. Агенты разведены по рабочим каталогам; схема разделения учётных данных предложена и обоснована, ничего не перенесено без согласия. 6. Белый список исходящих обращений работает; обращение вне списка отклоняется. 7. CORS закрыт по умолчанию, есть тест на возврат `allow_origins=["*"]`. 8. Риск HTTP по сети назван в интерфейсе. 9. В журнале видно, кто выполнил действие; секретов в нём нет. 10. Хаб при включённых ограничениях полностью работоспособен; проверено. 11. `ruff check .` чисто; релизный гейт не ухудшен. 12. Отчёт: `START_HEAD`, `FINAL_HEAD`, `origin/main`, `git status`, `X passed / Y skipped / Z failed`. На ветке ревьюера сейчас **475 passed, 2 skipped**. ## Главное У владельца на трёх машинах лежат ключи от 24 аккаунтов, а работают там автономные агенты без изоляции друг от друга. Пока не было потерь, но все предпосылки уже сработали хотя бы раз: и удаление учётных данных, и работа в чужом дереве, и открытый доступ к хабу из браузера. ## Порядок сдачи Передать точный `FINAL_COMMIT_SHA`.