Pular para o conteúdo
blog.kristyan.dev
linux arch-linux aur segurança supply-chain sysadmin

O AUR Foi Comprometido: Supply Chain, Pacotes Órfãos e o Preço de Rodar Código de Estranhos

Uma análise sobre o comprometimento de centenas de pacotes do AUR, o papel dos PKGBUILDs na cadeia de confiança do Arch Linux e como responder tecnicamente a um incidente de supply chain no desktop.


Se você usa Arch Linux por tempo suficiente, em algum momento passa a tratar o AUR como parte natural do sistema. Você instala o sistema base, configura o pacman, escolhe um helper como yay ou paru, e quando percebe está digitando yay -S nome-do-pacote com a mesma tranquilidade com que instalaria algo dos repositórios oficiais.

Esse é exatamente o problema.

Nos últimos dias, surgiram relatos de que mais de 400 pacotes do AUR foram comprometidos com payloads associados a infostealer e rootkit. A lista circulou em um Gist público com um script de checagem chamado aur_check.sh, que basicamente define um array de pacotes suspeitos e verifica, via pacman -Qi, se algum deles está instalado na máquina local.

O script em si não é mágico. Ele não faz análise forense. Ele não prova que sua máquina está limpa. Ele não remove nada. Ele só responde uma pergunta objetiva: algum pacote conhecido nessa lista está instalado no meu sistema?

Mas a existência desse script é o sintoma, não a doença.

A doença é mais profunda. É a combinação de um repositório comunitário gigantesco, pacotes órfãos ou pouco mantidos, helpers que reduzem fricção demais, usuários pulando revisão de PKGBUILD e uma cultura perigosa de tratar o AUR como se fosse um repositório oficial com outro nome.

Não é.

O AUR nunca foi isso.

O AUR não é o pacman com mais pacotes

O Arch Linux tem uma separação muito clara entre os repositórios oficiais e o Arch User Repository. O pacman instala pacotes dos repositórios oficiais assinados e mantidos dentro do ecossistema da distribuição. O AUR, por outro lado, hospeda receitas de build enviadas por usuários. Essas receitas normalmente vêm na forma de um PKGBUILD.

Um PKGBUILD não é um arquivo declarativo inocente como um package.json simplificado ou um manifesto estático. Ele é um script shell. E script shell executa comandos.

Esse detalhe muda tudo.

Fronteira de confiança entre o PKGBUILD remoto e a execução local durante build e instalação

O ponto crítico é quando uma receita remota atravessa a fronteira e passa a executar comandos dentro do seu ambiente.

Quando você instala algo do AUR, o fluxo geral é: baixar o repositório daquele pacote, ler o PKGBUILD, baixar os sources definidos nele, executar as etapas de preparação, build e empacotamento, gerar um pacote compatível com o pacman e então instalar esse pacote no sistema. Em tese, isso é transparente e auditável. Em teoria, é lindo.

Na prática, a maioria das pessoas não lê nada.

O helper pergunta se você quer revisar o diff. Você aperta enter. Ele pergunta se quer continuar. Você aperta enter. Ele baixa meia internet, compila, instala e segue a vida.

Até o dia em que alguém adiciona uma dependência maliciosa, troca uma URL de origem, injeta um comando estranho no prepare(), mexe no .install, ou aproveita algum pacote órfão que ninguém revisa há meses.

Aí o AUR deixa de ser conveniência e vira superfície de ataque.

Já tratei de falar sobre isso aqui.

O PKGBUILD é uma fronteira de confiança

Existe uma diferença importante entre “baixar um software” e “executar uma receita de build escrita por um desconhecido”.

Quando você instala um pacote oficial, ainda existe confiança envolvida, obviamente. Mas existe uma cadeia de manutenção, assinatura, empacotamento, revisão comunitária, infraestrutura da distribuição e um nível de responsabilidade mais claro. Não é perfeito, mas existe uma estrutura.

No AUR, a fronteira de confiança é outra. Você confia que o mantenedor do pacote escreveu um PKGBUILD honesto. Você confia que a URL de origem aponta para o projeto correto. Você confia que o source baixado não foi trocado. Você confia que checksums foram atualizados de forma legítima. Você confia que a função prepare() não executa algo suspeito. Você confia que a função package() não enfia lixo em algum lugar inesperado. Você confia que os hooks de instalação não abusam do momento em que o pacote é instalado com privilégios elevados.

E pior: você renova essa confiança a cada atualização.

Esse é o ponto que muita gente ignora. Não basta ter auditado um pacote uma vez há seis meses. O ataque de supply chain acontece justamente quando algo que já era confiável muda. Um pacote abandonado pode ser adotado por alguém mal-intencionado. Um mantenedor pode ser comprometido. Um upstream legítimo pode ser invadido. Uma dependência transitiva pode virar o vetor real. O pacote que ontem era inofensivo pode hoje estar puxando um payload durante o build.

O AUR é poderoso porque transforma qualquer usuário em empacotador.

O AUR é perigoso pelo mesmo motivo.

O caso dos pacotes comprometidos

O que chamou atenção nesse incidente foi a escala. Não estamos falando de um pacote obscuro isolado com meia dúzia de usuários. Os relatos apontaram centenas de pacotes contaminados em um intervalo curto de tempo, muitos deles aparentemente órfãos ou pouco vigiados. O padrão discutido pela comunidade envolvia alterações em PKGBUILDs e dependências suspeitas, com destaque para uma dependência npm chamada atomic-lockfile.

Esse detalhe importa porque mostra uma tendência cada vez mais comum: ataques modernos de supply chain raramente precisam explorar uma vulnerabilidade sofisticada no kernel, no compilador ou no gerenciador de pacotes. Eles exploram confiança operacional.

Não é preciso quebrar criptografia. Não é preciso achar um zero-day cinematográfico. Basta convencer o usuário a executar o caminho “normal” de instalação.

O desenvolvedor moderno já vive cercado de scripts automáticos. npm install, pip install, curl | bash, make install, docker compose up, yay -S, GitHub Actions, hooks de pre-commit, extensões de editor, plugins de shell, temas de terminal, dotfiles baixados de repositórios aleatórios. O desktop de desenvolvimento virou uma colcha de retalhos de pequenos pontos de execução automática.

O AUR entra nesse cenário como uma peça extremamente conveniente, mas também extremamente sensível. Ele é a cola entre o usuário Arch e quase qualquer coisa que ainda não chegou aos repositórios oficiais. Só que cola demais também gruda sujeira.

O problema não é usar AUR. O problema é usar AUR como se fosse Steam

Seria fácil escrever um texto moralista dizendo “não use AUR”. Também seria inútil.

O AUR é uma das grandes forças do Arch Linux. Ele resolve um problema real: empacotar software que não está nos repositórios oficiais, versões -git, builds customizados, binários proprietários, ferramentas pequenas, utilitários de nicho, temas, drivers, clients experimentais, forks e integrações que dificilmente teriam espaço em uma distribuição mais conservadora.

O problema é quando o AUR vira loja de aplicativo.

A pessoa pesquisa o nome do programa, instala o primeiro resultado, ignora o diff, ignora os comentários, ignora o mantenedor, ignora a data da última atualização, ignora os sources, ignora checksums, ignora dependências novas, e ainda automatiza atualização com --noconfirm.

Isso não é workflow avançado. Isso é superstição com shell.

O usuário Arch gosta de repetir que usa uma distribuição simples, transparente e controlável. Mas transparência só serve se você olhar. O PKGBUILD estar disponível não protege ninguém se a revisão vira teatro. O helper mostrar diff não resolve nada se o diff é tratado como tela de loading.

O AUR exige uma postura diferente. Não é “instalar pacote”. É aceitar temporariamente uma receita de build escrita por terceiros dentro do seu ambiente local.

Essa frase deveria pesar mais.

O que um pacote malicioso consegue fazer?

A resposta honesta é: depende do ponto em que ele executa, do usuário que executa e do quanto seu ambiente está exposto.

Durante o build, um PKGBUILD malicioso pode rodar comandos como seu usuário normal. Isso já é suficiente para muita coisa ruim. Seu usuário normalmente tem acesso ao seu $HOME, às suas chaves SSH, aos seus tokens salvos em arquivos de configuração, ao cache de navegadores, às credenciais de CLIs, ao histórico do shell, aos projetos de trabalho, aos repositórios Git, aos arquivos .env, às sessões de ferramentas de cloud e a uma quantidade absurda de material sensível.

Muita gente pensa em segurança Linux apenas como “virou root ou não virou root”. Isso é uma visão pobre. Para um infostealer, root é bônus. O ouro está no usuário.

Se o objetivo é roubar tokens do GitHub, credenciais npm, chaves SSH, arquivos de configuração de cloud, carteiras, cookies, secrets de projeto ou variáveis persistidas em dotfiles, o usuário normal já é suficiente. Um cat ~/.ssh/id_ed25519, uma busca por .env, uma leitura de ~/.config, um acesso a tokens de CLI e uma requisição HTTP para fora podem causar estrago antes de qualquer prompt de sudo aparecer.

Já durante a instalação do pacote gerado, o risco pode subir. Pacotes podem ter scripts de instalação executados pelo pacman. Dependendo do que foi empacotado e de como o fluxo foi montado, código pode rodar em contexto privilegiado. Aí a conversa deixa de ser roubo de credencial e vira persistência, manipulação de serviços, alteração de binários, hooks, unidades systemd, modificações em diretórios globais e outros vetores mais desagradáveis.

É por isso que “removi o pacote” não é necessariamente resposta suficiente.

Se o pacote malicioso só estava instalado mas nunca foi executado, talvez o dano seja limitado. Se ele rodou durante build ou install, você precisa tratar como incidente. E se há indícios de rootkit, persistência ou alteração de componentes do sistema, a resposta tecnicamente responsável pode ser reinstalar a máquina a partir de mídia confiável e rotacionar credenciais a partir de outro dispositivo limpo.

É chato. É trabalhoso. Mas segurança não liga para conveniência.

Como eu checaria minha máquina

A primeira reação de muita gente quando aparece um Gist com script de checagem é rodar:

curl alguma-coisa | bash

Não faça isso.

Esse reflexo é exatamente parte do problema cultural que permite ataques de supply chain. O incidente envolve execução de código não confiável. A resposta não pode ser executar mais código não confiável sem ler.

O fluxo correto é baixar o arquivo, inspecionar, entender e só depois executar. No caso do aur_check.sh, a versão que circulou é relativamente simples: define um array com os nomes dos pacotes afetados, itera por eles e usa pacman -Qi para ver se algum está instalado. Ainda assim, a própria interface do GitHub alerta para caracteres Unicode ocultos ou bidirecionais. Isso não significa automaticamente que o script é malicioso, mas é motivo suficiente para abrir em um editor que mostre caracteres invisíveis, usar cat -A, sed -n, grep, file, xxd ou qualquer ferramenta que permita enxergar o que realmente está no arquivo.

Eu faria algo nessa linha:

mkdir -p /tmp/aur-malware-check
cd /tmp/aur-malware-check
curl -L -o aur_check.sh '<raw-do-gist>'
file aur_check.sh
sed -n '1,80p' aur_check.sh
tail -n 40 aur_check.sh
grep -nE 'curl|wget|bash -c|eval|base64|chmod|chattr|systemctl|crontab|sudo|su|rm -rf|nc|python|perl|ruby|node' aur_check.sh
cat -A aur_check.sh | sed -n '1,120p'

Depois de revisar, eu executaria sem sudo:

chmod +x aur_check.sh
./aur_check.sh | tee aur_check_result.log

Mas eu não pararia aí. O script é uma ajuda, não uma autoridade. Eu também compararia a lista de pacotes suspeitos com os pacotes instalados localmente. O objetivo é gerar uma lista limpa dos afetados de verdade, sem depender só da saída visual.

pacman -Qqm | sort > foreign-packages.txt

O comando acima lista pacotes estrangeiros, ou seja, pacotes instalados que não pertencem aos repositórios oficiais conhecidos pelo pacman. Nem todo pacote estrangeiro veio necessariamente do AUR, mas no desktop Arch comum essa lista é um excelente ponto de partida para enxergar sua exposição.

Se algum pacote da lista aparecer instalado, a remoção precisa ser cuidadosa. Eu não sairia apagando metade do sistema no pânico. Primeiro verificaria se o pacote é explicitamente instalado ou dependência, olharia dependentes reversos, leria o log do pacman e só então removeria com pacman -Rns ou pelo helper, sem permitir rebuild ou reinstalação automática.

pacman -Qi nome-do-pacote
pactree -r nome-do-pacote
sudo pacman -Rns nome-do-pacote

Depois da remoção, eu rodaria a checagem de novo. Se o resultado indicar que nenhum pacote conhecido permanece instalado, ótimo. Mas isso só responde à pergunta sobre presença de pacote. Não responde à pergunta sobre comprometimento do usuário, persistência ou exfiltração anterior.

Remover pacote não desfaz roubo de token

Esse é o ponto mais importante do texto.

Quando falamos de infostealer, a linha do tempo importa. Se o código malicioso rodou ontem, roubou seus tokens e você removeu o pacote hoje, os tokens continuam roubados. Remoção local não faz a chave SSH voltar para dentro da gaveta. Não invalida token do GitHub. Não troca senha. Não revoga sessão. Não limpa um segredo vazado dentro de algum log remoto.

É por isso que resposta a incidente não é só limpeza de pacote. É contenção, erradicação e recuperação.

Se um dos pacotes afetados estava instalado e principalmente se foi atualizado durante a janela do ataque, eu consideraria a máquina suspeita. A partir de outro dispositivo limpo, eu rotacionaria senhas importantes, revogaria tokens de GitHub, npm, PyPI, Docker Hub, registries privados, provedores cloud e qualquer serviço usado em CLI. Eu revisaria chaves SSH, GPG, arquivos .env, credenciais de deploy, webhooks e secrets de CI/CD.

Também olharia o básico de persistência local: unidades systemd de usuário, unidades systemd globais recém-criadas, crontabs, arquivos em ~/.config/systemd/user, alterações em shell startup files como .bashrc, .zshrc, .profile, .config/fish/config.fish, binários estranhos em ~/.local/bin, entradas suspeitas no autostart e arquivos recentes em locais como /tmp, ~/.cache e diretórios de build do AUR.

Não é para sair deletando tudo. É para investigar com calma. Deletar evidência antes de entender o que aconteceu pode atrapalhar mais do que ajudar.

Se houver sinal de rootkit ou persistência privilegiada, a abordagem pragmática é parar de fingir que dá para “limpar na mão” com meia dúzia de comandos copiados de fórum. Rootkit joga em outra categoria. Nesse cenário, backup seletivo de dados, reinstalação limpa e rotação de credenciais é o caminho sensato.

A pergunta não é “consigo salvar essa instalação?”. A pergunta é “quanto tempo vale a minha confiança?”.

Helpers são conveniência, não modelo de segurança

Eu gosto de helpers. Uso esse tipo de ferramenta porque ninguém merece clonar manualmente cada repositório do AUR, rodar makepkg, instalar pacote, acompanhar atualização e repetir o processo para tudo. yay e paru existem porque resolvem um problema real de ergonomia.

Mas helper não é auditor.

O fato de uma ferramenta mostrar diff não significa que você leu. O fato de ela perguntar se deseja revisar não significa que a revisão aconteceu. O fato de ela automatizar build não significa que o build é seguro. Automatizar uma decisão ruim só faz a decisão ruim acontecer mais rápido.

O maior erro é configurar atualização do AUR como se fosse atualização oficial do sistema. Rodar tudo com --noconfirm, pular diff, aceitar alteração em lote e atualizar pacote AUR obscuro sem olhar é transformar o seu desktop em pipeline de CI de desconhecidos.

Pacotes oficiais eu atualizo com muito mais tranquilidade. Pacotes AUR eu trato como exceção. Se eu puder usar pacote oficial, uso pacote oficial. Se o software oferece release binária confiável, assinatura e checksum bem documentados, avalio. Se existe Flatpak mantido de forma decente, pode ser melhor para certos aplicativos de desktop. Se preciso do AUR, tento manter o número de pacotes pequeno e revisável.

Isso não é paranoia. É redução de superfície.

Como revisar um PKGBUILD de verdade

Revisar PKGBUILD não é apenas olhar se o nome do pacote parece certo. É observar as mudanças.

A primeira coisa é conferir source. De onde o código vem? É o repositório oficial do projeto? É um release versionado? É um arquivo jogado em algum domínio aleatório? É um binário pré-compilado sem assinatura? A URL mudou nesta atualização? O checksum mudou porque saiu uma versão nova ou porque alguém trocou o arquivo?

Depois vem a lógica do script. Funções como prepare(), build(), check() e package() devem fazer sentido para o tipo de software empacotado. Um pacote simples que de repente começa a chamar curl, wget, npm install, bash, python -c, base64 -d, chmod +x em arquivo baixado dinamicamente ou escreve em lugares estranhos merece atenção. Não porque todo uso desses comandos seja malicioso, mas porque esse é justamente o tipo de ponto em que payloads se escondem.

Também vale olhar arquivos .install. Eles podem definir ações de post_install, post_upgrade e pre_remove. Isso é útil para pacotes legítimos, mas também é um lugar óbvio para persistência. Se um pacote de tema, fonte, ícone, utilitário pequeno ou app simples quer mexer com serviço, daemon, cron, systemd ou diretório global sensível, a sobrancelha tem que subir.

A revisão ideal é comparar o diff da atualização, não reler o pacote inteiro do zero sempre. O que mudou desde a versão anterior? Entrou dependência nova? O maintainer mudou? O pacote foi adotado recentemente? O upstream mudou de lugar? Alguém comentou algo suspeito na página do AUR? O pacote ficou órfão por muito tempo e voltou do nada?

