ADR: Подпись коммитов контрибьютором
- Status: Accepted by Andrei Ganiushkin
andrey@ganyushkin.ru - Date: 2026-07-26
- Authors: Andrei Ganiushkin
andrey@ganyushkin.ru - Related issues: -
Context
Netgap — проект с открытым исходным кодом, в котором вклад принимается только от
контрибьюторов, прошедших процесс получения статуса (договор, передача прав — см.
CONTRIBUTING.md и CONTRIBUTING-CAA.ru.md). Список авторизованных участников ведётся в
CONTRIBUTORS.json, где для каждого контрибьютора зафиксированы email, role, permissions
и pgp_key_id.
Netgap — это по сути сетевой шлюз с зоной ответственности вплоть до обеспечения безопасности изолируемых периметров. Ошибка в коде или намеренная закладка может иметь последствия для безопасности инфраструктуры конечных пользователей. Поэтому важна фиксация кто именно и с каким формальным согласием внёс изменение.
Ключевой вопрос ADR: подход к фиксации авторства изменений, вносимых контрибьюторами.
Decision
Каждый коммит контрибьютора обязан быть:
- подписан личным GPG-ключом контрибьютора (
git commit -S), при этом подпись должна быть криптографически валидна и её ключ должен совпадать сpgp_key_id, зафиксированным за этим контрибьютором в CONTRIBUTORS.json. - снабжён трейлером
Signed-off-by:(git commit -s).
Рекомендуемая команда фиксации изменения:
git commit -S -s -m "Implement CSV export for reports"
Проверка авторства выполняется в CI автоматически (check-signed-off-by, check-gpg-signature,
check-contributors).
Considered Alternatives
Alternative 1: Полагаться только на проверку по платформе (аккаунт, Verified badge)
Не требовать GPG-подпись, доверять авторству коммита полю author/committer и статусу
верификации платформы (например, GitVerse, SourceCraft или GitLab «Verified», основанный на подписанном коммите через
веб-интерфейс или привязанный SSH/GPG-ключ на уровне аккаунта).
- Pros: ниже порог входа для контрибьюторов — не нужно генерировать и настраивать GPG-ключ локально. Проще онбординг.
- Cons: поле
author/emailкоммита в Git тривиально подделывается (git commit --author="...") и никак не подтверждает личность. Статус верификации платформы привязан к учётной записи на конкретном хостинге, а не к контрибьютору напрямую, и может быть утрачен или скомпрометирован при смене хостинга либо компрометации аккаунта. Отсутствует переносимая, независимая от платформы криптографическая привязка автора к содержимому коммита. При экспорте/зеркалировании репозитория на другой хостинг подтверждение личности теряется. - Reason for rejection: не даёт гарантии, что автор коммита — тот, кем он представлен, и оставляет проект уязвимым к подмене авторства и к атакам через компрометацию учётной записи на платформе, а не ключа конкретного человека.
Alternative 2: Подпись коммитов автоматизированным ключом CI/CD (боты, service account)
CI после прохождения проверок сам переподписывает или ставит коммит от имени сервисного ключа.
- Pros: единообразная, полностью автоматизированная подпись. Не требует от контрибьюторов генерации и хранения личных ключей.
- Cons: сервисный ключ подтверждает лишь факт «коммит прошёл через конвейер CI», а не то,
кто именно и с каким согласием внёс изменение — то есть теряется главная функция: привязка к конкретному человеку и его формальному
согласию (
Signed-off-by). Компрометация единственного сервисного ключа обесценивает подписи сразу всех коммитов проекта. - Reason for rejection: заменяет проверку личности проверкой самого факта прогона конвейера, что не решает задачу — установить, кто именно нёс ответственность за изменение, и создаёт единую точку отказа безопасности (компрометация одного ключа CI).
Alternative 3: Только Signed-off-by без GPG-подписи
Требовать трейлер Signed-off-by:, но не требовать
криптографическую GPG-подпись.
- Pros: проще в реализации и поддержке — не нужна инфраструктура ключей и сверка с
CONTRIBUTORS.json по
pgp_key_id. Ниже порог входа для контрибьюторов. - Cons: трейлер
Signed-off-by:— это просто текстовая строка в теле сообщения коммита, добавляемая флагом-s. Она никак не привязана криптографически к автору и подделывается так же легко, как и само полеauthor— то есть не даёт технической гарантии, только декларацию. - Reason for rejection: без GPG-подписи
Signed-off-by:— это заявление без возможности проверки.
Alternative 4: Ревью и апрув maintainer как единственная гарантия личности автора
Не требовать GPG-подпись автора коммита, полагаться на то, что maintainer при апруве MR/PR лично удостоверяется в личности контрибьютора.
- Pros: не требует от контрибьюторов дополнительной технической подготовки (генерации ключа). Процесс становится «социальным», а не техническим.
- Cons: ревью подтверждает на момент апрува, что maintainer провёл проверку, но не создаёт неотчуждаемого криптографического доказательства авторства, привязанного к конкретному коммиту навсегда — при последующем аудите или споре об авторстве нечем подтвердить решение задним числом. Не масштабируется при росте числа контрибьюторов и maintainer; полностью зависит от добросовестности и внимательности одного человека на каждый MR/PR.
- Reason for rejection: ревью и апрув maintainer остаются обязательными, но как дополнение, а не замена криптографической подписи — они решают разные задачи: ревью проверяет содержимое изменения, GPG-подпись — личность автора.
Consequences
Positive
- Каждый коммит в истории Netgap криптографически привязан к конкретному человеку из
CONTRIBUTORS.json, а не только к текстовому полю
author, которое тривиально подделывается. - Компрометация одной учётной записи на платформе хостинга не позволяет злоумышленнику внести коммит от чужого имени: без приватного GPG-ключа контрибьютора подпись невалидна.
- Появляется техническая основа для расследования инцидентов и разбора спорных ситуаций (кто, когда и с каким формальным согласием внёс конкретное изменение) — что критично для продукта с повышенными требованиями к прослеживаемости изменений.
- Требование переносимо между платформами: подпись коммита не зависит от конкретного хостинга (GitVerse, SourceCraft или GitLab) и сохраняет силу при миграции или зеркалировании репозитория.
- Автоматическая сверка (
check-gpg-signature,check-signed-off-by,check-contributors) снимает нагрузку с maintainer по ручной проверке личности на каждый MR/PR, оставляя ему содержательное ревью кода. - Условие закладывает единый принцип для проекта: критичные для доверия действия подтверждаются подписью конкретного человека, а не автоматикой.
Negative
- Повышается порог входа для новых контрибьюторов: требуется сгенерировать GPG-ключ, передать
его отпечаток для внесения в CONTRIBUTORS.json и настроить локальное окружение (
git config user.signingkey,commit.gpgsign). - Компрометация или потеря личного приватного ключа контрибьютора требует организационной
процедуры отзыва и замены
pgp_key_idв CONTRIBUTORS.json. - После
rebase/squashGPG-подпись становится недействительной и должна быть проставлена заново автором — дополнительный шаг в рабочем процессе.
Risks and Mitigations
- Risk: приватный GPG-ключ контрибьютора скомпрометирован или утерян.
- Mitigation: организационная процедура отзыва ключа и обновления
pgp_key_idв CONTRIBUTORS.json. При подозрении на компрометацию — блокировка приёма коммитов от соответствующего автора до переоформления ключа.
- Mitigation: организационная процедура отзыва ключа и обновления
- Risk: контрибьютор не настроил окружение (нет ключа, ключ не совпадает с CONTRIBUTORS.json), и его коммиты систематически отклоняются CI, что снижает скорость разработки.
- Risk: сервисный ключ maintainer, если он всё же появится в отдельных служебных сценариях
(например, автоматическое обновление зависимостей), ошибочно используется вместо личной
подписи контрибьютора.
- Mitigation: любые служебные коммиты (если они появятся) должны проходить тот же контроль — либо не допускаются вовсе, либо подписываются maintainer лично.
Acceptance Criteria
- CI отклоняет любой коммит без GPG-подписи или с подписью, ключ которой не совпадает с
pgp_key_idсоответствующего автора в CONTRIBUTORS.json. - CI отклоняет любой коммит без трейлера
Signed-off-by:. - CI отклоняет любой коммит, автор которого отсутствует в CONTRIBUTORS.json.
- В CONTRIBUTING.md описан порядок генерации GPG-ключа, передачи его отпечатка maintainer и включения в CONTRIBUTORS.json.