Principais conclusões
- O Go identifica um pacote com base em onde ele está hospedado, e não pelo nome de um usuário específico; portanto, um nome de usuário do GitHub que tenha expirado pode, por si só, se tornar um problema na cadeia de suprimentos.
- O banco de dados de checksum garante que todos recebam o mesmo código para uma determinada versão, e não que a versão tenha vindo de alguém em quem você confia.
- O GitHub retira de circulação namespaces populares, mas os downloads disponibilizados pelo espelho de módulos do Go não são registrados como os clones que acionam essa proteção.
- Os caminhos que precisam ser auditados são aqueles pequenos e transitórios que ninguém lê.
Em dezembro de 2023, pesquisadores da VulnCheck procuraram repositórios em Go no GitHub que estivessem em um estado recuperável, e o número acabou sendo maior do que a maioria das pessoas imaginaria. Mais de 15.000. Mais de 9.000 ficaram expostos devido a uma mudança de nome de usuário no GitHub, e outros mais de 6.000 pelo cancelamento de contas. Juntos, esses repositórios contêm mais de 800.000 versões de módulos Go.
Nada disso exige credenciais roubadas ou um pipeline de compilação comprometido. Basta que uma conta expire e que alguém perceba isso antes do próprio titular.
Essa contagem já tem alguns anos, e ninguém publicou uma nova. O mecanismo por trás disso, porém, não mudou, e a razão pela qual isso funciona no Go e quase em nenhum outro lugar se resume à forma como o Go define o que é um pacote.
Por que a identidade de um pacote “Go” é uma localização, e não um nome
A maioria dos principais registros mantém um registro de propriedade. O npm sabe quem tem permissão para publicar o axios. O PyPI sabe quem pode enviar uma nova versão do requests. O Maven Central verifica um namespace antes que alguém possa reivindicar um groupId sob ele.
No Go, o caminho também funciona como endereço.
github.com/someuser/somelib não é um nome registrado em um registro do Go. Trata-se de um caminho de módulo que indica à cadeia de ferramentas onde procurar, e quem controla esse local controla quais versões poderão ser disponibilizadas a partir dele no futuro. Quando a conta por trás dele desaparece e outra pessoa reivindica o nome de usuário, o caminho de importação no seu go.mod passa a apontar para o código dessa pessoa.
Trata-se de algo totalmente diferente das campanhas de invasão de contas que têm ganhado destaque ultimamente, como PolinRider. Essa campanha também atingiu módulos do Go em 2026, mas o mecanismo não é o mesmo. Esses invasores assumiram o controle de contas que ainda estavam ativas e, em vários casos, reescreveram tags do Git para ocultar o que haviam feito. O “repojacking” é mais discreto e, de certa forma, mais fácil, pois não é necessário invadir nada. Uma vez que a conta desaparece, não resta mais nada para defender.
A resolução de módulos do Go é elegante, e vale a pena destacar isso antes de abordarmos o que há de errado nela. O comando `go get github.com/user/lib`, por padrão, resolve a chamada por meio do proxy de módulos, registra uma versão e grava um hash. Sem conta, sem envio ao registro, sem pipeline de publicação. Basta enviar uma tag de versão com o nome correto e a versão passa a existir, sem nenhuma etapa separada de publicação no meio.
Isso funciona porque o caminho de importação é a fonte de verdade. O caminho indica à cadeia de ferramentas onde procurar, e nenhum outro sistema registra quem tem permissão para inserir código nesse local.
Compare o que cada registro realmente sabe sobre um pacote.
| Registro | Identidade é | Registro de titularidade | Recuperável após o abandono |
| npm | Um nome registrado | Sim, vinculado à conta | Não, os nomes continuam reservados |
| PyPI | Um nome registrado | Sim, vinculado à conta | Somente por meio de um processo formal |
| Maven Central | Um namespace vinculado a um domínio verificado ou a um repositório de origem | Sim, namespace verificado | Sim, se o domínio expirar |
| crates.io | Um nome registrado | Sim, vinculado à conta | Não, os nomes continuam reservados |
| Packagist | Um nome registrado | Sim, vinculado à conta | Não, os nomes continuam reservados |
| Módulos Go | Um local do VCS | Nenhum | Sim, sempre que o local ficar disponível |
O Maven tem sua própria versão desse problema por meio de domínios expirados, que foi o que a pesquisa do MavenGate descreveu em janeiro de 2024. Os IDs de grupo são derivados de domínios; portanto, um domínio que expira cede o namespace a ele associado.
O Packagist também tem um caso documentado. Em um incidente ocorrido em 2022, um pesquisador invadiu o repositório do GitHub por trás de um pacote do Packagist e conseguiu publicar um malware, pois o Packagist estava rastreando o GitHub em busca de lançamentos sem uma etapa separada de publicação. Essa brecha específica já foi corrigida, e após o incidente de reescrita de tags do Laravel-Lang em maio de 2026, o Packagist foi além e tornou as versões estáveis imutáveis, o que foi anunciado em maio e implementado em julho. Vale a pena esclarecer o que isso corrige, no entanto. Versões imutáveis impedem que alguém altere a referência Git de uma versão publicada. Elas não impedem que alguém reivindique um namespace abandonado e publique algo novo sob ele.
No Go, esse segundo cenário é a configuração padrão para qualquer módulo hospedado em uma conta pessoal do GitHub. A própria equipe de segurança do GitHub apontou o Go como uma das três formas pelas quais as compilações podem baixar código diretamente de um repositório em sua postagem de 2024 sobre repojacking, ao lado do GitHub Actions e dos submódulos do Git, e sua recomendação para esses dois é fixar em um commit específico. O Go possui uma camada de integridade que eles não têm, já que as versões dos módulos são autenticadas em relação ao go.sum e, por padrão, em relação ao banco de dados público de checksums. É aí que a questão fica mais interessante do que parece à primeira vista.
Existe também uma versão desse risco que se manifesta de forma mais gradual. Uma grande parte dos componentes de código aberto presentes em algum lugar do código de produção não recebe um commit há anos, e os projetos inativos são exatamente aqueles cujos mantenedores acabam excluindo uma conta ou deixando um domínio expirar. Na maioria dos ecossistemas, isso representa um problema de aplicação de patches. No Go, isso também constitui uma vulnerabilidade à invasão.
Como funciona o “repojacking” contra um caminho de importação
Dois eventos no nível da conta liberam um caminho no GitHub. A exclusão de uma conta libera o nome de usuário, e a alteração de um nome de usuário libera o antigo. De qualquer forma, github.com/olduser/lib fica disponível, e quem quer que registre esse nome de usuário em seguida poderá recriar um repositório com o nome correspondente e passar a ser o proprietário do caminho. A exclusão de um único repositório em uma conta que ainda existe não causa isso, já que ninguém mais pode criar um repositório dentro da sua conta.
O GitHub oferece proteção contra isso, e essa proteção é mais restrita do que a maioria das pessoas imagina. A aposentadoria de namespace bloqueia permanentemente uma combinação de nome de proprietário e nome de repositório, de modo que ela não possa ser reutilizada, e abrange repositórios que tiveram mais de 100 clones ou mais de 100 utilizações do GitHub Actions na semana anterior da renomeação ou exclusão, além de qualquer repositório que contenha uma ação listada no GitHub Marketplace.
Isso significa que a proteção abrange os pacotes com os quais todos já se preocupam e deixa a “cauda longa” de fora.
No caso específico do Go, essa “cauda longa” acaba sendo enorme, e a mesma pesquisa que identificou esses mais de 15.000 repositórios explicou o motivo. O espelho de módulos do Go baixa um repositório uma única vez e, em seguida, disponibiliza essa cópia para todos os demais usuários. Um módulo pode estar por trás de milhares de chamadas `go get` sem que esses downloads servidos por proxy sejam registrados como clones no GitHub; assim, uma biblioteca muito utilizada ainda pode ficar abaixo do limite de clones que, de outra forma, protegeria seu namespace. Para uma biblioteca comum do Go, que não seja uma GitHub Action nem esteja no Marketplace, a contagem de clones subestima bastante o uso a jusante.
Uma biblioteca de utilitários com 40 clones por semana, importada transitivamente para algumas centenas de projetos, está bem abaixo do limite, de qualquer forma. Esses são os caminhos que realmente precisam de auditoria, e são também os caminhos que ninguém lê, pois chegaram a quatro níveis de profundidade na árvore de dependências de outra pessoa.
A aposentadoria é avaliada no momento em que a conta é renomeada ou excluída, e não de forma contínua. Um repositório que era popular há dois anos e que ficou inativo desde então pode não ter atingido o limite na semana em questão. Não há nenhuma API para verificar isso nem uma lista pública; portanto, a única maneira de descobrir é tentar criar o repositório e ver se o GitHub informa que o nome foi aposentado e não pode ser reutilizado.
Por que o banco de dados de checksum não pode atestar uma nova versão
A infraestrutura de checksum do Go é realmente boa, melhor do que a da maioria dos ecossistemas. Saber exatamente o que ela garante — e até onde essa garantia se estende — é a diferença entre sentir-se protegido e estar protegido.
sum.golang.org mantém um registro de hashes de módulos, que só pode ser acrescentado e é publicamente auditável. Quando uma compilação identifica uma versão de módulo público que ainda não consta no go.sum, a cadeia de ferramentas procura esse hash no registro antes de gravá-lo localmente, de modo que o primeiro download não fique sem verificação. Compilações posteriores comparam com o que já está registrado, e o comando `go mod verify` verifica se as cópias no seu cache local de módulos não foram modificadas desde que foram baixadas.
Todo esse sistema é muito eficiente em uma única função. Ele garante que todos que acessarem example.com/[email protected] recebam os mesmos bytes, hoje e daqui a cinco anos. Se você republicar um conteúdo diferente em uma versão que já tenha sido registrada, a incompatibilidade se tornará evidente.
O “repojacking” nunca altera uma versão registrada. O invasor publica uma versão totalmente nova, ou um caminho completamente diferente, e o banco de dados de checksums faz exatamente o que foi projetado para fazer. No caso de uma versão genuinamente nova, o conteúdo que o invasor disponibiliza pode se tornar o conteúdo registrado para aquele módulo e versão, sendo autenticado fielmente para todos os usuários a partir daquele momento. O hash é real. A entrada no log é real. Nada em nenhum dos dois indica que o código veio do mantenedor em quem você confiava originalmente.
| Cenário | Hash anterior para essa versão | Detectada uma tentativa de invasão |
| Versão gravada republicada com novo conteúdo | Sim | Sim, discrepância na soma de verificação |
| Nova versão secundária de um caminho sequestrado | Não, versão totalmente nova | Não |
| Nova dependência adicionada após uma aquisição | Não | Não |
| A dependência transitiva ultrapassou o ponto de aquisição | Não, versão totalmente nova | Não |
Atualize uma dependência ou adicione uma nova, e a cadeia de ferramentas poderá comprovar que os bytes são os mesmos que todos os outros estão recebendo. Isso não é o mesmo que comprovar que eles pertencem à sua compilação.
O proxy do módulo em proxy.golang.org ajuda nesse sentido, dentro de certos limites. O espelho mantém as versões que já baixou e pode continuar disponibilizando-as mesmo depois que elas desaparecerem no repositório original; assim, uma versão baixada antes de uma aquisição permanece disponível e inalterada, mesmo que o repositório seja posteriormente excluído ou substituído. Essa é uma propriedade de segurança real. Ela se aplica apenas às versões já armazenadas em cache antes da aquisição e não tem nada a ver com a próxima versão.
O que resta é a questão que nenhuma soma de verificação responde: se o conteúdo exibido na primeira resolução merece, de fato, confiança. Analisar um módulo antes de sua resolução e recompilá-lo a partir do código-fonte para comparar com o que o caminho serviu é um controle diferente, que opera em uma etapa anterior da sequência. Essa é a camada SecureChain para Go acrescenta à frente do proxy.
Caminhos de importação personalizados abrem uma segunda rota
O “repojacking” contra nomes de usuário do GitHub é a versão que ganhou um nome e conta com algumas pesquisas que a respaldam. A versão que recebe muito menos atenção envolve domínios personalizados.
O Go suporta caminhos de importação personalizados por meio de metatags HTML. Ao resolver example.com/mylib, a cadeia de ferramentas é redirecionada para https://example.com/mylib?go-get=1, que retorna uma metatag `go-import` indicando o local real no VCS, e a cadeia de ferramentas segue esse caminho. É assim que golang.org/x/… funciona, e é assim que uma empresa publica módulos em seu próprio domínio.
O modo de falha é óbvio, principalmente porque não exige que o invasor seja astuto. Se o domínio expirar, quem quer que o registre em seguida controla todos os caminhos de módulos abaixo dele e pode redirecionar esses caminhos para onde quiser. Não há limite de contagem de clones aqui, nem aposentadoria de namespace, nem o GitHub envolvido de forma alguma.
Pequenas empresas de consultoria, startups que já não existem mais, projetos de código aberto conduzidos por uma única pessoa — é isso que fica exposto. Um domínio expirado também é mais barato e mais discreto de se adquirir do que um nome de usuário no GitHub com algum histórico associado a ele.
go list -m -f ‘{{.Path}}’ all \
| grep -v ‘^github.com/\|^golang.org/\|^gopkg.in/’ \
| cut -d/ -f1 | sort -u
Isso mostra todos os hosts não óbvios a partir dos quais sua compilação é resolvida. Descubra quem controla cada um desses domínios e fique de olho no status do registro, pois um domínio prestes a expirar — ou que simplesmente pareça abandonado — é uma dependência com um prazo determinado.
Verificação dos caminhos dos módulos quanto ao risco de apropriação indevida
Duas verificações, ambas rápidas. Execute ambas em todos os repositórios Go que sua organização possui, não apenas no serviço principal que todo mundo já acompanha.
Comece sinalizando as URLs dos repositórios do GitHub que não retornam mais uma resposta indicando que o repositório está ativo.
go list -m -f ‘{{.Path}}’ all | grep ‘^github.com/’ \
| cut -d/ -f1-3 | sort -u \
| while read p; do
printf ‘%s %s\n’ “$(curl -s -o /dev/null -w ‘%{http_code}’ “https://$p”)” “$p”
done | grep -v ‘^200’
Um código 404 é um sinal de alta prioridade de que o caminho não está mais disponível publicamente — vale a pena investigar antes de considerá-lo um risco confirmado de “repojacking”, já que também pode significar que o repositório passou a ser privado. Um código 301 geralmente indica uma mudança de nome ou uma transferência de conta, e a verificação deliberadamente não segue redirecionamentos para que o status exibido seja exatamente o que você vê. Assim que um invasor se apropria de um caminho, a verificação volta ao código 200; portanto, isso detecta a situação antes da invasão, e não depois dela.
Então, observe o que ficou para trás entre os caminhos que realmente levam a algum lugar.
go list -m -u all | grep ‘\[‘
Isso indica os módulos para os quais há versões mais recentes disponíveis, o que serve mais como um ponto de partida do que como uma resposta. Um módulo que está várias versões atrasado, enquanto o desenvolvedor original continua lançando novas versões, deve ser comparado com a atividade real do repositório.
Incorpore a verificação de erros 404 no CI com periodicidade semanal, em vez de tratá-la como uma auditoria pontual. Uma conta que hoje é proprietária de um desses repositórios pode ser excluída ou renomeada no mês que vem, e o intervalo de tempo antes que alguém reivindique o nome de usuário antigo é o aviso gratuito que você recebe.
O que realmente fazer com um módulo cujo mantenedor original desistiu de vez é uma questão mais complexa do que simplesmente encontrá-lo. Tornar o código de propriedade da empresa significa que sua equipe agora é responsável por seus CVEs, e a maioria das organizações que opta por esse caminho não conta com pessoal dedicado a isso. A retroportação de correções para um código do qual o mantenedor original se afastou é uma disciplina específica, centrada em testes de regressão e compatibilidade comportamental, em vez de novos recursos — e é geralmente por isso que acaba ficando fora do âmbito de responsabilidade de uma equipe de produto.
O que alterar nas suas compilações do Go
O Go não possui um gancho de ciclo de vida no estilo do npm, nem um equivalente ao `postinstall`; portanto, baixar uma dependência não oferece ao invasor um ponto de execução gratuito. Essa é uma vantagem genuína em relação ao npm e ao PyPI.
No entanto, essa vantagem tem um limite. O código em um módulo Go ainda é executado, eventualmente, sempre que algo que o importe for compilado, testado ou executado — geralmente por meio de uma função `init()` acionada na inicialização. O gatilho automático no momento da instalação desaparece. O caminho do código, porém, permanece, o que constitui um problema diferente, e não um problema menor.
Por onde começar depende de onde você já está. Se os caminhos dos módulos nunca foram auditados, execute a verificação 404 em todos os repositórios esta semana, pois uma hora de trabalho gera uma lista concreta a partir da qual você pode trabalhar. Se suas dependências incluírem módulos em contas pessoais do GitHub, transfira-os para o diretório `vendor` usando o comando `go mod vendor` e faça o commit do resultado; isso coloca o conteúdo revisado das dependências em seu próprio repositório, de modo que as compilações que utilizam o diretório `vendor` nunca busquem essas versões de um caminho que possa mudar de mãos.
Se você resolver módulos a partir de domínios personalizados, verifique o status de registro de cada um, pois um domínio personalizado expirado representa uma invasão mais abrangente do que um único nome de usuário e não possui nenhum limite de proteção. Se você usa o GOPRIVATE ou o GONOSUMDB, analise os padrões cuidadosamente. O GONOSUMDB exclui módulos correspondentes da verificação do banco de dados de checksum, e o GOPRIVATE atua como padrão para essa exclusão, a menos que você o substitua; portanto, um curinga mais abrangente do que o pretendido remove silenciosamente a verificação do banco de dados de checksum de mais módulos do que você pretendia isentar. E se o Go coexistir com o npm, o PyPI, o Maven ou o crates.io na sua organização, defina uma política única para todos eles, pois os invasores atuam em vários ecossistemas e proteger bem apenas um deles apenas transfere o problema para outro lugar.
Nada disso é uma solução definitiva. Não há garantia de que um módulo que hoje seja seguro — porque seu mantenedor ainda está ativo — continue assim.
A exposição depende muito da proporção da sua estrutura que está em contas organizacionais em comparação com as contas pessoais. Um serviço Go que dependa principalmente de golang.org/x, dos principais SDKs de nuvem e de projetos da CNCF geralmente terá menos desse problema específico. Um serviço que incorporou seis bibliotecas utilitárias mantidas por um único desenvolvedor em 2019 tem muito trabalho pela frente, e é por meio da auditoria que você descobre quais dependências precisam de atenção.
Onde o SecureChain para Go se encaixa
As verificações acima não impedem que uma invasão ocorra. Elas indicam onde você está vulnerável, o que é útil, mas não é o mesmo que prevenção. A lacuna que permanece é decidir se uma nova versão autenticada do módulo merece confiança, em vez de apenas confirmar que todos receberam os mesmos bytes, e é isso que o o SecureChain for Go foi desenvolvido para lidar com isso. Ele recompila os módulos a partir do código-fonte, os verifica no momento da obtenção e continua corrigindo vulnerabilidades conhecidas mesmo depois que o desenvolvedor original do módulo deixa de fornecer atualizações. A página do produto traz os detalhes, e o boletim informativo da TuxCare está disponível caso você prefira acompanhar as pesquisas sobre a cadeia de suprimentos específicas para Go à medida que forem sendo publicadas.
Perguntas frequentes
O que é “repojacking”?
Recuperar o caminho de um repositório de origem do qual um projeto ainda depende, geralmente após a conta original ter sido excluída ou renomeada. Como o caminho ainda é referenciado nos arquivos de dependência, o código publicado no local recuperado resulta em compilações que se baseiam no repositório original.
Por que o Go está especialmente sujeito ao repojacking?
O Go identifica os módulos por meio de seu caminho no VCS, e não por um nome registrado com um registro de propriedade. Não existe uma conta no registro que detenha o domínio github.com/user/lib; portanto, quando o nome da conta subjacente ficar disponível, quem quer que o reivindique passará a controlar o caminho do módulo.
O banco de dados de checksum do Go impede o “repojacking”?
Não por si só. Isso garante que todos que baixarem uma determinada versão do módulo recebam o mesmo conteúdo, inclusive no primeiro download, quando o hash for consultado no registro público. O que isso não consegue determinar é se uma versão totalmente nova publicada a partir de um caminho invadido é confiável, pois uma versão genuinamente nova publicada a partir desse caminho pode se tornar o conteúdo registrado com o qual todos os usuários a jusante passarão a se autenticar.
O GitHub impede a apropriação indevida do namespace de um repositório?
Parcialmente, e principalmente para repositórios populares. A aposentadoria do namespace bloqueia uma combinação de nome de proprietário e nome de repositório quando o repositório teve mais de 100 clones ou mais de 100 utilizações do GitHub Actions na semana anterior, ou contém uma ação listada no Marketplace. Projetos menores ficam abaixo desse limite, e seus namespaces permanecem disponíveis para reivindicação. Para Go, o critério de clones é ainda mais flexível, já que o espelho do módulo pode atender a milhares de downloads sem gerar clones.
Como posso identificar dependências do Go que possam ser recuperadas?
Liste os caminhos dos módulos com o comando `go list -m -f ‘{{.Path}}’ all` e, em seguida, verifique se a URL de cada repositório ainda retorna uma resposta válida. Um erro 404 em um caminho do qual sua compilação depende requer uma investigação mais aprofundada, já que geralmente significa que o repositório upstream não existe mais, mas também pode significar que ele passou a ser privado. De qualquer forma, vale a pena verificar esse caminho antes de presumir que ele está seguro, e é recomendável fazer essa verificação periodicamente, em vez de apenas uma vez.
O sistema de fornecimento protege contra o “repojacking”?
Sim, para dependências já enviadas para o seu diretório de fornecedores. O processo de “vendoring” copia o conteúdo das dependências para o seu repositório, e o modo “vendor” do Go não acessa a rede durante a compilação. Ele não protege novas dependências ou atualizações de versão, que continuam sendo resolvidas a partir do caminho normalmente.
Os módulos do Go executam código durante a instalação?
Os módulos Go não possuem scripts de ciclo de vida de dependências comparáveis aos ganchos de instalação do npm. Um código malicioso em um módulo Go é executado quando algo que o importa é executado, seja um teste, uma ferramenta de compilação ou um binário de produção, geralmente por meio de uma função init() que é acionada durante a inicialização.



