Gheist no GitHub: como checar se seus secrets vazaram
Gheist usa tokens do GitHub roubados para vazar secrets pelo Actions. Veja como achar as branches ci-sync-, o que trocar agora e como se proteger.
Desde 15 de setembro, uma campanha que a Wiz Research batizou de Gheist usa tokens do GitHub roubados para tirar secrets de repositórios pelo GitHub Actions. Até 24 de outubro ela ainda estava ativa: mais de 13.500 branches criadas em mais de 850 contas. O relatório foi publicado em 25/10 e o The Hacker News repercutiu em 26/10.
O ponto que mais importa: apagar a branch não desfaz nada. Se o workflow rodou, os secrets que ele enxergava já saíram. Este guia mostra como descobrir se você foi atingido, o que fazer na hora e o que muda na configuração para a próxima vez. Todos os passos vêm do relatório da Wiz e da documentação do GitHub, com os links no fim.
O que o Gheist faz
O ataque começa com um token válido de alguém que tem permissão de escrita no repositório. Segundo a Wiz, esses tokens vêm de logs de infostealer e de vazamentos anteriores, como o comprometimento do Trivy em março. Com o token na mão, a ferramenta:
- cria uma branch com nome
ci-sync-seguido de 6 caracteres (letras minúsculas e números); - faz um commit com a mensagem
ci: sync config, sem assinatura e em nome do dono do token, contendo só o arquivo.github/workflows/sync-check.yml; - o push dispara o workflow, que imprime as variáveis de ambiente e todos os secrets em base32;
- a ferramenta, que se identifica com o user agent
gheist, baixa o log pela API e apaga o run segundos depois.
O trecho central do workflow é este:
env | base32 -w 120
echo -n '${{ toJSON(secrets) }}' | base32 -w 120
Dois detalhes explicam por que funciona. Primeiro, o GitHub mascara secrets no log, mas a própria documentação avisa que a máscara não é garantida para valores transformados, e base32 é uma transformação. Segundo, permissions: {} no workflow limita o que o GITHUB_TOKEN pode fazer, mas não esconde os secrets: qualquer pessoa com escrita no repositório consegue ler todos eles por um workflow. Algumas variantes usam uma matriz de environments para puxar também os secrets de environment.
A Wiz identificou duas ferramentas. A segunda, a partir de 19/10, passou a incluir um job sync-host voltado a runners self-hosted, que tenta varrer a máquina atrás de outros tokens e grava arquivos como .gdmp_pat. Esse job tinha bugs e a Wiz não relatou execução bem-sucedida, mas, se você usa runner self-hosted, vale checar (passo 2).
Em repositórios públicos do github.com, a trava que o GitHub lançou em julho segura workflows suspeitos para aprovação, e a Wiz não encontrou runs do Gheist executados ali. Em repositórios privados e no GitHub Enterprise Server o workflow rodou, e em pelo menos um caso as credenciais da AWS que saíram foram usadas para assumir roles e listar o Secrets Manager.

