Pular para o conteúdo principal

ADR 0140 — Timeout repetido do pnpm audit é risco assumido; achado continua reprovando

Context​

Em 2026-09-04 o endpoint de advisories do npm (POST registry.npmjs.org/-/npm/v1/security/advisories/bulk) ficou intermitente por horas. O job Auditoria de dependências reprovou em quatro PRs distintas (#464, #466, #396, #468), sempre com a mesma assinatura:

[WARN] POST .../security/advisories/bulk error (23). Will retry in 10 seconds. 2 retries left.
[WARN] POST .../security/advisories/bulk error (503). Will retry in 1 minute. 1 retries left.
TimeoutError: The operation was aborted due to timeout

Não era a árvore de dependências, não era o runner e não era nenhuma das PRs. Foi reproduzido fora do CI, batendo direto no endpoint a partir de outra máquina: mesma ausência de resposta em 20s, enquanto um GET comum no mesmo registry respondia em 130ms. O pnpm audit já retenta três vezes por dentro (as duas linhas de retry acima); o serviço simplesmente não respondia.

O custo não foi só o tempo. Foi o comportamento que a situação ensina: cada falha dessas se resolve re-rodando o job até passar, e quem aprende a re-rodar até passar re-roda também quando o vermelho é de verdade. Um gate que grita sem motivo não fica neutro — ele corrói a confiança nos gates que gritam com motivo.

Decision​

Decisão do dono do produto: três tentativas sem resposta do registry são RISCO ASSUMIDO. O job segue verde, com aviso alto no resumo.

O que a decisão não afrouxa, e é o que a torna defensável:

situaçãovereditoretentativa
pnpm audit sai 0passa—
relatório de vulnerabilidadereprovanenhuma — a resposta já veio, e é não
requisição não completou (timeout/5xx/DNS/socket)infraaté 3
3 infras seguidaspassa, declarado—
saída não reconhecidareprovanenhuma

A precedência é o coração disto. scripts/ci/auditoria-de-dependencias.ts classifica a saída, e as marcas de ACHADO são testadas antes das de rede: um relatório que por acaso mencione timeout — um pacote com esse nome, um CVE de timeout — não pode ser perdoado como indisponibilidade. Há teste para exatamente esse caso.

Falha desconhecida reprova (fail closed). Se a saída não casa com nenhuma das duas assinaturas, o veredito é achado. A única coisa pior que um gate que reprova demais é um que aprova o que não entendeu.

O aviso diz o que a execução NÃO afirma. Quando o risco é assumido, o resumo do job registra que a árvore não foi auditada — nunca que está limpa. Verde por indisponibilidade e verde por auditoria limpa são estados diferentes, e o job não os confunde no texto.

A lógica mora em script testável, não em bash dentro do YAML: a classificação é função pura com 11 casos, incluindo as saídas REAIS do run 33838507158 (a que motivou tudo) em vez de paráfrases.

Consequences​

Existe uma janela real de risco, e ela está declarada. Numa indisponibilidade longa do npm, PRs entram sem auditoria de dependências. Isso está registrado em cadeia-de-suprimentos-do-ci.md, na seção "What is still trusted on faith", ao lado das outras confianças assumidas — não escondido no comentário de um workflow.

A alternativa foi medida antes de ser recusada. Manter vermelho por queda de terceiro custou, no dia, quatro PRs travadas e várias re-execuções de ~5min cada, sem que nenhuma delas descobrisse coisa alguma sobre o código. O que se compra com essa rigidez é a aparência de rigor; o que se paga é o hábito de ignorar vermelho.

Só o lado pnpm muda. mix hex.audit e mix deps.audit (Elixir) continuam como estavam — o incidente foi do endpoint do npm, e alargar a exceção para quem não a pediu seria transformar uma decisão pontual em política geral.

O que continua sem resposta: não há alerta quando o risco é assumido com frequência. Três timeouts numa PR isolada é ruído de terceiro; três timeouts em toda PR de uma semana é o gate desligado na prática, e hoje nada distingue os dois casos além de alguém reparar. Fica como lacuna declarada, não resolvida aqui.