Хеш подписанного коммита в Git — не такой уж уникальный идентификатор, как принято думать. Исследователь показал: человек, у которого нет доступа к приватному ключу подписи, способен создать второй коммит — с теми же файлами, тем же автором, той же датой и валидной подписью — но с другим хешем. GitHub при этом всё равно поставит на нём зелёную галочку «Verified».
Автор работы — Джейкоб Гинесин, аспирант Университета Карнеги-Меллон и криптографический аудитор компании Cure53. 2 июля он опубликовал на arXiv пятистраничную статью с описанием проблемы, приложил рабочий инструмент, реализующий все три варианта атаки, и выложил два демонстрационных репозитория, где «переродившиеся» коммиты по-прежнему помечены как проверенные.
Механизм называется «malleability цепочки хешей» — податливость к изменению. Каждый коммит в Git ссылается на родителя по хешу, поэтому изменение одного коммита автоматически меняет хеши всех потомков. Инструмент Гинесина перестраивает всю цепочку заново, чтобы сохранить консистентность. Правда, есть нюанс: как только у потомка меняется указатель на родителя, сам потомок теряет бейдж верификации — трюк работает только с конкретным целевым коммитом, а не бесконечно вниз по истории. Корень проблемы — в том, что хеш коммита считается по всему его содержимому, включая сырые байты подписи. А многие схемы подписи допускают перекодирование в другую, но всё ещё валидную форму. Меняются байты подписи — меняется хеш — код остаётся прежним.
Гинесин описал три маршрута атаки, покрывающих все схемы, которые GitHub помечает как проверенные.
Первый — ключи ECDSA. Здесь используется классическая алгебра эллиптических кривых: значение подписи s можно заменить на n − s, и обе формы останутся математически валидными. Такая подпись проходит и локальную проверку через git verify-commit, и получает бейдж на GitHub.
Второй — ключи RSA и EdDSA. В «нехешируемую» часть подписи (ту, что намеренно не покрывается самой подписью) добавляется лишнее поле, которое просто игнорируется при проверке. Подпись остаётся валидной, а байты коммита — и, соответственно, его хеш — меняются. Принимается и локально, и на GitHub.
Третий, самый показательный случай — ключи S/MIME на базе X.509. Здесь переписывается поле длины в DER-структуре подписи в нестандартную, но технически допустимую более длинную форму. Строгая локальная проверка через gpgsm такую подпись отклоняет. А GitHub — принимает и помечает как «Verified». Это расхождение инструмент Гинесина воспроизводит напрямую.
Общая причина всех трёх сценариев одна: GitHub не нормализует подписи перед их проверкой. Нет строгого контроля кодировки для S/MIME, нет удаления неаутентифицированных полей OpenPGP, нестандартные (не в канонической форме) значения ECDSA принимаются как есть.
К этому добавляются и другие особенности поведения платформы. GitHub фиксирует статус «Verified» привязанным к конкретному хешу коммита и никогда не перепроверяет его повторно — коммит остаётся «проверенным» даже после того, как ключ подписи был отозван. А если оригинал и его «двойник» запушить в две разные ветки, сравнивалка GitHub воспринимает их как разошедшиеся истории — одна впереди, другая позади, хотя содержимое файлов идентично.
У этой проблемы есть исторический аналог — Bitcoin столкнулся с точно такой же симметрией ECDSA много лет назад: любой человек мог изменить значение s в подписи транзакции и тем самым изменить её идентификатор, не имея ключа владельца. Решали проблему в два этапа: сначала ограничили подписи только «low-S» формой, а затем полностью вынесли подписи за пределы идентификатора транзакции через SegWit. Рекомендация из новой статьи звучит почти так же: канонизировать кодировку до того, как доверять хешу. Сам Гинесин называет это «известным уроком, а не экзотической новой криптографией».
Работа напрямую связана с недавними атаками на цепочки поставок через GitHub Actions — угоном тегов в проектах tj-actions/changed-files (2025 год) и trivy-action (2026 год). После этих инцидентов стандартной рекомендацией стало закрепление зависимостей на полный хеш коммита вместо изменяемого тега — и этот совет остаётся полностью верным, новое исследование его не отменяет. В случае с trivy-action вредоносные коммиты удалось выявить именно потому, что их не получалось подписать валидно. Но в этом и заключается предостережение Гинесина: не стоит слишком полагаться на подпись как на индикатор безопасности. Валидная подпись подтверждает, кто подписал коммит, но не гарантирует, что хеш коммита — уникальный идентификатор его содержимого.
Кто должен на это реагировать? Точно не разработчики, которые закрепляют зависимость на конкретный хеш Action или модуля, — закреплённый хеш всё равно скачает именно тот код, который ожидается, либо просто не сработает. Ответственность лежит на самих форджах — таких как GitHub. Рекомендованное решение — канонизировать подписи перед тем, как им доверять. То же касается любых систем дедупликации, логирования происхождения кода или блокировки по хешу: им стоит сначала проверять и приводить подпись к канонической форме, а не доверять сырому хешу подписанного объекта, поскольку атакующий может его перекодировать.
Не все системы одинаково уязвимы. Те, что дополнительно закрепляют независимый хеш непосредственно скачанных файлов, сохраняют защиту — в статье в качестве примера приводятся fixed-output derivations в Nix. А вот системы, полагающиеся исключительно на верифицированный хеш коммита, остаются открытыми для этой проблемы.
По срокам раскрытия: в январе Гинесин сообщил о находке в GNU и проект Git, в марте — в GitHub. На момент публикации статьи ни Git, ни какой-либо фордж проблему не устранили. Считается, что исправление на стороне форджей не требует чего-то принципиально нового — начать предлагается с кейса S/MIME, где GitHub сейчас принимает подписи, которые строгая локальная проверка через gpgsm отклоняет как невалидные.
Автор работы — Джейкоб Гинесин, аспирант Университета Карнеги-Меллон и криптографический аудитор компании Cure53. 2 июля он опубликовал на arXiv пятистраничную статью с описанием проблемы, приложил рабочий инструмент, реализующий все три варианта атаки, и выложил два демонстрационных репозитория, где «переродившиеся» коммиты по-прежнему помечены как проверенные.
Механизм называется «malleability цепочки хешей» — податливость к изменению. Каждый коммит в Git ссылается на родителя по хешу, поэтому изменение одного коммита автоматически меняет хеши всех потомков. Инструмент Гинесина перестраивает всю цепочку заново, чтобы сохранить консистентность. Правда, есть нюанс: как только у потомка меняется указатель на родителя, сам потомок теряет бейдж верификации — трюк работает только с конкретным целевым коммитом, а не бесконечно вниз по истории. Корень проблемы — в том, что хеш коммита считается по всему его содержимому, включая сырые байты подписи. А многие схемы подписи допускают перекодирование в другую, но всё ещё валидную форму. Меняются байты подписи — меняется хеш — код остаётся прежним.
Гинесин описал три маршрута атаки, покрывающих все схемы, которые GitHub помечает как проверенные.
Первый — ключи ECDSA. Здесь используется классическая алгебра эллиптических кривых: значение подписи s можно заменить на n − s, и обе формы останутся математически валидными. Такая подпись проходит и локальную проверку через git verify-commit, и получает бейдж на GitHub.
Второй — ключи RSA и EdDSA. В «нехешируемую» часть подписи (ту, что намеренно не покрывается самой подписью) добавляется лишнее поле, которое просто игнорируется при проверке. Подпись остаётся валидной, а байты коммита — и, соответственно, его хеш — меняются. Принимается и локально, и на GitHub.
Третий, самый показательный случай — ключи S/MIME на базе X.509. Здесь переписывается поле длины в DER-структуре подписи в нестандартную, но технически допустимую более длинную форму. Строгая локальная проверка через gpgsm такую подпись отклоняет. А GitHub — принимает и помечает как «Verified». Это расхождение инструмент Гинесина воспроизводит напрямую.
Общая причина всех трёх сценариев одна: GitHub не нормализует подписи перед их проверкой. Нет строгого контроля кодировки для S/MIME, нет удаления неаутентифицированных полей OpenPGP, нестандартные (не в канонической форме) значения ECDSA принимаются как есть.
К этому добавляются и другие особенности поведения платформы. GitHub фиксирует статус «Verified» привязанным к конкретному хешу коммита и никогда не перепроверяет его повторно — коммит остаётся «проверенным» даже после того, как ключ подписи был отозван. А если оригинал и его «двойник» запушить в две разные ветки, сравнивалка GitHub воспринимает их как разошедшиеся истории — одна впереди, другая позади, хотя содержимое файлов идентично.
У этой проблемы есть исторический аналог — Bitcoin столкнулся с точно такой же симметрией ECDSA много лет назад: любой человек мог изменить значение s в подписи транзакции и тем самым изменить её идентификатор, не имея ключа владельца. Решали проблему в два этапа: сначала ограничили подписи только «low-S» формой, а затем полностью вынесли подписи за пределы идентификатора транзакции через SegWit. Рекомендация из новой статьи звучит почти так же: канонизировать кодировку до того, как доверять хешу. Сам Гинесин называет это «известным уроком, а не экзотической новой криптографией».
Работа напрямую связана с недавними атаками на цепочки поставок через GitHub Actions — угоном тегов в проектах tj-actions/changed-files (2025 год) и trivy-action (2026 год). После этих инцидентов стандартной рекомендацией стало закрепление зависимостей на полный хеш коммита вместо изменяемого тега — и этот совет остаётся полностью верным, новое исследование его не отменяет. В случае с trivy-action вредоносные коммиты удалось выявить именно потому, что их не получалось подписать валидно. Но в этом и заключается предостережение Гинесина: не стоит слишком полагаться на подпись как на индикатор безопасности. Валидная подпись подтверждает, кто подписал коммит, но не гарантирует, что хеш коммита — уникальный идентификатор его содержимого.
Кто должен на это реагировать? Точно не разработчики, которые закрепляют зависимость на конкретный хеш Action или модуля, — закреплённый хеш всё равно скачает именно тот код, который ожидается, либо просто не сработает. Ответственность лежит на самих форджах — таких как GitHub. Рекомендованное решение — канонизировать подписи перед тем, как им доверять. То же касается любых систем дедупликации, логирования происхождения кода или блокировки по хешу: им стоит сначала проверять и приводить подпись к канонической форме, а не доверять сырому хешу подписанного объекта, поскольку атакующий может его перекодировать.
Не все системы одинаково уязвимы. Те, что дополнительно закрепляют независимый хеш непосредственно скачанных файлов, сохраняют защиту — в статье в качестве примера приводятся fixed-output derivations в Nix. А вот системы, полагающиеся исключительно на верифицированный хеш коммита, остаются открытыми для этой проблемы.
По срокам раскрытия: в январе Гинесин сообщил о находке в GNU и проект Git, в марте — в GitHub. На момент публикации статьи ни Git, ни какой-либо фордж проблему не устранили. Считается, что исправление на стороне форджей не требует чего-то принципиально нового — начать предлагается с кейса S/MIME, где GitHub сейчас принимает подписи, которые строгая локальная проверка через gpgsm отклоняет как невалидные.