agentkeeper
v2.1.2
Published
Confine AI coding agents to what they need: OS-level sandboxing plus a thin detection layer. No Docker, no daemon in the cloud, no telemetry.
Maintainers
Readme
agentkeeper
English · Русский
Ограничивает ИИ-агента тем, что ему действительно нужно, используя песочницу,
которая уже встроена в вашу операционную систему. Без Docker, без контейнеров,
без облачного сервиса, без телеметрии — и без изменений в привычной работе:
вы по-прежнему печатаете claude, gemini, codex.
Защита работает в обе стороны: от того, что скомпрометированный или
проинъецированный агент может сделать (прочитать ~/.ssh, собрать .env
файлы, вынести данные), и от того, с кем агент разговаривает — включая
«бесплатных прокси-провайдеров», чья бизнес-модель — раздавать доступ к новым
моделям, пока ваши разговоры, ваш ключ и секреты рабочей директории текут
через их сервер.
npm i -g agentkeeper
agentkeeper activateОдин раз. Дальше продолжаете печатать claude, codex, gemini, opencode
как обычно.
Идея
Правила должны угадывать, что плохо. Песочница просто не предоставляет такой возможности.
Промпт-инъекция управляет намерением агента, но не тем, до чего агент может
физически дотянуться. Отравленный CLAUDE.md может приказать агенту прочитать
~/.ssh/id_rsa — агент попробует, а файла в его мире не существует.
Атака сработала, ничего не произошло, никого не отвлекли.
Поэтому здесь изоляция на первом месте, а детекция — подстраховка для мест, куда изоляция не достаёт.
agentkeeper ограничивает то, что агент может сделать, и записывает то, что он сделал. Это слабее полной контейнерной изоляции и сильнее любого набора правил.
Что он на самом деле делает
Два слоя, которые не дублируют друг друга.
Слой 1 — песочница. Агент запускается внутри профиля изоляции ОС:
sandbox-exec (Seatbelt) на macOS, bubblewrap на Linux. Для Windows бэкенд
не поставляется: его AppContainer-канарейка ни разу не прошла, поэтому платформа
честно сообщает UNPROTECTED, а не делает вид (см. docs/platform-support.md).
Это решает, что вообще существует. Исходящий трафик идёт через
брокер назначений, который разрешает перечисленные хосты и отказывает всем
остальным.
Слой 2 — правила. Хук PreToolUse, глобальный git-хук и небольшой
резидентный наблюдатель следят за тем, что происходит внутри этой границы —
за вещами, которые изоляция не видит, потому что они касаются ресурсов,
к которым агенту доступ разрешён.
Покрытие угроз
Приведено без смягчений. «Нет» значит нет.
| Вектор | Слой | Предотвращено | Обнаружено | |---|---|---|---| | V1–V3 (артефакты автозапуска в репозитории) | 2 | Да | Да | | V4 (конфигурация окружения, CVE-2026-21852) | 1 + 2 | Да | Да | | V5 (эксфильтрация по стороннему каналу) | 1 | Частично (allowlist назначений) | Нет | | V6 (недоверенный ввод в CI) | 2 | Да | Да | | V7 (rug-pull) | 2 | Да | Да | | V8 (подмена MCP-сервера) | 1 + 2 | Да | Да | | V9 (закрепление вне репозитория) | 1 | Да | Да | | V10 (сбор учётных данных) | 1 | Да | Частично | | V11 (необратимые действия) | 1 + 2 | Да | Да | | V12 (уход из-под наблюдения) | 1 | Да | Нет |
Жирные ячейки стали возможны только со слоем 1. Инструмент «только правила» отвечает «нет» в каждой из них, потому что субпроцесс одобренной команды, MCP-сервер или второй агент вообще не проходят через хук вызова инструментов.
Про эксфильтрацию — прямо. Обещать обнаружение утечки в исходящем трафике этот проект честно не может. CVE-2026-54316 утёк API-ключ по одному символу через счётчик загрузок Hugging Face; в трафике не было ничего узнаваемого. Позиция здесь такая: ограничивать, какие назначения существуют, а не обещать детектировать то, что уходит через разрешённое.
Два уровня доступа
Именно эта часть решает, имеет ли модель разрешений смысл.
Если разрешение можно запросить в момент атаки — всё рушится: инъекция ждёт отвлечённого момента и спрашивает. Поэтому ресурсы разделены, и две половины выдаются принципиально разными способами.
Уровень 1 — повседневный. Спрашивается, пока агент работает.
Соседняя директория проекта, ~/.gitconfig (чтение), кэши пакетных менеджеров,
конфигурация редактора. Ошибочное «да» стоит немного.
Уровень 2 — опасный. Не спрашивается никогда. Вообще.
~/.ssh, ~/.aws, ~/.config/gcloud, ~/.kube, ~/.docker, ~/.netrc,
~/.npmrc, ~/.config/gh, связка ключей macOS, профили браузеров, история
шелла, все поверхности закрепления и все файлы настроек агентов.
У них нет кнопки «разрешить» нигде в интерфейсе. Не «вы уверены», не «требует подтверждения» — её нет. Отказ молчалив, событие пишется в журнал.
Единственный способ выдать доступ уровня 2 — самому открыть
~/.agentkeeper/allowlist.json в текстовом редакторе:
{
"version": 1,
"grants": [
{
"resource": "file:~/.ssh/deploy_key",
"access": "read",
"scope": "global",
"reason": "сборка подписывает артефакты этим ключом"
}
]
}Инъекция может сформулировать любую просьбу. Она не может открыть ваш редактор
в другой момент времени. Этот зазор между спросить и получить — то, что
заставляет модель держаться, и он обеспечен структурно, а не доверием: песочница
никогда не делает ~/.agentkeeper записываемым, а правило AG-B005 отказывает
вызовам инструментов, которые туда тянутся.
Честные ограничения
| Ограничение | Что из этого следует |
|---|---|
| Профиль фиксируется при старте процесса | Новый грант применяется к следующему запуску. Инструмент говорит об этом, а не делает вид, что иначе. |
| Ничего внутри разрешённого назначения не инспектируется | Как только api.anthropic.com:443 разрешён, содержимое этой TLS-сессии не проверяется. Альтернатива — терминировать TLS агента, а это хуже. |
| macOS сообщает DEGRADED, а не PROTECTED | Профиль Seatbelt запрещает все пути к учётным данным, закреплению и истории, но всё ещё разрешает широкое чтение вне домашней директории. Перечисляемый allowlist чтения роняет рантайм на текущей macOS, поэтому разрыв сообщается при каждом запуске, а не прячется. |
| Windows сообщает UNPROTECTED | AppContainer-бэкенд не поставляется: его deny-канарейка ни разу не завершилась на реальном железе, а недоказанная граница не поставляется. Слой детекции там по-прежнему работает. |
| sandbox-exec объявлен Apple устаревшим | Всё ещё встроенный механизм, всё ещё работает в текущей macOS. Риск указан здесь, а не скрыт. |
| bwrap установлен не везде | Без него слой 1 недоступен, и run отказывается стартовать, а не запускается без защиты. |
| Слабее контейнера | Общее ядро. |
| Агент всё ещё может разнести рабочую директорию | Изоляция защищает систему, а не ваше рабочее дерево — для этого есть git. |
| Обёртка стоит больше проектных 100 мс | Измерено: процесс Node плюс sandbox-exec — это ~95 мс нижней границы до того, как agentkeeper вообще что-то сделает. Наша собственная доля ~60 мс; итого, что чувствует пользователь, — ~155 мс на загруженной машине, меньше на свободной. Цифра в проектном брифе была написана без измерения этой границы. |
Когда механизм недоступен, agentkeeper run отказывается запускать
команду. Тихий незащищённый запуск хуже, чем отсутствие инструмента, потому
что вы бы верили, что защищены.
Чем это не является
| Не является | Почему |
|---|---|
| Универсальным антивирусом | Нет сигнатур, нет интереса к процессам вне агента |
| Линтером GitHub Actions | zizmor и actionlint делают это лучше |
| Сканером секретов | gitleaks и trufflehog существуют |
| Статическим анализатором MCP | Другой класс продукта |
| Облачным сервисом | Нет бэкенда, нет телеметрии, нет аккаунтов |
| Заменой контейнеров | Слабее гарантии, лучше эргономика — см. выше |
agentkeeper и zizmor
| | zizmor | agentkeeper |
|---|---|---|
| Структура workflow, разрешения, места инъекций | Глубоко, структурно | Не пытается |
| Агент-специфичная дельта для CI (уязвимые версии CLI, --yolo, два прохода по одному чекауту, недоверенный триггер + секреты) | — | Да |
| Всё вне CI | — | Собственно продукт |
Запускайте оба. CI-правила здесь — небольшая дельта, и они читают workflow как текст, а не парсят YAML — намеренно, чтобы не переизобретать работу, которую кто-то уже делает правильно.
Использование
agentkeeper activate # настройка один раз, с diff каждого изменения
agentkeeper doctor # проверить реальную песочницу; объяснить каждый разрыв
agentkeeper status # тот же ответ, кратко
agentkeeper policy # действующая граница здесь, и её хэш
agentkeeper integrations # какие агенты реально запускаются через защиту
agentkeeper allow <path> --read # открыть один путь уровня 1; --workspace для ограничения
agentkeeper revoke <id> # закрыть его обратно
agentkeeper scan [path] # проверить репозиторий (также работает как git-хук)
agentkeeper log --since 7d # что было записано
agentkeeper pause 1h # заглушить уведомления (изоляция остаётся)
agentkeeper repair # восстановить управляемые файлы после дрейфа
agentkeeper run -- <cmd> # продвинутое: запустить что угодно внутри профиля
agentkeeper deactivate # убрать всё, восстановить оригиналыПосле activate вы продолжаете печатать claude — shim в PATH направляет
его через границу.
Не существует переменной окружения, которая отключает shim. Перехват,
который может отключить любая переменная, — не перехват, а отравленный контекст
может породить переменную. Если нужно запустить агента без ограничений — сделайте
это осознанно: вызовите настоящий бинарник по его собственному пути или выполните
agentkeeper deactivate. Сессия, прошедшая вне границы, отчитывается как
BYPASSED, никогда как защита.
Документация
Архитектура · Модель угроз · Граница безопасности · Сетевая модель · Поддержка платформ · Совместимость с агентами · Политика · Правила · Покрытие угроз · Устранение неполадок
Стартовые профили
activate предлагает предзаполненный allowlist, чтобы первая неделя была
горсткой вопросов, а не сотней: web, python, infra, minimal. Это файлы
данных в profiles/ — pull request'ы приветствуются.
Установка agentkeeper не запускает никакого кода
В этом пакете нет postinstall-скрипта. Зависимость, которая прописывает
себя в вашу конфигурацию во время установки, неотличима от вредоноса, и это
в точности сигнатура ChainDrop, за которой следит этот инструмент. Ничего не
происходит, пока вы не выполните agentkeeper activate, и он покажет diff
каждого файла, которого хочет коснуться.
То же относится к интеграции с шеллом. Запись в ~/.zshrc и есть вектор V9.
Поэтому: явное согласие, точный текст показан заранее, одна строка, указывающая
на отдельный файл, и uninstall, проверенный тестом на восстановление оригинала
байт в байт.
Другие свойства, каждое проверяется за минуту:
- Нет сети в рантайме. Ничто в этом пакете не делает исходящих запросов.
- Нет телеметрии. Данным некуда отправляться.
- Ноль runtime-зависимостей. Только стандартная библиотека Node.
- Журнал аудита хранит пути, хэши и идентификаторы правил — никогда содержимое файлов. Журнал о том, что было защищено, не должен становиться единственным местом, где всё это записано открытым текстом.
Правила
Идентификаторы стабильны. Полный каталог в docs/rules.md.
| Семейство | Покрывает | По умолчанию |
|---|---|---|
| H | Артефакты автозапуска: хуки, задачи folderOpen, жизненный цикл devcontainer, git-хуки | вкл |
| E | Окружение и MCP: .gemini/.env, MCP-серверы, подмена endpoint'ов, авто-одобрение | вкл |
| I | Файлы инструкций: императивы «скачай-и-выполни», невидимый текст, дрейф | вкл |
| B | Отказы внутри песочницы: защищённые пути, конфиг агента, core.hooksPath, самозащита | вкл, не отключается |
| P | Закрепление вне песочницы, сравнение с доверенной базовой линией | вкл |
| C | CI-дельта для агентных workflow | вкл |
| A | Необратимые действия: force push, публикация, rm -rf, инфраструктура, исходящий MCP | выкл |
Семейство A выключено по умолчанию, и это настоящий компромисс, а не увёртка. Включение для того, кто активно пишет код, даёт несколько вопросов в день, что прямо противоречит проектному бюджету «меньше одного вопроса в неделю». Включайте, если запускаете агента без присмотра:
// ~/.agentkeeper/config.json
{ "version": 1, "rules": { "categoryA": { "enabled": true } } }Конфигурация не может выдать доступ уровня 2 и не может отключить блокирующее правило. Это обеспечено в коде, а не договорённостью.
Разработка
npm install
npm test # unit + integration
npm run bench # бюджеты производительности, измеренные в чистом процессе
npm run test:sandbox # реальная изоляция, реальные отказы — тесты, которые решают
npm run test:e2e # установка → использование → удаление на собранном пакете
npm run verify # всё, плюс границы архитектуры и покрытиеСтрогий TDD: каждое правило детекции начинается с фикстуры и падающего теста.
Враждебные фикстуры собираются скриптом во временную директорию и никогда не
коммитятся — репозиторий с живой задачей folderOpen выполнил бы её на каждом,
кто открыл проект.
Архитектура слоистая (domain → application → infrastructure /
presentation), зависимости направлены только внутрь, проверяется
dependency-cruiser в CI. domain не выполняет I/O, поэтому каждое правило
тестируется строкой на входе и массивом на выходе.
Пороги покрытия: domain 100%, application 95%, в целом 85%. Корпус
ложноположительных имеет золотое число находок; любое увеличение — красная
сборка.
Производительность измеряется, а не утверждается. npm run bench сообщает
каждую цифру относительно её бюджета, в чистом процессе — измерение внутри
тест-раннера раздувало всё примерно вчетверо, а p95(A) - p95(B) по разным
прогонам оказался шумом, а не разницей.
Лицензия
MIT. Политика раскрытия уязвимостей — в SECURITY.md.
