O segredo que a máscara deixou passar: lendo um vazamento de CI em base64 duplo
O desafio
Uma action de CI hackeada imprimiu isto no log. Remova o base64 que escapou do mascaramento. Recupere o token de publicação npm vazado.
O que você vai aprender
- Explicar como o comprometimento do tj-actions/changed-files vazou secrets de CI
- Reconhecer uma saída decodificada que ainda é base64
- Explicar por que uma segunda camada de codificação derrota o mascaramento de logs do GitHub
- Distinguir um token de job de curta duração de um token de publicação de longa duração
Habilidades testadas
Pré-requisitos
- O que é base64
- O que um pipeline de CI faz
Como funciona
O GitHub Actions esconde secrets nos logs dos jobs: antes de uma linha ser gravada, o runner troca cada valor de secret registrado por ***. Ele também mascara as formas base64 desse valor, nos três deslocamentos de byte, porque echo $TOKEN | base64 é o truque mais velho do mundo.
Em março de 2025, um atacante redirecionou todas as tags do tj-actions/changed-files, usado por cerca de 23.000 repositórios, para um commit malicioso. O código novo lia a memória do processo do runner, filtrava com grep cada entrada "name":{"value":"...","isSecret":true} e as imprimia com base64 -w 0 | base64 -w 0. A segunda camada muda cada caractere, então nada na linha bate com um padrão mascarado. Em repositórios públicos, qualquer pessoa podia abrir a execução e ler os secrets. Um log do repositório fictício deste hack era assim:
##[group]Run ridgeway-actions/changed-paths@v4 with: separator: , files: src/** env: NPM_TOKEN: *** ##[endgroup] SWtOUFJFVkRUMVpmVkU5TFJVNGlPbnNpZG1Gc2RX...
O bloco env: mostra a máscara funcionando sobre o valor em texto puro. A última linha é o mesmo secret, embrulhado duas vezes, e a máscara nunca o viu.
Erros comuns
- Parar depois de uma decodificação. O primeiro resultado ainda tem só letras, dígitos,
+,/e=: isso é base64 de novo. Decodifique até aparecer texto legível. - Responder com o token
ghs_. Ele aparece duas vezes (github_tokenesystem.github.token) porque é oGITHUB_TOKENdo próprio job. O GitHub o gera por job e ele expira quando o job termina. O objetivo é o token de publicação do npm, que continua funcionando depois da execução. - Responder com o valor do Codecov. O UUID pertence ao
CODECOV_TOKEN, um token de upload, não o do npm. - Aplicar base64 uma terceira vez. JSON não é base64, então a etapa falha: essa falha indica que você já chegou ao texto puro.
Como se proteger
- Fixe actions de terceiros em um SHA de commit completo de 40 caracteres, não em uma tag: tags podem ser movidas, que é exatamente o que aconteceu com o tj-actions.
- Publique no npm com trusted publishing (OIDC), para que nenhum token npm de longa duração fique nos seus secrets.
- Entregue um secret só ao job ou ao environment que precisa dele: um job que lista arquivos alterados não tem motivo para ver o
NPM_TOKEN. - Monitore o egress do runner (por exemplo com o StepSecurity Harden-Runner, que detectou o download do payload do tj-actions).
- Depois de um incidente, troque todos os secrets que as execuções afetadas puderam ler e apague os logs públicos.