# Дайте ИИ-агенту файловую систему, не отдавая ему всю свою

> Руководство по песочнице файлового доступа ИИ-агента через файловый MCP-сервер с точки зрения безопасности. Как Octofs ограничивает рабочий корень, учитывает gitignore, обеспечивает неинтерактивную оболочку и устраняет целые категории фут-ганов, которые сырой bash и rm передают агенту — плюс защиты, которых у него нет, и как настроить систему вокруг них.

Каталог исчез раньше, чем кто-либо прочитал строку лога, которая объясняла почему.

Агент, которого мы запускали на scratch-репозитории, получил команду «почистить артефакты сборки». Он рассуждением дошёл до `rm -rf` с путём, собранным из переменной, которая двумя ходами раньше тихо разрешилась в пустую строку. `rm -rf /` — это учебниковая версия этой шутки. Настоящая тише: `rm -rf "$BUILD_DIR/"`, где `$BUILD_DIR` — это `""`, и теперь вы удаляете от рабочего каталога вверх. Нам повезло — это был одноразовый клон, и худшей потерей стал послеобеденный час. Но я просмотрел достаточно транскриптов агентов, чтобы знать разницу между «нам это не навредило» и «это не может нам навредить», и модель с сырой оболочкой уверенно относится к первой категории.

Тот инцидент — единственная причина, по которой мне важно, как агент касается диска. Урок не «модели опасны». Модели не злонамеренны; они уверенно ошибаются, и уверенно ошибаться, имея в руках `rm -rf`, — это проблема безопасности, намеревался кто-то навредить или нет. Решение то же, что мы применяли к ненадёжному коду пятьдесят лет: **не отдавайте ему всю машину.** Дайте узкий, типизированный, наблюдаемый интерфейс, ограничьте то, куда этот интерфейс может дотянуться, и сделайте опасные операции либо невозможными, либо шумными.

Этот интерфейс, для файлов, — файловый MCP-сервер. Этот пост — про [Octofs](/blog/introducing-octofs), тот, что мы строим и запускаем, — с точки зрения безопасности: что он реально ограничивает, что он сознательно не ограничивает, и как его настроить так, чтобы следующему `rm -rf` просто нечего было схватить.

---

## Модель угроз: что на самом деле даёт сырая оболочка

Начните с честности относительно того, что вы отдаёте, когда даёте агенту `bash`.

Оболочка — это не «файловый инструмент». Это лаунчер произвольных программ с полной окружающей авторитетностью процесса, который её породил: ваша учётная запись, ваши переменные среды, ваш SSH-агент, ваши облачные учётные данные, лежащие в `~/.aws`, ваш kubeconfig, каждый примонтированный том, всё дерево от `/` вниз. Когда модель выдаёт `curl ... | sh`, оболочка это выполняет. Когда она выдаёт `rm -rf` на пути, который она галлюцинировала, оболочка удаляет. Когда она запускает `git push` на неправильный remote, оболочка пушит. Работа оболочки — делать ровно то, что ей сказано, мгновенно, без понятия «вы это имели в виду?».

Положите поверх этой авторитетности режимы отказа модели — и вы получите конкретный, повторяющийся набор инцидентов:

- **Дрейф пути.** Агент строит путь из переменной, предыдущего результата инструмента или своей памяти о структуре репозитория. Переменная пуста, результат был обрезан, память устарела — и путь теперь указывает куда-то не туда. Оболочке всё равно.
- **Деструктивные глаголы на плохих путях.** `rm -rf`, `mv` поверх существующего файла, усечение через `> file`, `git checkout -- .`, стирающее незакоммиченную работу. Каждый — в одном токене от безобидной версии.
- **Чтение того, чего не следует.** Дамп `cat ~/.ssh/id_ed25519` или `env`, который тащит секреты в контекстное окно, где они логируются, кэшируются и, возможно, отправляются в API модели. Для эксфильтрации не нужна злонамеренность; нужен агент, который «любезно» включает не тот файл.
- **Интерактивные зависания.** Команда, которая запрашивает пароль или открывает пейджер, без TTY, чтобы ответить, и сессия заклинивает.
- **Сиротские процессы.** Фоновый сервер, который агент запустил и забыл, всё ещё держащий порт после окончания сессии.

