shellfirm — защита терминала от опасных команд

· 10 мин чтения
ai-agents security guardrails cli rust
📂 Исходный код на GitHub

Утилита на Rust, которая перехватывает опасные команды в терминале до их выполнения: 100+ шаблонов для git, файловой системы, Docker, Kubernetes, облаков и баз данных, защита с учётом контекста, журнал аудита и MCP-сервер для ИИ-агентов.

shellfirm — защита терминала от опасных команд

Человек ошибается в командах редко. ИИ-агент ошибается часто и делает это быстро: просишь «поправить конфиг» — через секунду получаешь rm -rf ./src и несколько часов работы, которые никто не сохранял. shellfirm встаёт в терминал до выполнения команды, ловит опасные и просит подтвердить действие короткой задачкой. Инструмент написан на Rust, распространяется по лицензии Apache-2.0 и работает в восьми оболочках.

Что такое shellfirm

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

Главная идея простая. Опасная команда почти всегда вводится по ошибке, в спешке, по незнанию. Если перед выполнением нужно остановиться и решить пример (8 + 0 = ?), человек успевает заметить, что команда не та. Проблема в том, что привычные подтверждения (Are you sure? [y/n]) люди уже проклацывают не глядя. Простая арифметическая задача работает лучше: её нельзя бездумно подтвердить.

Вот как это выглядит на практике:

$ rm -rf ./src
============ RISKY COMMAND DETECTED ============
Severity: Critical
Blast radius: [PROJECT] — Deletes 347 files (12.4 MB) in ./src
Description: You are going to delete everything in the path.

Solve the challenge: 8 + 0 = ? (^C to cancel)

И второй случай, когда опасна не сама команда, а её последствия:

