Skip to content

ci(security): the npm audit gate fails an audit-endpoint error exactly like a high advisory — retry, and report 'could not check' distinctly #16337

Description

@mrveiss

Problem

On 2026-09-11, merge train #16321 went red on Frontend Testing Suite / Security Scan (run 34591254177, job 103246844388). The cause wasn't an advisory. npm audit --audit-level=high exited 1 because the registry answered npm error audit endpoint returned an error / error: 'Bad Request'. An earlier audit in the same job had already reported "2 vulnerabilities (1 low, 1 moderate)", both below the high gate.

The gate can't tell "we couldn't check" apart from "we found a high advisory". Both are the same red check, and only reading the log separates them (MEASUREMENT_DISCIPLINE.md: nothing found versus did not look). A registry hiccup therefore blocks a merge exactly the way a real finding does, and there's no retry.

Proposal

  • Keep failing closed. A security gate that couldn't run must not pass.
  • Retry the audit call a bounded number of times on an endpoint or network error, with the attempt count configurable through an env-backed constant.
  • Separate the three results. "Endpoint unavailable after N attempts" gets its own exit path and job-summary message. A real high or critical finding keeps its existing message. A pass stays a pass.
  • Test all three results with a fixture of npm audit JSON and error output.

Related: #16131, where the same gate audits only 1 of 13 tracked package.json files. The same workflow could take both.

Acceptance criteria

  • An audit endpoint error is retried, and if it persists, the job fails with a message naming the endpoint error, not advisories.
  • A high or critical finding still fails, labelled "advisories found", with the critical and high counts in the headline and the job summary. Amended 2026-09-11 (fix(ci): gate the frontend npm audit on one report, retry it, and report 'could not check' apart from advisories (#16337) #16357). The original read "with the advisory message, unchanged". That message was npm's own --audit-level=high output, printed by the second audit call this fix removes, so it can't stay word-for-word the same. The full npm report is still uploaded as the artifact.
  • The job summary states which of the three results happened.
  • A fixture test covers all three.

Activity

  1. mrveiss commented on Sep 11, 2026

    @mrveiss
    OwnerAuthor

    More evidence (from the merge-train reviewer session):

    Acceptance criterion added:

    • The gate no longer depends on the retiring audits/quick endpoint. Identify which npm version and code path fall back to it, and move the gate onto the current bulk advisory endpoint, for example by pinning or upgrading npm, or by calling the audit the supported way. Prove it with a run whose log shows the bulk endpoint in use.

    Why this matters: a retry only covers transient errors. Once the endpoint is retired, every frontend PR and base fails this job for good.

  2. mrveiss commented on Sep 11, 2026

    @mrveiss
    OwnerAuthor

    Design finding, from reading .github/workflows/frontend-test.yml "Run npm audit". The step calls the audit service twice:

    1. npm audit --json > audit-results.json || true fetches the machine-readable report and uploads it as the artifact. On 2026-09-11 this call succeeded (1 low, 1 moderate).
    2. npm audit --audit-level=high is the gate, and it makes a second network call for data the first one already fetched. This is the call that got the 400.

    Proposed fix, which also covers the ACs above:

    • Gate on the JSON the step already has. Fail when metadata.vulnerabilities.high + critical > 0, with the advisory message, unchanged.
    • If audit-results.json is missing, can't be parsed, or carries an error object, fail with a distinct "audit endpoint unavailable, could not check" message. That keeps the gate fail-closed.
    • Retry the single network call a bounded number of times, set by an env-backed constant, before concluding it's unavailable.

    That's one network call instead of two, and "didn't look" is reported separately from "found". No .npmrc is tracked in the repo, so nothing forces the legacy endpoint. The audits/quick hit appears to be npm's own fallback after its bulk call failed. Confirming that is part of the endpoint AC.

  3. mrveiss commented on Sep 11, 2026

    @mrveiss
    OwnerAuthor

    AC2 amended. The review of #16357 asked for this change to be made explicit, rather than meeting the criterion in spirit.

    • Before: "still fails with the advisory message, unchanged".
    • Now: a high or critical finding still fails, labelled "advisories found", with the critical and high counts in the headline and the job summary.
    • Why: the old message was npm's own --audit-level=high output. The second audit call printed it, and the fix removes that call, because an endpoint failure on it read exactly like an advisory.
    • The full npm report is still uploaded as audit-results.json.
  4. added this to the v0.9.0 milestone on Sep 12, 2026
  5. mrveiss commented on Sep 12, 2026

    @mrveiss
    OwnerAuthor

    Closing: all 4 criteria met on Dev_new_gui (4a7e76e28), delivered by #16357.

    AC Evidence
    1. Endpoint errors retried, persistent one fails naming the error pipeline-scripts/npm_audit_gate.py:101 retry_delay_seconds; :208 "Only unavailable is retried"; ANNOTATION_TITLES[UNAVAILABLE] = "npm audit: could not check" (:63)
    2. High/critical still fails as "advisories found" with counts :229 f"**Failed, advisories found:** {critical} critical and ..."
    3. Job summary states which of the three results happened ANNOTATION_TITLES maps FOUND/UNAVAILABLE to distinct titles that land in the summary
    4. A fixture test covers all three npm_audit_gate_test.py:65 (low/moderate pass), :81 (endpoint error → unavailable, named), :122 (no usable report → unavailable, never a pass), :163 (timeout → unavailable)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Projects

    No projects

      Milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions