Skip to main content

ADR 0140 — A repeated pnpm audit timeout is an accepted risk; a finding still fails

Context​

On 2026-09-04 the npm advisories endpoint (POST registry.npmjs.org/-/npm/v1/security/advisories/bulk) was intermittent for hours. The Auditoria de dependências job failed on four distinct PRs (#464, #466, #396, #468), always with the same signature:

[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

It was not the dependency tree, it was not the runner and it was none of the PRs. It was reproduced outside CI, hitting the endpoint directly from another machine: the same lack of response within 20s, while an ordinary GET on the same registry answered in 130ms. pnpm audit already retries three times internally (the two retry lines above); the service simply did not answer.

The cost was not only time. It was the behavior the situation teaches: each such failure is resolved by re-running the job until it passes, and whoever learns to re-run until it passes also re-runs when the red is real. A gate that shouts without reason does not stay neutral — it erodes trust in the gates that shout with reason.

Decision​

Product owner's decision: three attempts without a registry response are an ACCEPTED RISK. The job stays green, with a loud warning in the summary.

What the decision does not loosen, and is what makes it defensible:

situationverdictretry
pnpm audit exits 0passes—
vulnerability reportfailsnone — the answer already came, and it is no
request did not complete (timeout/5xx/DNS/socket)infraup to 3
3 infras in a rowpasses, declared—
unrecognized outputfailsnone

Precedence is the heart of this. scripts/ci/auditoria-de-dependencias.ts classifies the output, and the FINDING markers are tested before the network ones: a report that happens to mention timeout — a package with that name, a timeout CVE — cannot be forgiven as unavailability. There is a test for exactly that case.

An unknown failure fails (fail closed). If the output matches neither signature, the verdict is achado. The only thing worse than a gate that fails too much is one that approves what it did not understand.

The warning says what the execution does NOT assert. When the risk is accepted, the job summary records that the tree was not audited — never that it is clean. Green due to unavailability and green due to a clean audit are different states, and the job does not confuse them in the text.

The logic lives in a testable script, not in bash inside the YAML: the classification is a pure function with 11 cases, including the REAL outputs of run 33838507158 (the one that motivated everything) instead of paraphrases.

Consequences​

There is a real risk window, and it is declared. During a long npm outage, PRs get in without a dependency audit. This is recorded in cadeia-de-suprimentos-do-ci.md, in the section "What is still trusted on faith", next to the other assumed trusts — not hidden in a workflow comment.

The alternative was measured before being rejected. Staying red due to a third-party outage cost, on the day, four blocked PRs and several re-runs of ~5min each, without any of them discovering anything about the code. What this rigidity buys is the appearance of rigor; what it costs is the habit of ignoring red.

Only the pnpm side changes. mix hex.audit and mix deps.audit (Elixir) remain as they were — the incident was the npm endpoint's, and widening the exception to those who did not ask for it would turn a one-off decision into a general policy.

What remains unanswered: there is no alert when the risk is accepted frequently. Three timeouts in an isolated PR is third-party noise; three timeouts in every PR for a week is the gate turned off in practice, and today nothing distinguishes the two cases beyond someone noticing. It remains a declared gap, not solved here.