10

Como o nome da minha máquina Fedora colocou um usuário de 2015 no meu repositório e o que descobri abrindo a API do GitHub

Enquanto eu desenvolvia um projeto de API REST em Java no meu ambiente Linux (Fedora), notei algo bizarro na página do meu repositório no GitHub: na barra lateral de Contributors, ao lado da minha foto, havia o perfil de um usuário desconhecido (@wroc), cuja conta havia sido criada em 2015 e não tinha atividade recente.

O detalhe mais intrigante? O nome da minha máquina/usuário local no Fedora na época em que iniciei o projeto era justamente wroc.

A partir dessa coincidência, comecei a me fazer algumas perguntas de segurança e arquitetura:

Como um usuário que nunca teve acesso ao meu repositório apareceu oficialmente como contribuidor do meu código?

Se a conta dele foi vinculada ao commit, o servidor do GitHub retornou dados reais da conta dele na resposta da API sem autenticação dele?

Até onde vai esse comportamento se eu tentar testar isso propositalmente com outra conta?

Este artigo documenta a investigação passo a passo — abrindo os objetos brutos do Git, inspecionando os payloads JSON da API do GitHub, decodificando identificadores em Base64 e executando uma Prova de Conceito (PoC) prática.

  1. Inspecionando o Objeto Bruto do Git (git cat-file)
    O primeiro passo foi verificar o que realmente havia sido gravado localmente nos primeiros commits do repositório (como o commit b1a5ca0).

Para ver o conteúdo real que o Git envia pela rede durante um git push, sem formatação, usamos o comando git cat-file -p:

git cat-file -p b1a5ca0

O resultado é surpreendentemente simples:


tree d3e2ab9087f6ccb4a141f7d368711233f879867d
author wroc <wroc@users.noreply.github.com> 1790719762 -0300
committer wroc <wroc@users.noreply.github.com> 1790719762 -0300

chore: initialize project structure and IDE configurations

Quando você executa git commit, o Git opera 100% offline. Ele não faz requisições de rede, não verifica chaves SSH e não valida se você é dono daquele e-mail. Ele apenas grava uma string de texto puro dentro de .git/objects/. Na época, meu Git local estava configurado com o nome da máquina Fedora (wroc) combinado com o domínio noreply do GitHub (wroc@users.noreply.github.com).

Mas o que acontece quando enviamos esse arquivo de texto puro para o servidor do GitHub via git push autenticado com a minha chave SSH (lipebackend)?

  1. O que o Servidor do GitHub devolve na Response da API?
    Consultando diretamente a API REST do GitHub para esse commit específico:

curl -sL https://api.github.com/repos/lipebackend/api-rest/commits/b1a5ca0
Recebemos o seguinte payload JSON do servidor do GitHub:

{
  "sha": "b1a5ca0edc8b69be88b91a67b4bd5d63543b8f90",
  "node_id": "C_kwDOUzptmtoAKGIxYTVjYTBlZGM4YjY5YmU4OGI5MWE2N2I0YmQ1ZDYzNTQzYjhmOTA",
  "commit": {
    "author": {
      "name": "wroc",
      "email": "wroc@users.noreply.github.com",
      "date": "2026-09-29T22:09:22Z"
    },
    "committer": {
      "name": "wroc",
      "email": "wroc@users.noreply.github.com",
      "date": "2026-09-29T22:09:22Z"
    },
    "message": "chore: initialize project structure and IDE configurations",
    "verification": {
      "verified": false,
      "reason": "unsigned",
      "signature": null,
      "payload": null
    }
  },
  "author": {
    "login": "wroc",
    "id": 11420820,
    "node_id": "MDQ6VXNlcjExNDIwODIw",
    "avatar_url": "https://avatars.githubusercontent.com/u/11420820?v=4",
    "url": "https://api.github.com/users/wroc",
    "html_url": "https://github.com/wroc",
    "repos_url": "https://api.github.com/users/wroc/repos",
    "type": "User",
    "user_view_type": "public",
    "site_admin": false
  }
}

Olhando atentamente para essa resposta da API, surgem três questões muito interessantes de engenharia de software e segurança:

Eu enviei apenas um texto (wroc@users.noreply.github.com), mas o servidor do GitHub me devolveu um objeto "author" completo contendo o id real do banco de dados (11420820), o avatar_url real daquele usuário e um node_id codificado em Base64 (MDQ6VXNlcjExNDIwODIw).
Quando consultamos o perfil público do usuário em https://api.github.com/users/wroc, o campo "email" dele está null! Como o GitHub associou o e-mail se o perfil público diz "email": null?
Por que o node_id está codificado em Base64?
3. Por que o node_id está em Base64? (MDQ6VXNlcjExNDIwODIw)
Se decodificarmos o campo "node_id": "MDQ6VXNlcjExNDIwODIw" no terminal Linux:

echo "MDQ6VXNlcjExNDIwODIw" | base64 -d