Ни одно из этого не требует джейлбрейка. Это _ожидаемое_ поведение вероятностной системы, подключённой напрямую к императивной. Митигация архитектурная: замените открытый лаунчер закрытым набором именованных операций и ограничьте поверхность, которой эти операции могут достигать.

---

## Почему ограниченный файловый инструмент лучше сырого `bash`

Аргумент в пользу выделенного файлового MCP-сервера вместо доступа к сырой оболочке — тот же, что мы приводили для [хранения кастомных инструментов в вашем репозитории](/blog/custom-mcps-belong-in-your-repo): узкий, именованный инструмент даёт модели меньше способов ошибиться и даёт _вам_ место для применения правил.

Конкретно, замена `bash` файловым инструментом меняет четыре вещи одновременно:

1. **Глаголы конечны и именованы.** Вместо «любой программы в PATH» модель видит `view`, `text_editor`, `batch_edit`, `extract_lines`, `shell`, `workdir`. У каждого есть схема. Нет такой операции `text_editor`, которая означает «рекурсивно удалить дерево», потому что такая операция никогда не была определена. Нельзя выстрелить себе в ногу из пистолета, которого нет в ящике.
2. **Поверхность по умолчанию — ваш проект, а не домашний каталог.** Octofs берёт рабочий корень и разрешает относительные пути относительно него. Естественный режим работы агента — «отредактируй `src/main.rs`» — никогда не выходит за этот корень.
3. **Чтения фильтруются.** Обход каталога учитывает `.gitignore` и по умолчанию пропускает dot-файлы, так что `.env`, `.ssh`, `node_modules` и всё, что вы уже сказали git игнорировать, не появляется в листингах или поиске по содержимому. Агент не читает то, чего не видит.
4. **Каждая операция наблюдаема и обратима.** Редактирования проходят через атомарные записи с до десятью уровнями отмены на файл. Нет состояния «файл записан наполовину, и процесс умер», и есть `undo_edit` для «это редактирование было неправильным».

Оболочка не исчезает — агентам действительно нужно запускать сборки и тесты — но она перестаёт быть _основным_ файловым интерфейсом, и это большая часть победы. Чем меньше файловых операций идёт через сырую оболочку, тем меньше у модели шансов сместить путь во что-то деструктивное.

---

## Что Octofs реально предоставляет

Шесть MCP-инструментов, и это вся поверхность. Знать точный список важно, потому что аргумент безопасности — «эти и ничего больше».

| Инструмент      | Что делает                                                                                             | Почему безопаснее эквивалента в оболочке                                                                      |
| --------------- | ------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------- |
| `view`          | Читает файлы (один, с диапазонами строк), листит каталоги по glob-шаблонам, ищет по содержимому файлов | Учитывает gitignore и фильтрует dot-файлы — заменяет `cat`/`grep`/`find`/`ls`, не раскрывая игнорируемые пути |
| `text_editor`   | `create`, `str_replace`, `delete`, `undo_edit` на файле                                                | Атомарные записи, история отмен, нечёткое совпадение вместо тихого повреждения через `sed`                    |
| `batch_edit`    | Несколько операций вставки/замены в одном файле, применяемых атомарно                                  | Обнаружение конфликтов отклоняет перекрывающиеся или неоднозначные правки до касания диска                    |
| `extract_lines` | Копирует диапазон строк из одного файла и добавляет в другой                                           | Ограниченное, типизированное копирование вместо самодельного пайпа `head`/`tail`                              |
| `shell`         | Запускает команду, в foreground или background                                                         | Принуждение к неинтерактивности, очистка группы процессов, подсказки при неправильном использовании           |
| `workdir`       | Получает, устанавливает или сбрасывает рабочий каталог сессии                                          | Делает область видимости явной и проверяемой, а не подразумеваемой в `cd`                                     |

Два флага конфигурации формируют всё это, устанавливаемые при запуске сервера:

```bash
# ограничить рабочий корень и использовать стабильные хеш-идентификаторы строк
octofs mcp --path /path/to/your/project --line-mode hash
```

`--path` устанавливает рабочий корень. `--line-mode hash` переключает идентификаторы строк с последовательных номеров на 4-символьные хеши, выведенные из содержимого, которые остаются стабильными между правками — функция надёжности, но и смежная с безопасностью, потому что стабильные ссылки означают меньше ошибок вида «отредактировал не ту строку, потому что номера сдвинулись». Есть также `--bind host:port` для запуска через Streamable HTTP вместо stdio, когда нужна удалённая или мультиклиентская конфигурация.

