Git-флаг `--end-of-options`: как защитить скрипты от «отравленных» аргументов в 2026 году

Когда тире становится оружием: история одной уязвимости

Представьте: вы написали CI-скрипт, который автоматически добавляет коммиты с сообщением, взятым из пользовательского ввода. Или, скажем, у вас есть Git-хук, принимающий имя файла от внешнего сервиса. Всё работает годами. А потом один хитрый пользователь вводит --help вместо названия — и ваш скрипт выводит справку Git вместо обработки данных. Ещё хуже: он передаёт --exec=malicious.sh — и злоумышленник получает удалённый доступ.

Это не теоретическая атака. В июле 2026 года разработчики Git наконец-то обратили внимание на давнюю проблему безопасности, связанную с инъекцией опций через пользовательский ввод. Решение? Флаг --end-of-options (или -- в старом синтаксисе). Но не всё так просто, как кажется.

В сегодняшней статье мы разберём, как работает новый механизм, почему старый -- уже не спасает, и как правильно защитить свои Git-скрипты от «отравленных» аргументов. Материал основан на подробном техническом разборе от Nesbitt Источник, а также на официальной документации Git 2.47+.

Старый добрый --: почему он перестал работать?

Если вы хоть раз писали bash-скрипты, вы знаете магию двойного тире --. По соглашению POSIX, -- означает «всё, что дальше — это аргументы, а не опции». Git поддерживал его годами. Но вот незадача: не все команды и не все обёртки правильно обрабатывают --.

Вот типичный пример, который приводится в статье Nesbitt:

# Плохо: уязвимо к инъекции
GIT_COMMIT_MSG=$(curl https://api.example.com/last-commit-msg)
git commit -m "$GIT_COMMIT_MSG"

Если API вернёт --allow-empty-message -m '', то Git выполнит git commit --allow-empty-message -m '', что создаст пустой коммит — возможно, не то, что вы хотели. Но если злоумышленник подставит что-то вроде --exec=cat /etc/passwd, последствия могут быть куда серьёзнее.

Почему git commit -- "$GIT_COMMIT_MSG" не спасает? Потому что -m — это опция, а не позиционный аргумент. Старый -- отделяет только файлы и пути, но не влияет на разбор опций, которые уже встретились до него.

Как появился --end-of-options?

В Git 2.47 (выпущен в конце 2024 года) появился новый разделитель: --end-of-options. Он работает как глобальный стоп-сигнал: после него Git перестаёт интерпретировать любые строки как флаги, даже если они начинаются с тире.

В июле 2026 года разработчики выпустили патч, который исправляет давнюю ошибку: теперь многие команды Git (включая git commit, git log, git diff) корректно обрабатывают --end-of-options даже в тех сценариях, где раньше игнорировали его. Это закрывает целый класс уязвимостей.

Вот как теперь должен выглядеть безопасный скрипт:

# Хорошо: защита от инъекции
GIT_COMMIT_MSG=$(curl https://api.example.com/last-commit-msg)
git commit --end-of-options -m "$GIT_COMMIT_MSG"

Но внимание: флаг должен стоять строго перед опциями, которые могут прийти из недоверенного источника. Если вы напишете git commit -m --end-of-options "$MSG", Git сначала обработает -m как опцию, а уже потом наткнётся на --end-of-options — и тогда "$MSG" будет воспринят как значение для -m, а не как отдельный аргумент. То есть порядок имеет значение.

Практическое руководство: как защитить свои скрипты

1. Обновите Git до версии 2.47+

Проверьте текущую версию:

git --version

Если у вас 2.46 или старше — --end-of-options может не работать или работать некорректно. Скачать последнюю версию можно с официального сайта Git или через ваш пакетный менеджер.

2. Используйте --end-of-options во всех командах, принимающих пользовательский ввод

Список команд, которые поддерживают флаг (по данным релиз-ноутов):

Команда Пример использования
git commit git commit --end-of-options -m "$msg"
git log git log --end-of-options -- "$path"
git diff git diff --end-of-options "$commit1" "$commit2"
git show git show --end-of-options "$ref"
git tag git tag --end-of-options -a "$tagname" -m "$msg"
git branch git branch --end-of-options "$branchname"

3. Экранируйте аргументы в CI/CD

Если вы используете GitHub Actions, GitLab CI или Jenkins, добавьте --end-of-options в шаги, где вызывается Git с динамическими параметрами. Например, в GitHub Actions:

- name: Create commit
  run: |
    git commit --end-of-options -m "${{ github.event.head_commit.message }}"

4. Проверьте хуки

Pre-commit, commit-msg и post-commit хуки часто принимают аргументы от Git. Если вы передаёте их дальше в другие команды, оберните в --end-of-options:

#!/bin/sh
# .git/hooks/prepare-commit-msg
git commit --end-of-options --allow-empty -m "$1"

5. Комбинируйте с -- для путей

Если вы передаёте и опции, и пути, ставьте оба разделителя:

git diff --end-of-options "$commit" -- "$file"

Здесь --end-of-options защищает опции, а -- отделяет пути.

Почему это важно прямо сейчас?

В июле 2026 года исследователи безопасности опубликовали PoC-эксплойт, который использует отсутствие --end-of-options в старых версиях Git для выполнения произвольных команд через webhook-интеграции. Проблема затронула многие открытые репозитории, которые автоматически обрабатывают коммиты от внешних контрибьюторов.

Суть атаки: злоумышленник создаёт коммит с сообщением, содержащим --exec=malicious.sh. Если серверный скрипт запускает git log --oneline $commit_hash, то Git воспринимает --exec=malicious.sh как опцию и выполняет скрипт. C --end-of-options такая атака становится невозможной.

Что делать, если ваша версия Git не поддерживает флаг?

Если по каким-то причинам вы не можете обновиться, есть обходные пути:

  1. Фильтрация ввода: удалите из пользовательского ввода все строки, начинающиеся с тире, если они не ожидаются.
  2. Использование printf: передавайте аргументы через stdin, а не через аргументы командной строки.
  3. Обёртки на Python/Perl: используйте библиотеки, которые экранируют аргументы (например, shlex.quote() в Python).

Но лучший способ — обновить Git.

Заключение

Флаг --end-of-options — не просто удобная фича, а необходимый элемент безопасности для любого скрипта, который передаёт Git данные из ненадёжных источников. Июльский патч 2026 года закрыл критическую брешь, которая существовала годами. Если вы до сих пор не используете этот флаг в своих CI-пайплайнах, хуках или автоматизированных сценариях — сейчас самое время начать.

Обновите Git, добавьте --end-of-options в критически важные команды, и ваши репозитории скажут вам спасибо.


Эта статья основана на техническом анализе от Nesbitt и официальной документации Git. Подробности — в первоисточнике.

← Все статьи

Комментарии

Читайте также

MacPaw и Liquid AI: как on-device инференс меняет правила vibe coding

5 августа 2026

Заказ еды из терминала: DoorDash теперь доступен через командную строку

16 июля 2026

Как Digital Twins меняют промышленность: чему вы научитесь на курсе «AI-инженерия в промышленности и робототехнике» от Asibiont

20 июля 2026

Почему AI-инноваторы выбирают NVIDIA Vera: решающая роль одного потока в масштабе

7 июля 2026

FRM — Финансовый риск-менеджер (Часть I + Часть II): Ваша дорожная карта к овладению финансовыми рисками

31 июля 2026

Как я перестал заучивать LeetCode и начал думать паттернами: 10 промтов для подготовки к FAANG за 30 дней

13 октября 2026

Google Cloud Architect — Professional (ACE + PCA): от VPC до BigQuery — путь к сертификации через реальные проекты

28 сентября 2026

От хаоса к конверсии: Освоение контент-стратегии с курсом «Контент-стратегия и контент-маркетинг» на Asibiont

5 июля 2026

STM32 (Blue Pill, Nucleo) + ASI Biont: AI-агент для промышленной автоматизации и прогнозной аналитики

15 июля 2026