Skip to main content

Versão maliciosa do pacote elementary-data no PyPI rouba credenciais de nuvem de engenheiros de dados

Escrito por
blog feature toolkit

27 de abril de 2026

0 minutos de leitura

Um 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

elementary-data==0.23.3

Versões seguras

Todas as versões, exceto 0.23.3; atualize para 0.23.4

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 .env

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

$TMPDIR/.trinny-security-update (Linux/macOS), %TEMP%\.trinny-security-update (Windows)

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; GCP application_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/*.conf arquivos 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.cnf e 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-data ou 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

pip show elementary-data

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:

# Linux / macOS (checks $TMPDIR, which defaults to /tmp if unset)
ls -la "${TMPDIR:-/tmp}/.trinny-security-update"

# Windows (PowerShell)
Test-Path "$env:TEMP\.trinny-security-update"

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:

# Scan your project dependencies
snyk test --file=requirements.txt

# Or for pip-based environments
pip freeze > requirements_check.txt && snyk test --file=requirements_check.txt

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

pip install --upgrade elementary-data

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:

elementary-data>=0.23.4

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/*.conf que 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, ~/.pypirc e ~/.cargo/credentials.toml.

  • Gerenciadores de segredos: troque os tokens do HashiCorp Vault (~/.vault-token) e quaisquer credenciais em .netrc, .pgpass ou .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

# Remove any cached .pth files or bytecode
pip cache purge

# Find and remove any elementary.pth artifacts
find / -name "elementary.pth" 2>/dev/null

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:

docker pull ghcr.io/elementary-data/elementary:latest

Verifique se você não está usando uma cópia em cache da imagem comprometida:

docker images ghcr.io/elementary-data/elementary --digests

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::

# Vulnerable: ${{ github.event.comment.body }} expands before bash parses the command
- run: echo "Comment: ${{ github.event.comment.body }}"

# Safe: pass untrusted input through an environment variable
- run: echo "Comment: $COMMENT_BODY"
  env:
    COMMENT_BODY: ${{ github.event.comment.body }}

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.

Leia mais

feature insights context
Blog

Os ataques autônomos já chegaram. A defesa precisa acompanhar o ritmo.

Os atacantes autônomos estão reduzindo o tempo disponível para a defesa. Saiba como a descoberta, a correção, a validação e a prevenção contínuas ajudam as equipes de segurança a acompanhar esse ritmo.

Blog

Por que agentes de programação com IA continuam criando falhas de controle de acesso

Agentes de programação com IA podem gerar uma lógica de autorização que compila e passa pela revisão, mas permite que um tenant acesse os dados de outro. Saiba por que é difícil detectar falhas de controle de acesso e como evitá-las.

Blog

Evo ADS Govern Agent Behavior já está disponível: controle o uso de MCP

Evo ADS Govern Agent Behavior já está disponível, começando por MCP Governance. Descubra, aprove, monitore, registre e bloqueie o uso de servidores MCP nos principais agentes de programação com IA.