$ git push origin main --force
============ RISKY COMMAND DETECTED ============
Severity: High
Blast radius: [RESOURCE] — Force-pushes branch main (3 commits behind remote)
Description: This command will force push and overwrite remote history.
Alternative: git push --force-with-lease
  (Checks that your local ref is up-to-date before force pushing, preventing accidental overwrites of others' work.)

Solve the challenge: 3 + 5 = ? (^C to cancel)

Обратите внимание на три вещи. Понятный текст вместо загадочного сообщения. Понятная причина. И готовая безопасная замена, которую можно скопировать и выполнить.

Что он умеет ловить

В базе больше 100 шаблонов, разложенных по девяти экосистемам:

  • файловая система,
  • git,
  • Kubernetes,
  • Terraform,
  • Docker,
  • AWS,
  • GCP и Azure,
  • Heroku,
  • базы данных.

Поддерживаются Zsh, Bash, Fish, Nushell, PowerShell, Elvish, Xonsh и Oils. Команда shellfirm init сама определяет вашу оболочку и ставит нужный хук.

Каждое срабатывание получает уровень опасности: Critical, High, Medium, Low или Info. Вы можете задать порог, ниже которого проверки просто не срабатывают. Например, если Medium для вас мешает, поставьте min_severity: High. Пропущенные проверки всё равно попадают в журнал аудита, чтобы вы видели, что они были.

Кроме перехвата есть ещё три полезных механики.

Безопасные альтернативы. Рядом с каждым предупреждением shellfirm показывает, что написать вместо этого. Для git push --force он подсказывает git push --force-with-lease и объясняет, зачем: эта команда сначала проверит, что локальная ветка не отстала, и не даст затереть чужую работу.

Радиус поражения. Перед тем как показать предупреждение, shellfirm считает реальный масштаб последствий: сколько файлов и сколько мегабайт удалит rm -rf ./src, сколько коммитов улетит на сервер при --force. Расчёт занимает до трёх секунд и умеет тихо отключаться: если что-то не получилось, строка с масштабом просто не появляется.

Журнал аудита. Каждая перехваченная команда и решение пользователя записываются в файл построчно в формате JSON-строк. По нему можно потом разбираться, что происходило в терминале.

Как проходит проверка

Полный путь команды описан в документации по конвейеру проверки. Он состоит из десяти шагов.

1. Перехват хуком. Хук оболочки отправляет строку команды в shellfirm pre-command. На этом этапе команда — просто текст, ничего ещё не выполнено.

2. Удаление содержимого кавычек. Текст внутри кавычек вырезается. Иначе rm -rf "/" спокойно проскочил бы мимо проверки: символ / оказался бы внутри кавычках.

3. Разбиение на части по операторам. Команда делится по &&, ||, |, ; и переводу строки. Каждая часть проверяется отдельно, поэтому опасная команда не спрячется в цепочке с безопасными.

4. Параллельное сравнение с шаблонами. Каждая часть сверяется со всеми активными шаблонами через регулярные выражения. Сравнение идёт параллельно через библиотеку rayon, поэтому даже сотня правил не замедляет терминал.

5. Фильтры после совпадения. Регулярное выражение может поймать лишнее, поэтому дальше срабатывают фильтры. Их три:

  • NotContains — оставить совпадение, только если в команде нет подстроки. Так git push --force ловится, а git push --force-with-lease отфильтровывается.
  • Contains — оставить совпадение, только если подстрока есть.
  • PathExists — оставить совпадение, только если указанный путь правда есть на диске. Это спасает от ложных срабатываний, когда в пути опечатка.

6. Определение окружения. shellfirm смотрит, где вы находитесь. Подробнее об этом ниже.

7. Подключение правил проекта. Если рядом лежит .shellfirm.yaml, его правила добавляются к вашим личным.

8. Порог опасности. Проверки ниже min_severity пропускаются, но остаются в журнале.

9. Расчёт масштаба последствий.

10. Показ задачки или пропуск команды. Если ничего не сработало, команда выполняется как обычно.

Контекст решает, насколько строго спрашивать

Самая интересная часть shellfirm — реакция на окружение. Один и тот же git reset --hard в папке проекта на ноутбуке и на сервере продакшена — это разные вещи, и shellfirm это понимает. Подробное описание есть в разделе защиты с учётом контекста.

Сигналы считываются так:

Сигнал Как определяется Уровень риска
SSH-сессия переменные SSH_CONNECTION или SSH_TTY Повышенный
Запуск от root EUID=0 Критичный
Защищённая ветка git git rev-parse --abbrev-ref HEAD совпал со списком Критичный
Контекст Kubernetes продакшена kubectl config current-context содержит prod, production, prd или live Критичный
Переменные окружения продакшена NODE_ENV=production, RAILS_ENV=production, ENVIRONMENT=production Критичный

Защищённые ветки по умолчанию: main, master, production и release/*. Список можно поменять.

Дальше работает простое правило: контекст может только усилить проверку, никогда не ослабить. Если у вас в настройках стоит самая простая арифметика, а вы подключились по SSH — shellfirm потребует нажать Enter. Если вы работаете с веткой main под root — потребует ввести yes.

Примеры из документации:

Ситуация Настроенная задача Контекст Итоговая задача
Локальная разработка Math обычный Math
SSH на тестовый сервер Math повышенный Enter
Ветка main Math критичный Yes
Запуск от root Enter критичный Yes
Уже выбран Yes Yes повышенный Yes

В самом окне задачки показываются метки контекста, чтобы было видно, почему требование такое строгое:

  ============ RISKY COMMAND DETECTED ============
  Severity: CRITICAL
  Context: ssh=true, branch=main, k8s=prod-us-east-1

  ? Type `yes` to continue Esc to cancel ›

Правила проекта в файле .shellfirm.yaml

Индивидуальные настройки живут у человека. А вот правила для команды удобно хранить в репозитории, чтобы они были у всех и попадали в репозиторий при ревью. Для этого shellfirm читает .shellfirm.yaml из текущей папки или любой родительской. Подробности — в разделе правил команды.

Важное ограничение: политика проекта может только добавлять. Она способна поднять серьёзность, добавить запрет или потребовать более строгую задачу. Ослабить ваши личные настройки она не может.

Шаблон создаётся одной командой, а проверить синтаксис и идентификаторы шаблонов можно так:

shellfirm policy init
shellfirm policy validate

Структура файла:

# .shellfirm.yaml
version: 1

# Optional: deny specific patterns entirely
deny: []

# Optional: override challenge types
overrides: []

# Optional: add custom check patterns
checks: []

# Optional: project-specific context configuration
context: {}

Пример для команды, которая выкатывает веб-приложение:

# .shellfirm.yaml
version: 1

deny:
  # Never allow these in this repository
  - "git:force_push"
  - "fs:format_filesystem"

overrides:
  # Always require S2 for destructive file operations
  - id: "fs:recursively_delete"
    challenge: Yes

  # Require S4 for git reset --hard on protected branches
  - id: "git:reset_hard"
    challenge: Yes
    on_branches:
      - main
      - production

checks:
  # Team-specific deployment check
  - id: "team:deploy_production"
    from: base
    test: "deploy\\s+--target\\s+production"
    severity: High
    description: "Direct production deployment"
    alternative: "deploy --target staging"
    alternative_info: "Deploy to staging and promote after verification"

Через checks можно описать свои скрипты выкатки, которыми пользуется только ваша команда, и shellfirm начнёт спрашивать перед ними так же, как перед встроенными правилами.

Настройки

Файл настроек создавать не обязательно: shellfirm подставляет значения по умолчанию. Он лежит в ~/.config/shellfirm/settings.yaml на Linux и в ~/Library/Application Support/shellfirm/settings.yaml на macOS. Точный путь покажет shellfirm status.

Менять настройки удобно через shellfirm config: без аргументов открывается интерактивное меню, shellfirm config show печатает текущие значения, shellfirm config edit открывает файл в редакторе, а shellfirm config reset возвращает всё к началу.

Настройки задаются разреженно: достаточно указать только то, что отличается от значений по умолчанию. Например, файл с одной строкой меняет тип задачи, а всё остальное остаётся как было.

challenge: Yes

Всего основных параметров несколько: challenge (тип задачи: Math, Enter или Yes), min_severity (порог опасности), enabled_groups и disabled_groups (какие группы проверок включать), ignores_patterns_ids (что не проверять), deny_patterns_ids (что запрещать без задачки), audit_enabled и blast_radius (включены по умолчанию), context (защищённые ветки и продакшен-окружения), agent и llm. Полный список — в справочнике настроек.

Отдельно стоит сказать про анализ с помощью языковой модели. Он включается только если вы сами включите функцию llm и пропишете провайдера и модель. По умолчанию анализа от ИИ нет — всё держится на регулярных выражениях.

Работа вместе с ИИ-агентами

Для ИИ-агентов в shellfirm есть отдельный режим. Логика простая: агент не может решить задачу с клавиатуры, спрашивать человека он тоже не умеет. Поэтому есть порог, выше которого команда просто не выполняется.

По умолчанию порог равен High. Команды ниже порога проходят, команды выше — блокируются с указанием причины. Настроить можно в блоке agent:

agent:
  auto_deny_severity: High
  require_human_approval: false

Пять уровней на выбор: Info (блокировать всё найденное), Low, Medium (сбалансированный вариант), High (по умолчанию) и Critical (блокировать только самое страшное, дать агенту максимум свободы).

Списки запрета важнее порога: команды, попавшие в deny_patterns_ids, блокируются всегда, независимо от severity и настроек.

Флаг require_human_approval: true меняет поведение на более мягкий вариант: в ответе агента появляется requires_human_approval: true, и ваша система может передать команду человеку на ревью вместо молчаливой блокировки.

Для Claude Code всё настраивается одной командой:

shellfirm connect claude-code

Она добавляет сразу два слоя. Hooks — каждая команда Bash проверяется до выполнения, опасные блокируются. MCP — агент может сам спросить у shellfirm, насколько опасна команда, и получить безопасную замену. В режиме агента доступны четыре инструмента:

Инструмент Что делает
check_command Проверяет команду и возвращает серьёзность, сработавшие правила и альтернативы
suggest_alternative Возвращает безопасные замены
explain_risk Подробно объясняет, чем команда опасна
get_policy Читает активную конфигурацию и правила проекта

Тот же MCP-сервер подключается к Cursor и другим агентам. Про все варианты интеграции, включая CI/CD, скрипты и cron, написано в разделе про автоматизацию.

Отдельно есть режим для интерактивных инструментов: shellfirm wrap запускает psql, mysql или redis-cli и проверяет запросы перед отправкой в базу. Так защита работает не только на уровне командной строки, но и внутри REPL.

Установка

Три способа, все дают одну и ту же утилиту:

# npm
npm install -g @shellfirm/cli

# Homebrew
brew tap kaplanelad/tap && brew install shellfirm

# Cargo
cargo install shellfirm

Готовый бинарный файл можно скачать со страницы релизов.

Быстрый старт из трёх шагов:

# 1. Поставить хук (оболочка определится сама)
shellfirm init

# 2. Перезапустить оболочку или перечитать файл инициализации
source ~/.zshrc

# 3. Проверить
git reset --hard

По инструкции для ручной настройки, если нужно что-то нестандартное, есть раздел по настройке оболочки. Там же описан плагин для Oh My Zsh.

Кому это нужно

shellfirm закрывает одну конкретную дыру: между намерением и выполнением команды нет ничего, кроме подтверждения, которое легко не заметить. Инструмент добавляет в это место паузу и немного контекста.

Больше всего от него выигрывают те, кто работает с агентами, которым доверяют выполнять команды. Живой человек обычно помнит, что git push --force на main — плохая идея. Агент не помнит ничего и действует по инструкции из контекста. Здесь внешняя проверка становится последним рубежом, и это единственный рубеж, который агент не может обойти.

Остальным инструмент тоже полезен: ssh на боевой сервер под root, дедлайн, спешка, усталость. Момент, когда команда уже набрана, но ещё не выполнена, — единственное место, где её ещё можно остановить.

Источник: https://github.com/kaplanelad/shellfirm