Saída: 04:User11420820

Por que os engenheiros do GitHub codificaram 04:User11420820 em Base64? Não foi por criptografia, mas por arquitetura de banco de dados e evolução de API (GraphQL Relay Specification):

No banco relacional do GitHub (MySQL), cada tabela possui sua própria chave primária sequencial (id). Existe o id = 11420820 na tabela users, mas também pode existir o id = 11420820 na tabela repositories ou issues.

Quando o GitHub introduziu a API GraphQL (v4), precisava de um identificador global único (Global Node ID) para que uma query genérica node(id: "...") soubesse exatamente em qual tabela fazer o SELECT.

A solução foi juntar o tamanho do tipo + nome da entidade + chave primária: 04:User11420820.
Por que Base64? Para criar um Identificador Opaco (Opaque Token), desencorajando clientes a fazerem split(":") no código e permitindo que o GitHub mudasse a estrutura interna no futuro sem quebrar contratos.

E o GitHub realmente mudou! Compare o node_id da conta wroc de 2015 (MDQ6VXNlcjExNDIwODIw — texto simples em Base64) com o node_id da minha conta mais recente lipebackend (U_kgDOE_lvuA — serializado em binário MessagePack + Base64).

  1. Como o GitHub vinculou a conta se "email": null na API pública?

Se você consultar https://api.github.com/users/wroc (ou o meu próprio perfil https://api.github.com/users/lipebackend), verá:


{
  "login": "wroc",
  "id": 11420820,
  "node_id": "MDQ6VXNlcjExNDIwODIw",
  "email": null,
  "created_at": "2015-03-11T08:40:54Z"
}

O campo "email": null no DTO público da API significa apenas que o usuário optou por não exibir um e-mail público na biografia do perfil. Porém, nos bastidores do servidor do GitHub, quando um commit entra via git push, o backend executa uma resolução interna de identidade:

Regra de Roteamento @users.noreply.github.com: Contas criadas antes de 2017 utilizavam o formato @users.noreply.github.com (contas mais novas usam +@users.noreply.github.com, justamente para evitar colisões ou perda de vínculo ao trocar de @username).

Quando o GitHub lê wroc@users.noreply.github.com no commit, ele extrai o login wroc e faz a busca direta na tabela de usuários (WHERE login = ?).
Tabela Interna de E-mails Privados (O "Oráculo de Identidade"): Mesmo que você não use @users.noreply.github.com e coloque um e-mail pessoal real de alguém no commit, o servidor do GitHub faz um JOIN interno na tabela privada de e-mails cadastrados (user_emails) — mesmo que o e-mail esteja marcado como privado no perfil — e anexa o objeto público do usuário dono daquele e-mail na resposta do commit!
Veja o fluxo completo do que ocorre nos bastidores:

    [1. HORA DO COMMIT - 100% Offline na Máquina Local]
   git commit grava texto puro em .git/objects:
   author: "wroc <wroc@users.noreply.github.com>" (Sem senha / Sem chave SSH)
          |
          v
[2. HORA DO PUSH - Autentica o Transporte, não o Autor!]
   git push conecta usando a chave SSH de "lipebackend"
   Servidor do GitHub valida: "lipebackend é dono do repo, push aceito!"
          |
          v
[3. O JOIN AUTOMÁTICO NO BACKEND DO GITHUB]
   GitHub lê o texto do commit e faz SELECT no Banco de Dados Interno de Usuários
   Encontra o registro real de "wroc" (id: 11420820) sem exigir assinatura dele!
          |
          v
[4. RESPONSE DA API / INTERFACE WEB]
   GET /repos/lipebackend/api-rest/commits/b1a5ca0
   Devolve o JSON com os dados reais da conta de "wroc" e o adiciona em Contributors!
5. Prova de Conceito (PoC): Testando na prática (Dados Anonimizados)
Para tirar a prova dos nove de que qualquer e-mail cadastrado no GitHub (mesmo oculto com "email": null no perfil público) seria resolvido e anexado ao meu repositório sem nenhuma autenticação da outra ponta, fiz um teste controlado com a permissão de um amigo desenvolvedor (dados anonimizados abaixo como dev-exemplo / contato@exemplo.dev)

[1. HORA DO COMMIT - 100% Offline na Máquina Local]
   git commit grava texto puro em .git/objects:
   author: "wroc <wroc@users.noreply.github.com>" (Sem senha / Sem chave SSH)
          |
          v
[2. HORA DO PUSH - Autentica o Transporte, não o Autor!]
   git push conecta usando a chave SSH de "lipebackend"
   Servidor do GitHub valida: "lipebackend é dono do repo, push aceito!"
          |
          v
[3. O JOIN AUTOMÁTICO NO BACKEND DO GITHUB]
   GitHub lê o texto do commit e faz SELECT no Banco de Dados Interno de Usuários
   Encontra o registro real de "wroc" (id: 11420820) sem exigir assinatura dele!
          |
          v