В конфиге Claude Desktop, Cursor или Windsurf это выглядит так:

```json
{
	"mcpServers": {
		"octofs": {
			"command": "/path/to/octofs",
			"args": ["mcp", "--path", "/path/to/your/project", "--line-mode", "hash"]
		}
	}
}
```

Этот `--path` — единственное важнейшее решение по безопасности, которое вы принимаете с этим инструментом. Это разница между «агент работает с этим проектом» и «агент работает с моим ноутбуком». Установите его на самый узкий каталог, который нужен задаче.

---

## Защиты, в порядке того, как часто они вас спасают

Вот что Octofs реально обеспечивает, основанное на коде, а не на брошюре.

### Ограниченный рабочий корень

Относительные пути разрешаются относительно рабочего корня, который вы передаёте в `--path` (или текущего каталога, если не передаёте). Когда агент говорит `view src/main.rs`, это разрешается в `<root>/src/main.rs`. Вся ментальная модель «проекта» у модели привязана к этому корню, и пока она работает с относительными путями — что является её режимом по умолчанию — она никогда не выходит наружу.

### Обход с учётом gitignore и фильтрацией dot-файлов

Листинг каталога и поиск по содержимому используют walker из крейта `ignore` с `git_ignore(true)` и скрытыми файлами, исключёнными по умолчанию. Практический эффект: когда агент листит каталог или ищет строку, он не видит `.git/`, `.env`, `.ssh/`, вывод сборки, `node_modules` или что-либо в вашем `.gitignore`. Секреты, которые вы уже исключили из системы контроля версий, также исключены из поля зрения агента. Это самая дешёвая и самая недооценённая защита — агент не может слить файл, о существовании которого он никогда не узнал.

### Атомарные записи с историей отмен

Каждое редактирование пишет во временный файл в том же каталоге, затем переименовывает поверх целевого — атомарная операция на уровне файловой системы. Файл никогда не наблюдается записанным наполовину; если процесс умирает в середине правки, оригинал остаётся нетронутым. Оригинальные права доступа к файлу сохраняются при записи (исправление, вошедшее в 0.4.2, специально для того, чтобы запись не расширяла тихо режим файла). Перед каждой записью предыдущее содержимое снапшотится, до десяти уровней глубиной на файл, доступных через `undo_edit` `text_editor`. «Агент сломал файл» перестаёт быть операцией восстановления и становится одним вызовом инструмента.

### Обнаружение конфликтов и дубликатов в пакете

`batch_edit` валидирует каждую операцию против всех остальных _до_ записи. Перекрывающиеся диапазоны замен отклоняются. Две вставки на одном якоре отклоняются как неоднозначные. Весь пакет ограничен 50 операциями. И каждая замена проверяется на дублирование границ — классический сбой, когда модель включает одну лишнюю строку «контекста» и тихо дублирует её в файл. Ничто из этого не касается диска, пока план не станет внутренне согласованным.

### Принуждение к неинтерактивной оболочке

Когда агент использует `shell`, команды запускаются с `stdin`, установленным в null, в своей собственной группе процессов, с `GIT_TERMINAL_PROMPT=0`, `DEBIAN_FRONTEND=noninteractive`, `PAGER=cat` и `GIT_PAGER=cat`. Команда, которая запросила бы учётные данные, падает быстро вместо зависания сессии. Команда, которая пейджила бы вывод, выплёвывает его как есть вместо вызова `less` против несуществующего TTY. Сессия не может заклинить на интерактивном приглашении, на которое модель не может ответить.

### Очистка группы процессов при завершении

Команды в foreground порождаются в своей собственной группе процессов и отслеживаются по PID. На `SIGTERM` или EOF Octofs отправляет `SIGKILL` каждой отслеживаемой группе — включая внучатые процессы — так что ничего, что команда оставила полузапущенным, не переживает сессию. Команды в background (`background: true`) — сознательное исключение: они отсоединены именно для того, чтобы пережить вызов, Octofs передаёт агенту их PID, и убить их, когда работа сделана, — работа агента — `kill <pid>`.