Passo 1: procure as branches ci-sync-
No clone local, este comando lista só branches que começam com ci-sync- no remoto:
git ls-remote origin 'refs/heads/ci-sync-*'
Pela API, sem clonar:
gh api repos/DONO/REPO/git/matching-refs/heads/ci-sync- --jq '.[].ref'
Para uma organização inteira:
gh repo list SUA-ORG --limit 1000 --json nameWithOwner --jq '.[].nameWithOwner' | while read r; do gh api "repos/$r/git/matching-refs/heads/ci-sync-" --jq ".[].ref | \"$r \" + ." done
Repositório vazio pode devolver erro 409; pode ignorar. Se a branch já foi apagada, procure o commit e os arquivos no histórico de quem ainda tem o clone:
git log --all --format='%h %an %ad %s' --grep='^ci: sync config$'
for b in f3da2b8b357dcda02fd455d314be2a95007516a6 \
d09535857a741bbb976bdc084a9a021713133291 \
1dc5af9cce950aad1eb1546ecce8565b117540c4 \
c665b778b2d822453293003e7ca0bbb2cb08e39d; do
git log --all --oneline --find-object=$b
done
Esses quatro hashes são os blobs das versões do sync-check.yml publicadas pela Wiz. Qualquer resultado é sinal forte.
Passo 2: confira o audit log e o runner
Na organização, abra Settings > Audit log e busque:
action:workflows.delete_workflow_run
Run apagado segundos depois de um push em branch ci-sync- é o padrão do ataque. Abra os eventos e veja o campo de user agent: a ferramenta usa gheist. A documentação não lista um filtro de busca direto por user agent, então a conferência é no detalhe do evento ou na exportação do log. Na conta pessoal, o equivalente é o security log em Settings > Security log.
Se você usa runner self-hosted, procure na máquina os arquivos .gdmp_pat, .gdmp_patg e .gdmp_sweep, e a linha ===== gdmp-pivot: nos logs de job.
Passo 3: veja se as credenciais de nuvem foram usadas
Se o repositório usa OIDC com a AWS, cada troca de token fica no CloudTrail como AssumeRoleWithWebIdentity, com o subject do GitHub no evento. Quando o job não usa environment, o subject inclui a branch (repo:DONO/REPO:ref:refs/heads/BRANCH), então dá para procurar ci-sync-:
aws cloudtrail lookup-events \
--lookup-attributes AttributeKey=EventName,AttributeValue=AssumeRoleWithWebIdentity \
--start-time 2026-09-15T00:00:00Z \
--query 'Events[].CloudTrailEvent' --output text |
grep -o 'ci-sync-[0-9a-z]\{6\}' | sort -u
O lookup-events cobre só os últimos 90 dias e uma região por vez; rode em cada região que você usa. Quando o job usa environment, o subject vira repo:DONO/REPO:environment:NOME, sem a branch; nesse caso cruze o horário do evento com o horário do push suspeito. Para chaves de acesso fixas guardadas como secret, procure no CloudTrail chamadas feitas por essas chaves fora do padrão, principalmente listagem do Secrets Manager.
Passo 4: achou? Faça isto agora
- Revogue o token de quem fez o push. O commit sai em nome do dono do token. Revogue PATs em github.com/settings/tokens e, como o token veio de algum lugar, verifique a máquina dessa pessoa contra infostealer antes de gerar um novo.
- Troque todos os secrets que o repositório enxergava: do repositório, dos environments e da organização. Não só os que parecem importantes. Para listar:
gh secret list -R DONO/REPO gh secret list -R DONO/REPO --env NOME_DO_ENVIRONMENT gh secret list --org SUA-ORG
- Invalide no provedor, não só no GitHub. Trocar o valor do secret no GitHub não derruba a chave antiga: gere uma nova na AWS, no banco, na API de pagamento, e revogue a antiga lá. Se o sistema não aguenta a troca de uma vez, veja como fazer em dois passos em trocar segredo sem derrubar o sistema.
- Trate runner self-hosted que rodou o job como comprometido. Reinstale a máquina em vez de limpar.
- Guarde a evidência antes de apagar a branch: exporte o audit log e anote hashes e horários. A documentação de resposta a incidentes do GitHub recomenda preservar os registros.
- Não aprove nenhum run de
sync-checkque esteja aguardando aprovação.
A lógica é a mesma de segredo que vazou no Git: primeiro troca, depois limpa. Explicamos isso em segredo no Git: troque a chave antes de limpar o histórico.
Passo 5: procure persistência
Quem teve escrita no repositório pode ter deixado outras portas. O guia de resposta a incidentes do GitHub manda revisar:
- workflows novos ou alterados em qualquer branch;
- webhooks;
- runners self-hosted registrados;
- GitHub Apps e OAuth apps autorizados;
- deploy keys;
- binários e artefatos de release.
Atualize também os sistemas externos que usavam as credenciais antigas, para não ficar nada apontando para chave revogada.
Como se proteger daqui para frente
Nenhuma dessas medidas impede um token roubado de criar uma branch. Elas reduzem o que essa branch consegue levar.
- Secrets sensíveis em environment com revisores obrigatórios. Secret de environment só é entregue ao job depois da aprovação. Atenção ao plano: em repositório privado, revisores obrigatórios exigem GitHub Enterprise; nos planos Free, Pro e Team, só em repositórios públicos.
- Environment restrito por branch. Em Deployment branches and tags, deixe só
main(ou as branches de release). Em repositório privado, isso está disponível a partir do Pro e do Team. - OIDC no lugar de chave fixa, com trust policy apertada. Restrinja o subject no provedor de nuvem. Se a policy aceita um environment inteiro, lembre que o subject não traz a branch: a restrição de branch do environment é o que segura o ataque.
- Tokens com prazo curto. Prefira GitHub App ou fine-grained token. Na organização, em Personal access tokens, dá para restringir tokens classic, definir prazo máximo e exigir aprovação para fine-grained.
- Runner self-hosted efêmero, que roda um job e é descartado, e nunca em repositório onde muita gente tem escrita. Os riscos estão em runner self-hosted no notebook.
- Menos gente com escrita. Escrita no repositório equivale a acesso a todos os secrets dele. Revise quem tem.
O que checar nos próximos dias
- Rode a busca do passo 1 de novo: a campanha estava ativa em 24/10.
- Confirme no provedor que as chaves antigas estão mesmo inválidas.
- Acompanhe o CloudTrail e os logs dos serviços cujas chaves saíram.
- Defina o prazo máximo de token e o bloqueio de token classic na organização, se ainda não fez.
Fontes
- Wiz Research: Gheist (25/10/2026)
- The Hacker News (26/10/2026)
- GitHub Changelog: workflows suspeitos aguardam aprovação (28/07/2026)
- GitHub Docs: responder a um incidente de segurança
- GitHub Docs: uso seguro do Actions
- GitHub Docs: deployments e environments
- GitHub Docs: referência de OIDC
- GitHub Docs: audit log da organização
- GitHub Docs: política de tokens da organização
- GitHub REST API: referências Git
- AWS CLI: cloudtrail lookup-events