Esse é o tipo de pergunta que separa usuário avançado de usuário que só decorou comando.

O problema estrutural: supply chain é arquitetura

Esse incidente não é só “malware no AUR”. É mais uma manifestação do mesmo problema que aparece em npm, PyPI, Docker Hub, GitHub Actions, extensões de editor, plugins de shell e templates de projeto.

A indústria inteira empilhou automação em cima de confiança implícita.

A gente baixa dependência de dependência de dependência, executa script de install, roda gerador de projeto, instala plugin, importa action, puxa container, baixa binário de release, cola comando de README e depois finge surpresa quando alguém compromete um elo fraco.

O AUR só torna isso mais visível porque o modelo é honesto. Ele não esconde que você está executando receita de usuário. Está tudo lá. O PKGBUILD é texto. O histórico está no Git. O diff pode ser visto. A página avisa que aquilo é conteúdo produzido por usuários. A responsabilidade nunca deixou de existir.

O problema é que conveniência anestesia responsabilidade.

E isso não é exclusivo de Arch. Usuário Ubuntu que cola curl | bash de qualquer README está no mesmo jogo. Usuário macOS que instala fórmula Homebrew sem olhar source também participa. Desenvolvedor Node que roda npm install em projeto aleatório está confiando em lifecycle scripts. Usuário Docker que roda imagem aleatória com volume montado no $HOME também está brincando com a fronteira de confiança.

O Arch só não tenta te proteger de você mesmo.

Isso é virtude e defeito ao mesmo tempo.

O que eu mudaria no meu próprio uso

Depois de um incidente assim, minha conclusão não é abandonar o Arch. Também não é abandonar o AUR. A conclusão é tratar AUR como exceção operacional, não como padrão de instalação.

Eu começaria reduzindo minha lista de pacotes estrangeiros. pacman -Qqm deveria ser uma lista que eu consigo explicar. Se tem coisa ali que não uso há meses, sai. Se existe alternativa oficial, migro. Se instalei só para testar, removo. Se é pacote abandonado e sem comentários recentes, reavalio. Se é binário proprietário que baixa coisa de um domínio opaco, penso duas vezes.

Também evitaria atualização cega de AUR. Atualizar o sistema com pacotes oficiais é uma coisa. Atualizar receitas comunitárias de build sem ler diff é outra. Eu prefiro gastar dois minutos revisando alterações do que duas horas tentando descobrir por que apareceu uma unidade systemd suspeita no meu usuário.

Outra medida prática é separar melhor os ambientes. Nem todo app experimental precisa viver no host principal. Ferramentas de teste podem rodar em container, VM, Distrobox descartável ou usuário separado. Isso não elimina risco, especialmente se você monta seu $HOME inteiro dentro do ambiente, mas reduz blast radius quando feito com cuidado.

E, principalmente, eu deixaria menos segredo solto no desktop. Tokens permanentes em arquivo de texto, .env antigos esquecidos, chaves SSH sem passphrase, credenciais cloud logadas para sempre, PAT amplo no GitHub, npm token global, tudo isso transforma qualquer infostealer simples em desastre caro. O atacante não precisa ser brilhante se o ambiente está servido em bandeja.

Conclusão: o AUR continua incrível, mas não é inocente

O AUR é uma das melhores partes do Arch Linux. Também é uma das mais perigosas quando usada sem disciplina.

Esse incidente dos pacotes comprometidos não deve ser lido como “Arch é inseguro” ou “Linux pegou vírus”. Essa leitura é preguiçosa. O ponto real é mais interessante: sistemas abertos deslocam parte da responsabilidade para o usuário. Você ganha controle, transparência e flexibilidade. Em troca, perde a desculpa de dizer que ninguém avisou.

O AUR sempre avisou.

PKGBUILD é código. Código executa. Código de terceiros exige revisão. Helper é conveniência. Conveniência não é segurança. Pacote removido não desfaz credencial roubada. Máquina comprometida não volta a ser confiável só porque o terminal ficou verde de novo.

No fim, esse caso é menos sobre 400 pacotes específicos e mais sobre maturidade operacional. A pergunta não é se você usa Arch. A pergunta é se você entende a cadeia de execução que você mesmo autorizou.

Porque quando você digita yay -S sem ler nada, você não está apenas instalando um programa.

Você está assinando um contrato de confiança com alguém que talvez você nunca tenha visto na vida.

E contrato de confiança, em infraestrutura, sempre cobra juros.