Commits “Verified” do GitHub podem ser reescritos sem invalidar assinaturas
9 de Julho de 2026

Uma nova pesquisa mostra que o hash de um commit assinado no Git não é o identificador único e definitivo que boa parte do ecossistema de software imagina.

Dado qualquer commit assinado, alguém sem a chave de assinatura pode criar um segundo commit com os mesmos arquivos, o mesmo autor e a mesma data, além de uma assinatura válida.

Ainda assim, o GitHub o marca como “Verified”.

Tudo o que um revisor verificaria continua batendo.

O hash do commit, não.

E isso importa porque muitos sistemas tratam um hash de commit verificado como um nome permanente e exclusivo para aquele conteúdo.

O problema prático é simples.

Se um commit malicioso for bloqueado pelo hash, um atacante pode reenviar o mesmo conteúdo com um novo hash, também “Verified”, que a lista de bloqueio nunca viu antes.

Rotinas de deduplicação, registros de procedência e registros de builds reproduzíveis que se apoiam no hash herdam a mesma fragilidade.

Um espelho comprometido ou malicioso também pode entregar aos usuários que fazem clone commits assinados de forma válida, mas com hashes diferentes dos repositórios canônicos.

Isso não significa, porém, que seja possível passar código diferente por uma verificação de assinatura.

Os arquivos são idênticos em todas as cópias, então um hash fixado continua baixando exatamente o conteúdo esperado, ou falha na tentativa.

Não há CVE nem comunicado de fornecedor, e nada precisa ser alterado no seu repositório.

A falha está na forma como uma plataforma decide o que significa “Verified”, e a correção precisa ser feita do lado da plataforma.

O trabalho é de Jacob Ginesin, doutorando na Carnegie Mellon University e auditor criptográfico na Cure53.

Seu artigo de cinco páginas, publicado no arXiv em 2 de julho, vem acompanhado de uma ferramenta pública que executa os três ataques, além de dois repositórios de demonstração nos quais os commits alterados continuam aparecendo como “Verified” no GitHub.

Como cada commit nomeia seu pai por hash, alterar um commit obriga a criação de novos hashes para todos os commits acima dele.

A ferramenta reescreve essa cadeia para mantê-la consistente.

Um descendente assinado, porém, perde seu próprio selo no momento em que o ponteiro para o pai é modificado.

Ginesin chama esse efeito de “malleability de cadeia de hashes”.

A causa está na malleability da assinatura.

O hash de um commit é calculado a partir de tudo o que há nele, inclusive os bytes brutos da assinatura no cabeçalho.

Muitas assinaturas podem ser reescritas em outra forma diferente, mas ainda válida, e mudar esses bytes altera o hash sem tocar em uma linha de código.

As três rotas cobrem todos os esquemas GPG que o GitHub verifica, além de S/MIME:

• Chaves ECDSA: basta inverter a assinatura com um truque clássico de álgebra de curvas elípticas, transformando o valor s em n - s.

As duas formas são válidas.

Isso passa em um git verify-commit local e ainda recebe o selo do GitHub.

• Chaves RSA e EdDSA: adiciona-se um campo extra, ignorado, à seção “unhashed” da assinatura, parte que a assinatura deliberadamente não cobre.

A assinatura continua válida, mas os bytes do commit, e portanto seu hash, mudam.

Tanto o ambiente local quanto o GitHub aceitam.

• Chaves S/MIME (X.509): reescreve-se um campo de comprimento na estrutura DER da assinatura para uma forma mais longa e fora do padrão.

Uma verificação local rígida, via gpgsm, rejeita o arquivo, mas o GitHub ainda o marca como “Verified”, e a ferramenta reproduz esse comportamento.

As três rotas têm um mesmo fator comum: o GitHub não normaliza a assinatura antes de verificá-la.

Não há codificação estrita para S/MIME, não há remoção desses campos do OpenPGP, e valores ECDSA não canônicos são aceitos como estão.

Depois disso, o GitHub grava um registro “Verified” associado a cada hash de commit e não faz uma nova checagem.

Assim, um commit permanece “Verified” mesmo depois de a chave de assinatura ser revogada.

Se o original e seu duplicado forem enviados para branches diferentes, a visualização de comparação do GitHub os trata como históricos divergentes, com um commit à frente e outro atrás, apesar de os arquivos serem idênticos.

Para deixar claro: isso não é uma colisão de hash.

Não quebra SHA-1 nem SHA-256, e não tem relação com a migração do Git para SHA-256.

Ninguém está forçando dois commits diferentes a compartilhar um hash.

O oposto é que acontece: um mesmo commit pode ser escrito de várias formas válidas, cada uma com seu próprio hash.

A ideia central é antiga.

O Bitcoin enfrentou exatamente a mesma simetria do ECDSA anos atrás, quando qualquer pessoa podia inverter o valor s em uma assinatura de transação e mudar o identificador da transação sem a chave do dono.

A correção foi aceitar apenas o formato “low-S” e, mais tarde, tirar as assinaturas do identificador com o SegWit.

As correções sugeridas no artigo seguem a mesma lógica: normalizar a codificação antes de confiar no hash.

É uma lição conhecida, não uma novidade exótica da criptografia.

O artigo também conecta o problema aos recentes sequestros de tags no GitHub Actions, incluindo os ataques ao tj-actions/changed-files em 2025 e ao trivy-action em 2026, este último citado no texto.

Depois desses incidentes, a orientação foi simples: fixe dependências por um hash completo de commit, e não por uma tag que pode mudar.

Essa recomendação continua válida.

Fixar por hash impediu aqueles ataques, e esta pesquisa não altera isso.

O ponto aqui é mais estreito.

No caso do Trivy, os commits maliciosos chamaram atenção porque não podiam ter sido assinados de forma válida.

O alerta agora é para não confiar demais nesse indício.

Uma assinatura válida prova quem assinou um commit, mas não transforma o hash do commit em um nome único e absoluto para o conteúdo.

Então, quem precisa agir? Não é o desenvolvedor que fixa uma Action ou um módulo.

Um hash fixado ainda busca o código correto.

A responsabilidade é das plataformas.

O artigo afirma que elas devem normalizar as assinaturas antes de confiar no hash.

Ferramentas que bloqueiam, deduplicam ou registram procedência com base no hash de commit deveriam fazer o mesmo, verificando e normalizando antes de confiar no hash bruto de um objeto assinado que um atacante pode reencodar.

Nem todos os sistemas ficam igualmente expostos.

Esquemas que também fixam um hash independente dos arquivos baixados, como as fixed-output derivations do Nix, mantêm uma camada de proteção.

Já os que param no hash de um commit verificado não têm essa defesa.

Ginesin afirma ter relatado o problema ao GNU e ao Git em janeiro, e ao GitHub em março, e diz que, até a publicação do artigo, nem o Git nem qualquer plataforma tinham tratado a questão.

A correção do lado da plataforma é bem compreendida, e o ponto mais óbvio para começar é o caso S/MIME, no qual o GitHub ainda aceita o que uma verificação local rígida rejeita.

Publicidade

Traçamos um caminho para você ir do zero ao avançado em hacking

Conheça a Solyd One, e tenha acesso a todos os cursos de segurança ofensiva da Solyd, todas as certificações práticas, incluindo a SYCP, 2 CTFs anuais com prêmios de até 30 mil reais, dezenas de laboratórios realísticos, suporte, comunidade e muito mais. Saiba mais...