
Malleabilidade de assinaturas permite reencodar o commit assinado e gerar outro hash ainda validado, fragilizando bloqueios, deduplicação e sistemas que tratam hash verificado como identificador único.
| Componente | Modelo de verificação “Verified” do GitHub aplicado a commits Git assinados (OpenPGP, S/MIME/X.509 e chaves ECDSA/RSA/EdDSA, conforme o tipo de assinatura) |
| Vetor | Reescrita do commit assinando sem posse da chave, reencodando bytes da assinatura (ou campos não cobertos por ela) para produzir um novo objeto de commit com mesmo conteúdo/metadata, mas hash diferente, ainda aceito como assinatura válida e exibido como “Verified” |
| Impacto | Sistemas que bloqueiam, deduplicam ou registram proveniência usando apenas hash de commit “Verified” podem ser contornados: o mesmo conteúdo pode reaparecer sob um novo hash igualmente verificado; espelhos hostis podem entregar históricos com hashes diferentes do repositório canônico sem alterar arquivos |
| Prioridade | Revisar políticas e ferramentas que tratam hash de commit verificado como identificador permanente; reforçar validação com canonicalização/normalização de assinaturas antes de confiar no hash e, quando possível, manter um hash independente do conteúdo dos arquivos como backstop |
| Artefatos | Ferramenta pública (citada na pesquisa) que demonstra três técnicas de malleabilidade e reescreve a cadeia de commits para manter consistência de pais/filhos; repositórios de demonstração com commits “malleated” ainda marcados como “Verified” no GitHub |
| Mitigação | Correção principal é do lado do forge: normalizar a assinatura antes da verificação (por exemplo, impor codificação estrita e formas canônicas), e associar o estado “Verified” a uma verificação reexecutável/derivada do conteúdo normalizado, não a bytes malleáveis; em pipelines, preferir controles que verifiquem assinatura e também o conteúdo efetivamente obtido |
Uma pesquisa recente descreve um problema operacional em torno de commits Git assinados exibidos como “Verified” no GitHub: o hash do commit, que muitos processos tratam como nome único e permanente do conteúdo, pode ser alterado sem que o código mude e sem que a assinatura deixe de ser considerada válida. Na prática, é possível criar um “gêmeo” de um commit assinado — com os mesmos arquivos, autor e data —, mas com um hash diferente, e ainda assim obter a marcação “Verified” na interface do GitHub. Isso não cria um commit com conteúdo diferente sob o mesmo hash; o que ocorre é o inverso: o mesmo conteúdo pode existir sob múltiplos hashes válidos quando a assinatura é reencodada de formas equivalentes.
O efeito importa porque uma parte relevante do ecossistema de supply chain e governança de código usa o hash do commit como chave primária: listas de bloqueio por hash, deduplicação de objetos, trilhas de proveniência e registros que assumem “hash verificado” como identificador inconfundível. Se uma organização bloqueia um commit “ruim” apenas pelo hash, um adversário pode reapresentar exatamente o mesmo conteúdo sob um novo hash ainda assinado e ainda exibido como “Verified”, escapando de controles que só reconhecem o identificador anterior. A pesquisa também descreve cenários em que um espelho comprometido pode servir a clonadores commits validamente assinados, porém com hashes diferentes do repositório canônico, criando divergência aparente de histórico mesmo com arquivos idênticos.
O fluxo começa com a observação de que o hash de um commit Git é calculado sobre todo o objeto do commit, incluindo os bytes brutos da assinatura armazenada no cabeçalho. Se esses bytes puderem ser reescritos de forma que a assinatura continue válida, então o objeto inteiro muda e o hash muda, mesmo que nenhuma linha de código seja alterada. A pesquisa descreve esse fenômeno como “hash chain malleability”: como cada commit referencia seu pai pelo hash, alterar um commit no meio da cadeia força a alteração dos hashes dos commits descendentes para manter a cadeia coerente. A ferramenta citada no trabalho reescreve essa cadeia para preservar consistência estrutural.
A causa raiz apresentada é malleabilidade de assinatura combinada com validação permissiva no lado do forge. As três rotas exploradas compartilham um habilitador comum: o GitHub não normaliza a assinatura antes de verificá-la (sem imposição estrita de codificação em S/MIME, sem remoção de certos campos em OpenPGP, e aceitando valores ECDSA não canônicos). Com isso, diferentes representações byte a byte que preservam a validade criptográfica podem ser aceitas “como estão”, e cada variação gera um hash distinto. O texto também descreve um aspecto de estado: o GitHub registra “Verified” por hash de commit e não revalida esse registro, de modo que um commit pode permanecer “Verified” mesmo após a revogação da chave de assinatura, o que reforça a necessidade de tratar “Verified” como resultado de uma verificação reexecutável, não como atributo permanente do identificador.
A superfície afetada principal é o conjunto de processos que tomam decisões com base no hash do commit marcado como “Verified”, assumindo que esse hash identifica de forma única “o que foi revisado” ou “o que foi aprovado”. O problema se manifesta quando controles são implementados como negação/permitido por hash, quando deduplicação agrupa por hash de commit em vez de pelo conteúdo dos arquivos, e quando trilhas de auditoria e proveniência registram apenas o hash “verificado” como prova de imutabilidade. Nesse cenário, o hash deixa de ser uma âncora única para o conteúdo, porque múltiplos hashes podem apontar para o mesmo conjunto de arquivos e metadata.
Também entram na superfície ambientes que dependem de espelhos, caches ou réplicas de repositórios, inclusive em cadeias de CI/CD e fluxos de consumo de dependências que clonam de locais alternativos. A pesquisa descreve que um espelho hostil pode apresentar commits assinados com hashes diferentes dos vistos no forge canônico, sem alterar os arquivos. Isso cria risco de inconsistência em comparações de histórico e em sistemas que tentam reconciliar “o mesmo commit” apenas pelo hash. Ao mesmo tempo, o texto ressalta um limite importante: quem fixa um hash específico continua obtendo o conteúdo esperado para aquele hash (ou falha), porque não se trata de colisão de hash nem de quebra de SHA-1/SHA-256; o risco está no ecossistema que presume unicidade do identificador, não na integridade do conteúdo obtido ao pinhar um hash exato.
- Pipelines e políticas que bloqueiam ou aprovam mudanças por lista de hashes de commits, assumindo unicidade do identificador
- Sistemas de proveniência, inventário e deduplicação que registram apenas o hash do commit (mesmo quando “Verified”) como chave principal
- Cadeias com espelhos, mirrors ou repositórios intermediários capazes de entregar históricos assinados com hashes divergentes do repositório canônico
A pesquisa não descreve uma exploração com código diferente passando por verificação de assinatura; portanto, hunting deve se concentrar em sinais de reescrita de histórico e em inconsistências entre identidades lógicas (autor/data/mensagem/árvore de arquivos) e identificadores (hashes) em diferentes referências. Um padrão operacional relevante é a presença de dois commits com conteúdo e metadata equivalentes, mas com hashes distintos, exibidos como “Verified”. Outro sinal é divergência anômala em comparações de ramos: o GitHub pode tratar dois históricos como divergentes (um commit à frente e outro atrás) mesmo quando os arquivos são idênticos, porque os hashes não batem.
Em ambientes com mirrors, a telemetria útil inclui comparação de referências canônicas (por exemplo, o que o forge principal anuncia como head de um ramo) com o que um espelho entrega, e validação local de assinaturas com ferramentas estritas quando aplicável. O texto menciona especificamente que, em S/MIME, uma verificação local mais estrita pode rejeitar estruturas que o GitHub ainda considera “Verified”, o que sugere que discrepâncias entre verificadores (estrito vs permissivo) podem ser um indicador de risco de normalização. Para controle defensivo, a coleta de evidências deve priorizar a rastreabilidade do conteúdo efetivamente obtido (hash da árvore de arquivos e/ou hash independente do conteúdo), não apenas o hash do objeto commit.
Como o problema também toca a persistência do estado “Verified” no forge, uma linha de investigação é detectar commits que permanecem marcados como verificados após mudanças de confiança na chave (por exemplo, revogação), quando a organização usa esse selo como sinal forte. Essa verificação não depende de publicar comandos nem técnicas ofensivas; é uma inspeção de consistência e de política de confiança aplicada ao longo do tempo.
- Commits “Verified” com mesmo conteúdo/metadata e hashes diferentes em ramos distintos ou após reescritas do histórico
- Diferenças entre a validação local estrita (especialmente em S/MIME) e o selo “Verified” exibido no forge para o mesmo objeto
- Divergências de histórico em compare views (ou equivalentes) com arquivos idênticos, mas hashes não coincidentes, sugerindo reencodificação do objeto
A mitigação principal indicada é do lado do forge: canonicalizar e normalizar assinaturas antes de verificar e antes de derivar confiança do hash. A lógica é tornar a verificação dependente de uma forma canônica de codificação, reduzindo a possibilidade de múltiplas representações válidas produzirem diferentes hashes e, ao mesmo tempo, receberem o mesmo selo de confiança. O texto compara a situação a um problema histórico em ECDSA no ecossistema de Bitcoin, onde a correção consistiu em aceitar apenas uma forma canônica (“low-S”) e, posteriormente, separar a assinatura do identificador; o paralelo serve como orientação de engenharia: limitar a maleabilidade por validação canônica e por desenho de identificadores.
Do lado do consumidor (equipes de engenharia, AppSec e supply chain), a ação concreta é revisar onde o hash do commit é usado como “identidade” e reforçar controles que exigem verificação e normalização antes de confiar no identificador. Quando a organização mantém logs de proveniência, bloqueios de segurança e deduplicação, a recomendação é não depender apenas de “hash de commit verificado”, e sim correlacionar com um identificador independente do conteúdo efetivamente obtido, como hash da árvore de arquivos ou um hash de saída fixa do artefato construído quando a cadeia permitir. O texto observa que esquemas que também fixam um hash independente do conteúdo dos arquivos mantêm um backstop; os que param no hash do commit, mesmo verificado, não têm esse amortecedor.
A pesquisa também contextualiza o tema em discussões recentes sobre pinagem por hash em GitHub Actions após sequestros de tags em 2025 e 2026. A orientação de pinhar por hash continua válida para impedir ataques baseados em tags móveis; o ponto adicional é evitar superestimar o hash verificado como “nome único”. Em termos operacionais, isso se traduz em: manter pinagem por hash onde ela reduz superfície de ataque, mas desenhar políticas de bloqueio, auditoria e proveniência para tolerarem a existência de múltiplos hashes para o mesmo conteúdo quando a assinatura é reencodada em formas válidas.
- Priorizar validação canônica de assinaturas no forge (normalização de codificação e rejeição de formas não canônicas) antes de registrar/exibir “Verified”
- Revisar controles que bloqueiam/aprovam por hash de commit e complementar com verificação do conteúdo efetivamente obtido (hash independente de arquivos/artefatos)
- Endurecer a governança de mirrors e fontes intermediárias, validando consistência entre referências canônicas e o que é servido por réplicas/caches
0 Comentários