Каталог исчез раньше, чем кто-либо прочитал строку лога, которая объясняла почему.
Агент, которого мы запускали на scratch-репозитории, получил команду «почистить артефакты сборки». Он рассуждением дошёл до rm -rf с путём, собранным из переменной, которая двумя ходами раньше тихо разрешилась в пустую строку. rm -rf / — это учебниковая версия этой шутки. Настоящая тише: rm -rf "$BUILD_DIR/", где $BUILD_DIR — это "", и теперь вы удаляете от рабочего каталога вверх. Нам повезло — это был одноразовый клон, и худшей потерей стал послеобеденный час. Но я просмотрел достаточно транскриптов агентов, чтобы знать разницу между «нам это не навредило» и «это не может нам навредить», и модель с сырой оболочкой уверенно относится к первой категории.
Тот инцидент — единственная причина, по которой мне важно, как агент касается диска. Урок не «модели опасны». Модели не злонамеренны; они уверенно ошибаются, и уверенно ошибаться, имея в руках rm -rf, — это проблема безопасности, намеревался кто-то навредить или нет. Решение то же, что мы применяли к ненадёжному коду пятьдесят лет: не отдавайте ему всю машину. Дайте узкий, типизированный, наблюдаемый интерфейс, ограничьте то, куда этот интерфейс может дотянуться, и сделайте опасные операции либо невозможными, либо шумными.
Этот интерфейс, для файлов, — файловый MCP-сервер. Этот пост — про 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-сервера вместо доступа к сырой оболочке — тот же, что мы приводили для хранения кастомных инструментов в вашем репозитории: узкий, именованный инструмент даёт модели меньше способов ошибиться и даёт вам место для применения правил.
Конкретно, замена bash файловым инструментом меняет четыре вещи одновременно:
- Глаголы конечны и именованы. Вместо «любой программы в PATH» модель видит
view,text_editor,batch_edit,extract_lines,shell,workdir. У каждого есть схема. Нет такой операцииtext_editor, которая означает «рекурсивно удалить дерево», потому что такая операция никогда не была определена. Нельзя выстрелить себе в ногу из пистолета, которого нет в ящике. - Поверхность по умолчанию — ваш проект, а не домашний каталог. Octofs берёт рабочий корень и разрешает относительные пути относительно него. Естественный режим работы агента — «отредактируй
src/main.rs» — никогда не выходит за этот корень. - Чтения фильтруются. Обход каталога учитывает
.gitignoreи по умолчанию пропускает dot-файлы, так что.env,.ssh,node_modulesи всё, что вы уже сказали git игнорировать, не появляется в листингах или поиске по содержимому. Агент не читает то, чего не видит. - Каждая операция наблюдаема и обратима. Редактирования проходят через атомарные записи с до десятью уровнями отмены на файл. Нет состояния «файл записан наполовину, и процесс умер», и есть
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 |
Два флага конфигурации формируют всё это, устанавливаемые при запуске сервера:
# ограничить рабочий корень и использовать стабильные хеш-идентификаторы строк
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 это выглядит так:
{
"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, полузаписанный файл, зависший пейджер — и сужает поверхность по умолчанию. Он сам по себе не сдерживает решительного или совершенно сбитого с толку агента, который конструирует абсолютный путь. Соедините его с песочницей ОС, когда вам нужна эта гарантия.
Конфигурация плотно: чек-лист
Если вы хотите максимальную безопасность, которую этот инструмент может дать, настройте его так:
- Установите
--pathна самый узкий каталог проекта, который нужен задаче. Не ваш дом, не/, не родитель, содержащий ещё пять репозиториев. Один проект, один корень. - Gitignore-ните свои секреты до того, как указывать агента на репозиторий.
.env,*.pem, файлы учётных данных, локальная конфигурация. Они становятся невидимыми для листингов и поискаview. Это бесплатно и работает. - Используйте
--line-mode hashдля любого многошагового редактирующего агента. Стабильные ссылки на строки означают меньше ошибок «отредактировал не ту строку», что одновременно надёжность и безопасность. - Предпочитайте типизированные инструменты
shellв инструкциях вашего агента. Подсказки при неправильном использовании толкают в эту сторону; усильте это. Каждый файл, прочитанный черезviewвместоcat, — тот, который соблюдает правила ignore. - Гейтите деструктивные операции в слое разрешений рантайма вашего агента. Octofs предоставляет инструменты; ваш рантайм решает, какие требуют одобрения. Автоодобряйте
view. Всегда спрашивайте дляshellиdeleteизtext_editor. Здесь человек остаётся в контуре для глаголов, которые нельзя отменить черезundo_edit. - Когда радиус поражения реален, поместите всё это в контейнер. Примонтируйте только каталог проекта. Теперь разрыв абсолютных путей закрыт ядром, а не надеждой.
Сдвиг мышления — это суть. Вы не доверяете модели быть осторожной. Вы устраиваете мир так, чтобы неосторожность имела малый радиус поражения — ограниченный корень, фильтрованные чтения, обратимые записи, неинтерактивная оболочка и явный рабочий каталог.
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 — open source под Apache-2.0 — единый бинарник на Rust, текущая версия 0.5.0. Ограничение, обход с учётом gitignore, атомарные записи и неинтерактивная оболочка, описанные здесь, всё в исходниках; оговорка про абсолютные пути тоже. Если нужной вам защиты там нет, откройте issue.



