EN FR ES PT DE AR 中文

Os Agentes de IA Eliminaram o Último Revisor Humano do Código. O Problema da Cadeia de Abastecimento é Agora Seu.

A geração de código por agentes transferiu, sem ruído, a validação de dependências dos engenheiros para máquinas que não validam nada. A solução não passa por fugir de uma linguagem ou de um repositório, mas por confinar aquilo que os agentes executam.

Peça a um engenheiro para lhe dizer que pacotes foram lançados na funcionalidade da semana passada e, antigamente, obtinha uma resposta. Peça agora, quando foi um agente a escrever o código, e o mais comum é um encolher de ombros. Foi o agente que acrescentou a dependência. Foi o gestor de pacotes que puxou as dependências dessa dependência. Ninguém leu nada disto, porque ler nunca fez parte do fluxo de trabalho para o qual a ferramenta foi vendida. Este é o cerne incómodo da segurança da cadeia de abastecimento nos agentes de codificação com IA.

Vale a pena ser preciso sobre o que significa «foi um agente que o fez», porque a frase esconde três mecanismos distintos. A ferramenta orientada por modelo decide que é precisa uma biblioteca e escreve o import. O gestor de pacotes expande depois esse único nome numa árvore de dependências transitivas que o utilizador nunca escolheu. O plugin do editor executa a instalação, e muitas vezes a suite de testes, dentro do ambiente de desenvolvimento como efeito colateral de «experimentar». Uma pessoa a escolher uma biblioteca pelo menos espreita o repositório, o número de downloads, o último commit. O resolvedor não espreita nada, e o plugin executa o que quer que o resolvedor tenha produzido. Os pacotes três camadas abaixo recebem exatamente o escrutínio que essa cadeia lhes dá, ou seja, nenhum.

Nem todos os agentes se comportam assim, e a ressalva importa. Muitos continuam a parar e a pedir confirmação antes de cada comando de shell, e os fornecedores documentam estes controlos de aprovação: o agente do Cursor tem uma definição de execução automática que corre comandos numa lista branca sem confirmação, e o Claude Code da Anthropic inclui um modo de aceitação automática e listas brancas de permissões a par dos pedidos por omissão. O facto documentado é que estes modos sem necessidade de permissão existem e são precisamente os vendidos pela rapidez. A frequência com que as equipas os deixam ligados não é algo que nenhum fornecedor publique, pelo que a afirmação de que a revisão humana cai para zero deve ser tratada como argumento, não como medição: decorre de como os modos funcionam, não de um inquérito.

E se o pacote que ninguém reviu for malicioso?

O resultado é um modo de falha que já aconteceu, e a contagem está a subir. O State of the Software Supply Chain 2024 da Sonatype registou mais de 512.000 pacotes maliciosos de código aberto num único ano, um aumento de 156% face ao ano anterior. É uma contagem, não uma probabilidade medida, mas a tendência não deixa dúvidas: quanto mais pacotes hostis existirem nos repositórios, maior a probabilidade de qualquer resolução tocar num deles, ainda que ninguém consiga apontar um número exato. Onde se esconderia um pacote desses? A investigação sobre dependências da Endor Labs apurou que cerca de 95% das dependências vulneráveis são transitivas, o tipo indireto que nenhum programador escolhe à mão. São dependências vulneráveis, não maliciosas, pelo que o número mede onde estão as falhas, não onde caem os ataques. Mas ambas partilham o mesmo esconderijo: se as falhas acidentais já se concentram nas camadas que um agente resolve em silêncio, é precisamente aí que código introduzido de propósito também ficaria por ler.

O incidente documentado torna isto tangível. Investigadores de segurança da Snyk descreveram um programador a testar uma integração do editor Cursor que instalou uma cópia envenenada de um pacote popular de Python como dependência transitiva. O código malicioso escreveu um ficheiro de arranque oculto que corria ao ser importado. O que expôs o ataque foi quase cómico: a carga maliciosa gerava subprocessos Python de forma recursiva até a máquina ficar sem memória e encravar. A deteção foi um acidente de esgotamento de recursos, não um controlo que disparou.

Seguindo o mecanismo, a lição é incómoda. Ninguém teve de ser descuidado. O comportamento normal da cadeia de ferramentas bastou: o agente pediu um pacote, o resolvedor acrescentou algo de que esse pacote dependia, a integração executou-o. Nenhuma pessoa escolheu o código malicioso. Três passos automatizados trataram de tudo, do início ao fim, e nenhum deles audita.

Fugir do repositório é o reflexo errado

O instinto depois de um incidente destes é desconfiar do ecossistema: culpar o repositório, refugiar-se numa linguagem com reputação «mais segura». Isso interpreta mal onde está a exposição. Nunca esteve na marca de um repositório. Está em código não auditado, várias dependências abaixo, e todos os ecossistemas públicos têm isso. As contagens de pacotes maliciosos da Sonatype abrangem o npm, o PyPI e os restantes, pelo que mudar de repositório apenas muda a marca em que se desconfia, enquanto o código por ler continua no servidor de build.

Como se confina, na prática, um agente de codificação?

Três controlos fazem a maior parte do trabalho, e nenhum deles é exótico ou hipotético. Cada um visa o mesmo alvo: parar de tentar decidir se um pacote é de confiança e, em vez disso, limitar o que um pacote não confiável pode fazer.

Primeiro, bloqueio de saída de rede por omissão na build. Um agente de build precisa de um índice de pacotes e talvez de um servidor git. Não precisa do endpoint de metadados da nuvem, da rede interna, nem de um servidor arbitrário algures no mundo. No GitHub Actions este é já um controlo implementado através do Harden-Runner da StepSecurity, que bloqueia o tráfego de saída por omissão e aceita uma lista branca:

# .github/workflows: bloquear a saída, permitir apenas o necessário à build
- uses: step-security/harden-runner@v2
  with:
    egress-policy: block
    allowed-endpoints: >
      pypi.org:443
      files.pythonhosted.org:443
      github.com:443

Uma carga maliciosa que tente exfiltrar uma chave SSH já não tem para onde a enviar, e o Harden-Runner regista a chamada bloqueada, pelo que a tentativa fica visível no registo da execução. Limitou-se o que o pacote pode fazer sem ser preciso decidir se é seguro.

Segundo, instalar a partir de um lockfile submetido ao repositório, com hashes, e recusar tudo o que não corresponda. Fixar apenas a versão de topo não chega, porque o perigo está na árvore transitiva. Um lockfile regista cada pacote resolvido e o respetivo hash, pelo que uma versão trocada ou envenenada já não pode chegar em silêncio. Os comandos já existem: o npm ci instala estritamente a partir de um package-lock.json submetido, o pip install --require-hashes -r requirements.txt recusa qualquer descarga cujo hash falte ou esteja errado, e acrescentar --ignore-scripts no npm bloqueia a execução de código no momento da instalação de que muitos ataques dependem. Com os hashes fixados, uma cópia adulterada deixa de ser uma descarga silenciosa e passa a ser uma diferença que uma pessoa, ou um scanner, revê antes de algo correr:

# package-lock.json, exposto no pull request
   "node_modules/log-helper": {
-    "version": "2.4.0",
-    "integrity": "sha512-6b1e2c..."
+    "version": "2.4.1",
+    "integrity": "sha512-00d4af..."

Sem o lockfile, o resolvedor limita-se a ir buscar a nova versão e a alteração é invisível. Com ele, a mutação é uma linha vermelha à espera de revisão, em vez de uma instalação silenciosa.

Terceiro, analisar automaticamente cada alteração de dependências, e falhar a build quando encontrar algo que não consiga justificar. É aqui que as ferramentas já apanharam o comboio. O OSV-Scanner verifica os lockfiles face a uma base de dados pública de vulnerabilidades, e serviços como o Socket inspecionam cada pacote acrescentado à procura de scripts de instalação, código ofuscado e acessos de rede inesperados, logo no próprio pull request. Ligue um destes ao mesmo sandbox e corra-o em cada alteração ao lockfile:

# .github/workflows: falhar o PR perante uma dependência conhecida como maliciosa ou recém-acrescentada
- uses: google/osv-scanner-action@v2
  with:
    scan-args: "--lockfile=package-lock.json"

Acrescentar um modelo por cima é, a meu ver, o ponto em que a economia finalmente pende a favor de quem defende, ainda que esta parte seja argumento e não um benchmark: os mesmos modelos que instalam sem ler também conseguem ler uma diferença de dependências mais depressa do que uma pessoa, sinalizar um script de arranque e traçar um caminho de exfiltração. Usado desta forma, o atacante automatiza o ataque e a equipa automatiza o auditor. Os scanners acima são o núcleo já pronto a implementar; o modelo é um acelerador por cima, não um substituto da sandbox e do lockfile.

Aplicamos a mesma disciplina em cada projeto de consultoria, e ela atravessa também a análise que publicamos: manter a pessoa no circuito onde o julgamento importa, e confinar a máquina onde não importa.

Os agentes não vão voltar a entrar na caixa, nem devem. Mas uma equipa que instala aquilo que nunca lê automatizou a sua própria última linha de defesa. A resposta não é confiar mais nos pacotes. É dar à build um sítio seguro onde correr, um lockfile que torna visível qualquer adulteração, e um scanner que lê cada alteração para que nenhuma dependência chegue à produção por ler.

Perguntas frequentes

Os agentes de codificação com IA são mais perigosos do que um programador humano a instalar pacotes?

É a mesma classe de ataque à cadeia de abastecimento, mas a exposição é maior nos modos de aprovação automática e instalação em segundo plano que as equipas ativam para ganhar velocidade, em que o agente resolve e executa dependências transitivas com pouca ou nenhuma pessoa a olhar para qualquer uma delas, pelo que um compromisso de segurança se pode propagar antes de alguém dar por isso.

Devo mudar de linguagem de programação depois de um compromisso numa dependência?

Não. Todos os ecossistemas públicos têm código transitivo não auditado, e a investigação da Endor Labs situa cerca de 95% das dependências vulneráveis nessa camada, pelo que mudar de linguagem apenas muda a marca em que se desconfia, sem resolver nada. Em vez disso, fixe versões com um lockfile de hashes, verifique a proveniência e isole a execução.

Um agente de IA pode ajudar a defender contra pacotes maliciosos, e não apenas causar o risco?

Sim, e o núcleo já pronto a implementar não é exótico: corra as builds numa sandbox com bloqueio de saída por omissão, como o Harden-Runner, e coloque um scanner automático como o OSV-Scanner ou o Socket em cada diferença de dependências para sinalizar scripts de instalação, ofuscação e chamadas de rede inesperadas. Um modelo pode fazer a triagem das diferenças sinalizadas mais depressa do que uma pessoa, mas isso deve ser tratado como um acelerador sobre o scanner e a sandbox, não como um substituto.

Relacionado

Escrito por uma persona editorial de IA do sistema editorial proprietário da Abyshire e revisto pela nossa equipa.