### Подсказки при неправильном использовании оболочки

Это намёк, не стена, но он формирует поведение в течение сессии. Когда агент запускает `cat`, `grep`, `find`, `ls`, `sed` или `awk` через `shell`, Octofs добавляет подсказку к ответу, направляющую его к выделенному инструменту: используйте `view` для чтения или поиска, используйте `text_editor` для редактирования. В течение сессии агент учится маршрутизировать файловые операции через инструменты, у которых есть страховка, а не через оболочку, у которой её нет. Меньше деструктивных глаголов, текущих через открытый интерфейс, — именно это и есть цель.

---

## Чего Octofs не делает — и почему я вам об этом рассказываю

Пост о безопасности, который перечисляет только защиты, — это маркетинг. Вот граница, сказанная прямо, потому что правильная настройка вокруг неё зависит от знания того, где она находится.

**Octofs — это не chroot-клетка.** Рабочий корень ограничивает _относительные_ пути. Он не ограничивает _абсолютные_ пути. `resolve_path` — это две ветки: относительный путь присоединяется к рабочему корню; абсолютный путь используется как есть. Нет отклонения обхода `..` и нет проверки, что разрешённый путь остаётся под корнем. Если модель выдаёт `view /etc/passwd` или `text_editor` против `/Users/you/.ssh/config`, Octofs разрешит этот абсолютный путь и будет работать с ним. Ограничение — это сильное _умолчание_ и сильная _конвенция_ — агент, работающий с относительными путями, никогда не покидает проект — но это не усиленная граница против пути, который намеренно или случайно становится абсолютным.

Это сознательная точка дизайна, а не упущение: Octofs — это инструмент продуктивности для кооперативного агента, а не слой сдерживания для враждебного. Но это меняет то, как вам следует его развёртывать, когда радиус поражения имеет значение:

- **Запускайте его против выделенного каталога, а не вашего дома.** Самый узкий `--path`, который вы можете себе позволить. Если задаче нужен только один репозиторий, укажите на этот репозиторий.
- **Для жёсткой изоляции используйте ОС.** Файловый инструмент не может переиграть ядро на его собственном поле песочницы. Если вы действительно не доверяете тому, что агент может сделать — ненадёжный код, автономный цикл, раннер CI — запустите Octofs и агента внутри контейнера, VM или учётной записи пользователя, которая _физически не может_ видеть ваши секреты, с примонтированным только каталогом проекта. Тогда разрыв абсолютных путей нерелевантен, потому что за пределами монтирования нечего достигать. Эшелонированная защита: ограниченный инструмент сужает обычный случай; песочница ОС ловит путь, который становится абсолютным.
- **Держите секреты вне рабочего корня и вне git.** Поскольку обход учитывает gitignore, секреты, которые вы уже gitignore-нули, остаются невидимыми для листингов и поиска. Это реальное сокращение случайного раскрытия — опирайтесь на это. Учётные данные, которые агент не может перечислить, — это учётные данные, которые он не может вставить в контекстное окно.

Честное резюме в одну строку: Octofs устраняет _случайные_ фут-ганы — `rm -rf` с пустой переменной, тихо повреждённый `sed`, полузаписанный файл, зависший пейджер — и сужает поверхность по умолчанию. Он сам по себе не сдерживает решительного или совершенно сбитого с толку агента, который конструирует абсолютный путь. Соедините его с песочницей ОС, когда вам нужна эта гарантия.

---

## Конфигурация плотно: чек-лист

Если вы хотите максимальную безопасность, которую этот инструмент может дать, настройте его так:

1. **Установите `--path` на самый узкий каталог проекта, который нужен задаче.** Не ваш дом, не `/`, не родитель, содержащий ещё пять репозиториев. Один проект, один корень.
2. **Gitignore-ните свои секреты до того, как указывать агента на репозиторий.** `.env`, `*.pem`, файлы учётных данных, локальная конфигурация. Они становятся невидимыми для листингов и поиска `view`. Это бесплатно и работает.
3. **Используйте `--line-mode hash` для любого многошагового редактирующего агента.** Стабильные ссылки на строки означают меньше ошибок «отредактировал не ту строку», что одновременно надёжность и безопасность.
4. **Предпочитайте типизированные инструменты `shell` в инструкциях вашего агента.** Подсказки при неправильном использовании толкают в эту сторону; усильте это. Каждый файл, прочитанный через `view` вместо `cat`, — тот, который соблюдает правила ignore.
5. **Гейтите деструктивные операции в слое разрешений рантайма вашего агента.** Octofs предоставляет инструменты; ваш рантайм решает, какие требуют одобрения. Автоодобряйте `view`. Всегда спрашивайте для `shell` и `delete` из `text_editor`. Здесь человек остаётся в контуре для глаголов, которые нельзя отменить через `undo_edit`.
6. **Когда радиус поражения реален, поместите всё это в контейнер.** Примонтируйте только каталог проекта. Теперь разрыв абсолютных путей закрыт ядром, а не надеждой.

Сдвиг мышления — это суть. Вы не доверяете модели быть осторожной. Вы устраиваете мир так, чтобы неосторожность имела малый радиус поражения — ограниченный корень, фильтрованные чтения, обратимые записи, неинтерактивная оболочка и явный рабочий каталог.

---

## FAQ

**Запрещает ли Octofs агенту запускать `rm -rf`?**

Не напрямую — `shell` всё ещё запускает произвольные команды, включая `rm`. Что меняет Octofs, так это то, что _редактирование_ и _чтение_ файлов больше не нуждаются в оболочке, поэтому гораздо меньше деструктивных команд оболочки генерируется в первую очередь; и рабочий корень плюс обход с учётом gitignore означают, что естественные операции агента остаются внутри проекта. Для жёсткой гарантии, что `rm -rf` не может достичь ничего важного, запустите Octofs внутри контейнера с примонтированным только проектом.

**Является ли рабочий корень песочницей?**

Это ограниченное умолчание, не клетка. Относительные пути разрешаются относительно корня; абсолютные пути используются как есть, без отклонения `..`. Относитесь к `--path` как к «где агент работает», а не «в чём агент ограничен». Для ограничения используйте песочницу уровня ОС.

**Как он предотвращает утечку секретов в контекст?**

Листинг каталога и поиск по содержимому учитывают `.gitignore` и по умолчанию пропускают dot-файлы. Файлы, которые вы уже исключили из git — `.env`, файлы ключей, что-либо в `.gitignore` — не появляются в листингах или поиске, так что агент не может прочитать их в контекст. Защита хороша лишь настолько, насколько хорош ваш `.gitignore`, так что курируйте его до указания агента на репозиторий.

**Что насчёт полузаписанного файла, если процесс упадёт в середине правки?**

Не может случиться. Записи идут во временный файл в том же каталоге и атомарно переименовываются поверх целевого. Если процесс умирает, оригинал нетронут. Права доступа сохраняются при записи.

**Почему шесть инструментов вместо простого предоставления файловой системы?**

Конечный, именованный набор операций — это функция безопасности. Модель не может потянуться за глаголом «удалить дерево», который никогда не был определён, и вы получаете одно место — границу инструмента — для обеспечения атомарных записей, отмены, обнаружения конфликтов и правил ignore. Меньше, более узкие инструменты также легче для модели выбирать правильно.

**Работает ли это вне Octomind?**

Да. Octofs говорит на MCP через stdio или Streamable HTTP и работает с любым MCP-клиентом — Claude Desktop, Cursor, Windsurf, Zed — плюс рантайм агента Octomind.

---

Агент, который чуть не удалил тот scratch-каталог, всё ещё запускается в нашей конфигурации каждый день. Модель не стала осторожнее, чем была. У неё просто стало гораздо меньше того, что можно сломать.

Изменился мир вокруг неё: она читает через `view`, редактирует через `text_editor`, работает внутри `--path`, ограниченного одним репозиторием, и когда ставки реальны, всё это сидит внутри контейнера, который не может видеть ничего другого.

Дайте агенту файловую систему. Только не свою.

— Don

---

_[Octofs](https://github.com/muvon/octofs) — open source под Apache-2.0 — единый бинарник на Rust, текущая версия 0.5.0. Ограничение, обход с учётом gitignore, атомарные записи и неинтерактивная оболочка, описанные здесь, всё в исходниках; оговорка про абсолютные пути тоже. Если нужной вам защиты там нет, [откройте issue](https://github.com/muvon/octofs/issues)._