[4. RESPONSE DA API / INTERFACE WEB]
   GET /repos/lipebackend/api-rest/commits/b1a5ca0
   Devolve o JSON com os dados reais da conta de "wroc" e o adiciona em Contributors!
5. Prova de Conceito (PoC): Testando na prática (Dados Anonimizados)

Primeiro, verificamos que no perfil público dele (https://api.github.com/users/dev-exemplo) o e-mail está oculto:

curl -s https://api.github.com/users/dev-exemplo | grep -E '"(login|id|node_id|email)"'
"login": "dev-exemplo",
  "id": 9876543,
  "node_id": "MDQ6VXNlcjk4NzY1NDM=",
  "email": null,

Em seguida, criamos um commit vazio (--allow-empty) no repositório forçando as variáveis de ambiente do Git e enviamos via git push:

GIT_AUTHOR_NAME="dev-exemplo" \
GIT_AUTHOR_EMAIL="contato@exemplo.dev" \
GIT_COMMITTER_NAME="dev-exemplo" \
GIT_COMMITTER_EMAIL="contato@exemplo.dev" \
git commit --allow-empty -m "chore: test commit author resolution" && git push origin HEAD`

Imediatamente consultamos o commit gerado na API do GitHub:

curl -sL https://api.github.com/repos/lipebackend/api-rest/commits/

E o servidor do GitHub respondeu na hora com a conta real vinculada:


{
  "sha": "eaf17be95da2a3210efba894140287493da7b7a4",
  "commit": {
    "author": {
      "name": "dev-exemplo",
      "email": "contato@exemplo.dev",
      "date": "2026-10-17T02:26:52Z"
    },
    "message": "chore: test commit author resolution",
    "verification": {
      "verified": false,
      "reason": "unsigned"
    }
  },
  "author": {
    "login": "dev-exemplo",
    "id": 9876543,
    "node_id": "MDQ6VXNlcjk4NzY1NDM=",
    "avatar_url": "https://avatars.githubusercontent.com/u/9876543?v=4",
    "url": "https://api.github.com/users/dev-exemplo",
    "html_url": "https://github.com/dev-exemplo",
    "type": "User"
  }
}
  1. Implicações de Segurança (AppSec, OSINT e Bug Bounty)
    Embora o Git trate isso como esperado (pois foi projetado por Linus Torvalds de forma descentralizada, onde mantenedores aplicam patches enviados por e-mail mantendo o Author original), a forma como o GitHub resolve e exibe essas contas sem exigir assinatura criptográfica por padrão abre brechas reais:

Oráculo de Desanonimização de E-mail (Email-to-Account Enumeration): Mesmo que um usuário configure "email": null no perfil público do GitHub, qualquer pessoa que possua um endereço de e-mail pode criar um commit de teste em um repositório privado, fazer push e descobrir pela resposta da API qual é a conta do GitHub (login, id, avatar_url) associada àquele e-mail.
Falsificação de Autoria e de Reputação (Commit & Contributor Spoofing): Projetos maliciosos frequentemente injetam commits com o e-mail de desenvolvedores famosos na branch principal para que o GitHub exiba a foto e o perfil oficial dessas pessoas na aba Contributors, gerando falsa credibilidade.
Armadilha para Sistemas Terceiros (Onde moram os Bug Bounties reais): O próprio GitHub documenta esse comportamento como Known Issue / By Design porque expõe o bloco "verification": { "verified": false, "reason": "unsigned" }. Porém, diversas ferramentas externas de CI/CD, bots de deploy e integrações corporativas consomem a API de commits do GitHub, leem apenas response.author.login (ou response.author.id) acreditando que a identidade já foi autenticada pelo GitHub, e esquecem de verificar se commit.verification.verified == true!
7. Como se proteger?
Como desenvolvedor e arquiteto de software, existem três camadas essenciais de proteção:

Assine criptograficamente todos os seus commits (SSH ou GPG Signing): Configure sua chave SSH ou GPG no Git (git config --global commit.gpgsign true) para que todos os seus commits tenham "verified": true e exibam o selo verde Verified no GitHub.
Ative o Modo Vigilante (Vigilant Mode) no GitHub: Em Settings → SSH and GPG keys → Vigilant mode, marque "Flag unsigned commits as unverified". Assim, qualquer commit feito com o seu e-mail sem a sua assinatura criptográfica receberá um alerta explícito Unverified.
Se você desenvolve integrações com a API do GitHub: Nunca confie nos campos author.login ou author.id de um commit para tomar decisões de autorização ou segurança sem antes validar se commit.verification.verified === true.

Carregando publicação patrocinada...
1

Sim, esse é o básico de GIT, que 99% não faz. Assinem todas os seus commits com -s e -S, de preferência com um GPG com boa cadeia de confiança, armazenado em um dispositivo físico autenticado por biometria com certificado FIPS 140-3.

1