Versão maliciosa do pacote elementary-data no PyPI rouba credenciais de nuvem de engenheiros de dados
27 de abril de 2026
0 minutos de leituraUm pacote Python no PyPI chamado elementary-data, com mais de 1 milhão de downloads por mês, foi alvo de um ataque à cadeia de suprimentos por meio de um vetor de ataque do GitHub Actions.
Resumo
Advisory | |
Severidade | Crítica (CVSS v4.0: 9.3) |
Pacote afetado |
|
Versões seguras | Todas as versões, exceto |
Tipo de ataque | Cadeia de suprimentos (injeção no CI/CD do GitHub Actions, seguida de pacote que rouba credenciais) |
Credenciais roubadas | Perfis do dbt, credenciais do Snowflake/BigQuery/Redshift, chaves da AWS/GCP/Azure, tokens de API, chaves SSH, arquivos |
Escopo | O pacote CLI do PyPI e uma imagem Docker foram comprometidos; o Elementary Cloud e o pacote Elementary dbt não foram afetados |
Indicador de detecção |
|
Divulgação | 25 e 26 de abril de 2026 |
O que é elementary-data?
elementary-data é uma ferramenta CLI nativa do dbt para observabilidade de dados, usada por engenheiros de dados e analytics para monitorar a integridade de pipelines, detectar anomalias e acompanhar falhas em testes em data warehouses como Snowflake, BigQuery, Redshift e Databricks. O pacote tem cerca de 280 mil downloads por semana e mais de 1,1 milhão por mês, o que o coloca entre as ferramentas de dados amplamente adotadas.
O pacote oferece integrações com a maioria das principais plataformas de dados em nuvem, justamente o que o tornou um alvo atraente. Uma ferramenta que lida rotineiramente com conexões ao Snowflake, BigQuery e AWS durante a execução de CI/CD fica ao lado de muitas credenciais valiosas.
Como o ataque aconteceu
O comprometimento ocorreu em duas etapas: primeiro, a invasão do pipeline de publicação; depois, a publicação de conteúdo malicioso para roubar outras credenciais. Vale observar que esse incidente de segurança envolvendo o pacote elementary-data é um dos vetores mais notórios explorados recentemente pelo TeamPCP e por outros agentes de ameaça.
Etapa 1: injeção de script no GitHub Actions
Em 24 de abril de 2026, às 22h10 UTC, um atacante usando uma conta do GitHub criada havia dois dias (realtungtungtungsahur) publicou um comentário elaborado na PR #2147 do repositório elementary-data. O comentário explorava uma falha de injeção de script em .github/workflows/update_pylon_issue.yml, um workflow que tratava eventos de comentários em issues e PRs.
O bloco run: vulnerável interpolava diretamente ${{ github.event.comment.body }} em um script de shell antes da análise do bash. Como essa expressão é expandida no momento em que o template do workflow é processado, em vez de ser higienizada como um argumento de string, a inserção de metacaracteres ou subcomandos de shell no corpo do comentário permite executar código arbitrário no runner. Quando o workflow foi acionado, o payload do atacante foi executado com o GITHUB_TOKEN do repositório disponível.
É importante destacar que o atacante não precisava de acesso direto de gravação ao repositório. O GITHUB_TOKEN disponível no runner tinha permissões suficientes para criar commits, enviar tags e iniciar outros workflows. O job handle_comment injetado permaneceu ativo por duas horas e 46 minutos, dando ao atacante uma janela prolongada para preparar cada etapa seguinte.
Com o token roubado, o atacante forjou um commit de release com o hash b1e4b1f3aad0d489ab0e9208031c67402bbb8480. O commit foi estruturado para parecer automatizado e oficial: era um commit órfão (inalcançável a partir de qualquer branch), atribuído a github-actions[bot], trazia uma assinatura PGP "Verified" forjada e usava a mensagem release/v0.23.2 (#2188) — copiada literalmente de uma PR legítima mesclada nove dias antes. O atacante marcou esse commit órfão com a tag v0.23.3 e, em seguida, iniciou o próprio workflow Release package do repositório, usando tag=v0.23.3 como entrada. A etapa de checkout desse workflow usava ref: ${{ inputs.tag || github.ref }}, então a compilação foi feita diretamente a partir do commit órfão malicioso, sem tocar em master. O pipeline legítimo de CI/CD empacotou e publicou o código malicioso. Às 22h20 UTC, elementary-data==0.23.3 já estava disponível no PyPI. Quatro minutos depois, foi publicada uma imagem Docker comprometida (ghcr.io/elementary-data/elementary:0.23.3 e :latest, digest sha256:31ecc5939de6d24cf60c50d4ca26cf7a8c322db82a8ce4bd122ebd89cf634255).
Esse vetor de ataque já apareceu diversas vezes no ecossistema PyPI. O ataque à cadeia de suprimentos da Ultralytics em dezembro de 2024 usou o mesmo padrão de injeção em pull_request_target para roubar credenciais e publicar quatro versões maliciosas. O comprometimento do LiteLLM no início de 2026 seguiu um caminho um pouco diferente (uma GitHub Action de terceiros adulterada), mas chegou ao mesmo resultado: tokens roubados do PyPI usados para publicar um pacote que rouba credenciais.
Etapa 2: o pacote malicioso
O atacante inseriu o payload malicioso em um arquivo chamado elementary.pth, incluído no diretório site-packages do pacote.
Os arquivos .pth configuram caminhos do Python e são processados automaticamente por site.py, o módulo de inicialização do Python, quando o interpretador é iniciado. Qualquer linha de um arquivo .pth que comece com import é executada como código Python na inicialização do interpretador, antes que seu próprio código seja executado. Isso significa que o malware é ativado sempre que o Python é iniciado no sistema afetado, inclusive durante operações de pip install, e não apenas quando alguém importa explicitamente elementary.
Essa técnica também foi usada no comprometimento do LiteLLM v1.82.8. Ela é mais persistente e difícil de detectar do que inserir código malicioso em __init__.py, porque não exige que a vítima importe o pacote adulterado. Basta instalá-lo.
Dentro do payload: o que o malware fazia
O código inserido em elementary.pth roubava credenciais e usava três etapas de criptografia: uma camada externa em base64, seguida de criptografia XOR baseada em um fluxo de chaves MD5 (semente: swabag) e, depois, uma segunda camada de descriptografia XOR. A ofuscação não é sofisticada para os padrões atuais de malware, mas é intencional: dificulta a detecção trivial do payload por meio de strings e aumenta o tempo necessário para analisar o que o pacote realmente faz.
Quando o Python era iniciado em uma máquina afetada, o payload decodificado:
1. Coletava credenciais e segredos em todo o sistema de arquivos, procurando uma ampla variedade de dados:
Perfis do dbt (
~/.dbt/profiles.yml) e credenciais de data warehouses (Snowflake, BigQuery, Redshift, Databricks).Credenciais de provedores de nuvem: AWS
~/.aws/credentials, além de credenciais de funções ativas obtidas pelo endpoint de metadados IMDSv2, com chamadas diretas assinadas com SigV4 ao AWS Secrets Manager e ao SSM Parameter Store; GCPapplication_default_credentials.json; diretórios do Azure~/.azure/.Chaves privadas SSH (
id_rsa, id_ed25519, ~/.git-credentials).Segredos de contêineres e orquestração:
~/.docker/config.json,~/.kube/config,todos os/etc/kubernetes/*.confarquivos e tokens de ServiceAccount do Kubernetes.Credenciais de gerenciadores de pacotes:
~/.npmrc,~/.pypirc,~/.cargo/credentials.toml.Outros segredos armazenados: arquivos
.env*(busca em até seis níveis de diretório),~/.vault-token,~/.netrc,~/.pgpass,~/.my.cnfe tokens de API em variáveis de ambiente.Arquivos de carteiras de criptomoedas (Bitcoin, Litecoin, Dogecoin, Zcash, Dash, Monero, Ripple, Ethereum, Cardano e pares de chaves de validadores Solana).
Arquivos do sistema:
/etc/passwd,/etc/shadow, arquivos de histórico do shell e/var/log/auth.log.
2. Compactava todo o material coletado em um arquivo chamado trin.tar.gz e o exfiltrava com curl --data-binary para o servidor C2 em igotnofriendsonlineorirl-imgonnakmslmao.skyhanni.cloud, usando o cabeçalho HTTP X-Rise-To-The-Trinny: agree.
3. Deixava um arquivo indicador em $TMPDIR/.trinny-security-update (Linux/macOS) ou %TEMP%\.trinny-security-update (Windows), indicando que o malware foi executado pelo menos uma vez.
O alcance das credenciais vai muito além do dbt e dos data warehouses. O payload foi desenvolvido para vasculhar amplamente qualquer segredo acessível na máquina, incluindo clusters do Kubernetes, gerenciadores de segredos de infraestrutura e chaves de criptomoedas. O foco no dbt e em data warehouses torna o ataque relevante para a base de usuários da ferramenta, mas qualquer pessoa que a execute em uma máquina de desenvolvimento ou runner de CI pode perder muito mais.
O perfil das credenciais visadas combina perfeitamente com o dos usuários típicos da ferramenta. É quase certo que engenheiros de dados que executam a CLI elementary-data a utilizem conectada a um data warehouse, com credenciais de provedores de nuvem, muitas vezes em um ambiente de CI/CD no qual essas credenciais ficam armazenadas como segredos ou variáveis de ambiente. Este é um ataque direcionado, não uma tentativa indiscriminada.
Impacto e escopo
A janela do ataque começou em 24 de abril, às 22h20 UTC (quando o pacote apareceu no PyPI), e terminou quando o pacote foi removido em 25 de abril, entre 8h51 e 11h51 UTC, depois que membros da comunidade sinalizaram o problema às 6h18 UTC. Foram cerca de oito a dez horas de exposição.
Quem se enquadrar em qualquer uma das situações a seguir deve presumir que o malware foi executado e que suas credenciais foram exfiltradas:
executou
pip install elementary-dataou fez uma atualização durante esse período;usou uma imagem Docker obtida do registro elementary-data entre 24 de abril, às 22h24 UTC, e a remoção;
ou tinha um pipeline de CI/CD que baixava automaticamente a versão mais recente.
O Elementary Cloud e o pacote Elementary dbt não foram afetados, e nenhuma outra versão da CLI continha o código malicioso.
Detecção: você foi afetado?
Etapa 1: confira a versão instalada
Se o resultado mostrar Version: 0.23.3, seu ambiente ficou exposto.
Etapa 2: procure o indicador de execução
O malware cria um arquivo indicador quando é executado:
A presença desse arquivo significa que o código que rouba credenciais foi executado nesse ambiente. A ausência não garante que o ambiente esteja seguro: o malware pode não ter criado o indicador em todos os caminhos de execução, ou o diretório temporário pode ter sido limpo.
Etapa 3: verifique com a Snyk
Para verificar se há esse e outros pacotes maliciosos ou vulneráveis conhecidos nas dependências Python:
O banco de dados de vulnerabilidades da Snyk inclui SNYK-PYTHON-ELEMENTARYDATA-16316110 e sinalizará qualquer ambiente que ainda esteja fixado na versão 0.23.3.
Observação: recomendamos consultar nosso guia de boas práticas de segurança em Python e o artigo sobre boas práticas para conteinerizar aplicações Python com Docker para seguir as diretrizes de desenvolvimento seguro.
Correção
1. Atualize imediatamente
A versão 0.23.4 foi publicada em 25 de abril de 2026 e não contém código malicioso.
Se você usa um arquivo requirements.txt ou pyproject.toml, atualize a versão fixada:
2. Troque todas as credenciais que possam ter sido expostas
Considere comprometidas todas as credenciais acessíveis a processos Python nas máquinas afetadas. Especificamente:
Perfis do dbt: troque as senhas do warehouse e os tokens OAuth em
~/.dbt/profiles.yml.Chaves de provedores de nuvem: troque ou revogue as chaves do IAM da AWS (e confira no Secrets Manager e no SSM Parameter Store quais valores foram acessados), as chaves de contas de serviço do GCP e as entidades de serviço do Azure.
Kubernetes: troque os tokens de ServiceAccount e audite todos os arquivos
/etc/kubernetes/*.confque estavam acessíveis.Registros de contêineres: troque as credenciais armazenadas em
~/.docker/config.json.Tokens de gerenciadores de pacotes: troque os tokens de
~/.npmrc,~/.pypirce~/.cargo/credentials.toml.Gerenciadores de segredos: troque os tokens do HashiCorp Vault (
~/.vault-token) e quaisquer credenciais em.netrc,.pgpassou.my.cnf.Chaves SSH: se havia chaves privadas na máquina, considere-as expostas e troque-as.
Secrets de CI/CD: se a máquina afetada era um executor de CI, troque todos os secrets armazenados nesse ambiente.
Só a troca não basta. Analise os logs de acesso ao domínio de exfiltração igotnofriendsonlineorirl-imgonnakmslmao.skyhanni.cloud e aos seus serviços para detectar acessos não autorizados que talvez já tenham ocorrido.
3. Limpe os caches do Python
4. Baixe imagens Docker íntegras
A imagem comprometida (ghcr.io/elementary-data/elementary:0.23.3 e :latest) tinha o digest sha256:31ecc5939de6d24cf60c50d4ca26cf7a8c322db82a8ce4bd122ebd89cf634255. A última imagem comprovadamente íntegra é a 0.23.2, com o digest sha256:b3bbfafde1a0db3a4d47e70eb0eb2ca19daef4a19410154a71abee567b35d3d9. Baixe uma imagem íntegra criada após 25 de abril de 2026:
Verifique se você não está usando uma cópia em cache da imagem comprometida:
5. Audite seus fluxos de trabalho do GitHub Actions
Se você mantém pacotes Python, este incidente é um alerta para auditar qualquer fluxo de trabalho que processe eventos de comentários em issues ou PRs. Procure especificamente por expressões de contexto sem aspas interpoladas diretamente em blocos run::
O artigo da Snyk sobre vulnerabilidades do GitHub Actions e a análise do comprometimento do TJ Actions abordam outros padrões que merecem atenção.
Além de sanitizar as entradas, a solução mais duradoura é remover dos secrets do seu fluxo de trabalho os tokens de API do PyPI de longa duração. O PyPI oferece suporte a Trusted Publishers, que usam tokens OIDC de curta duração, restritos a um fluxo de trabalho específico em um repositório específico — tokens que não podem ser exfiltrados e reutilizados. O invasor do elementary-data precisou de um secret de longa duração para publicar; o Trusted Publishers elimina essa superfície de ataque. Além disso, mantenha os fluxos de trabalho de release com privilégios elevados protegidos por etapas de aprovação manual, que exijam confirmação humana antes da publicação.
Um padrão que se repete
Este ataque segue um roteiro já conhecido: encontrar uma brecha na configuração do GitHub Actions de um projeto, injetar código para roubar o token de publicação do PyPI, usar esse token para publicar uma versão maliciosa e incluir um arquivo .pth ou um mecanismo de inicialização semelhante para ampliar o alcance.
O mesmo padrão apareceu no ataque à Ultralytics (dezembro de 2024, injeção de branch via pull_request_target, com minerador de criptomoedas), no ataque à LiteLLM (início de 2026, ação Trivy envenenada, com ladrão de credenciais e backdoor persistente) e no incidente Cline/Clinejection (injeção de prompt assistida por IA no Actions, com roubo de tokens).
O padrão não é novo. As ferramentas para explorá-lo são bem conhecidas e parecem estar sendo usadas repetidamente. Para quem mantém pacotes, a prioridade é reforçar os fluxos de trabalho: restrinja o the use of pull_request_target, exija aprovação manual para fluxos de release, use tokens OIDC de curta duração para publicar no PyPI em vez de tokens de API de longa duração e implemente regras de proteção de branches para impedir acionamentos de release não autorizados.
Para quem usa elementary-data, a equipe do Elementary agiu rapidamente: levou menos de quatro horas entre o relato da comunidade e a primeira medida de correção, e a equipe publicou um relatório completo do incidente. Essa rapidez na resposta merece destaque, assim como o próprio incidente.
Proteja sua cadeia de suprimentos com a Snyk
87% dos entrevistados foram afetados por problemas de segurança na cadeia de suprimentos. Mantenha a sua protegida com a Snyk.



